##// END OF EJS Templates
Added lightweight journal option for visual
marcink -
r2952:029a40c5 beta
parent child Browse files
Show More
@@ -1,508 +1,513
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.settings
3 rhodecode.controllers.admin.settings
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 settings controller for rhodecode admin
6 settings controller for rhodecode admin
7
7
8 :created_on: Jul 14, 2010
8 :created_on: Jul 14, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import formencode
28 import formencode
29 import pkg_resources
29 import pkg_resources
30 import platform
30 import platform
31
31
32 from sqlalchemy import func
32 from sqlalchemy import func
33 from formencode import htmlfill
33 from formencode import htmlfill
34 from pylons import request, session, tmpl_context as c, url, config
34 from pylons import request, session, tmpl_context as c, url, config
35 from pylons.controllers.util import abort, redirect
35 from pylons.controllers.util import abort, redirect
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37
37
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 HasPermissionAnyDecorator, NotAnonymous
40 HasPermissionAnyDecorator, NotAnonymous
41 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.celerylib import tasks, run_task
42 from rhodecode.lib.celerylib import tasks, run_task
43 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
43 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
44 set_rhodecode_config, repo_name_slug, check_git_version
44 set_rhodecode_config, repo_name_slug, check_git_version
45 from rhodecode.model.db import RhodeCodeUi, Repository, RepoGroup, \
45 from rhodecode.model.db import RhodeCodeUi, Repository, RepoGroup, \
46 RhodeCodeSetting, PullRequest, PullRequestReviewers
46 RhodeCodeSetting, PullRequest, PullRequestReviewers
47 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
47 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
48 ApplicationUiSettingsForm, ApplicationVisualisationForm
48 ApplicationUiSettingsForm, ApplicationVisualisationForm
49 from rhodecode.model.scm import ScmModel
49 from rhodecode.model.scm import ScmModel
50 from rhodecode.model.user import UserModel
50 from rhodecode.model.user import UserModel
51 from rhodecode.model.db import User
51 from rhodecode.model.db import User
52 from rhodecode.model.notification import EmailNotificationModel
52 from rhodecode.model.notification import EmailNotificationModel
53 from rhodecode.model.meta import Session
53 from rhodecode.model.meta import Session
54 from rhodecode.lib.utils2 import str2bool
54 from rhodecode.lib.utils2 import str2bool
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 class SettingsController(BaseController):
59 class SettingsController(BaseController):
60 """REST Controller styled on the Atom Publishing Protocol"""
60 """REST Controller styled on the Atom Publishing Protocol"""
61 # To properly map this controller, ensure your config/routing.py
61 # To properly map this controller, ensure your config/routing.py
62 # file has a resource setup:
62 # file has a resource setup:
63 # map.resource('setting', 'settings', controller='admin/settings',
63 # map.resource('setting', 'settings', controller='admin/settings',
64 # path_prefix='/admin', name_prefix='admin_')
64 # path_prefix='/admin', name_prefix='admin_')
65
65
66 @LoginRequired()
66 @LoginRequired()
67 def __before__(self):
67 def __before__(self):
68 c.admin_user = session.get('admin_user')
68 c.admin_user = session.get('admin_user')
69 c.admin_username = session.get('admin_username')
69 c.admin_username = session.get('admin_username')
70 c.modules = sorted([(p.project_name, p.version)
70 c.modules = sorted([(p.project_name, p.version)
71 for p in pkg_resources.working_set]
71 for p in pkg_resources.working_set]
72 + [('git', check_git_version())],
72 + [('git', check_git_version())],
73 key=lambda k: k[0].lower())
73 key=lambda k: k[0].lower())
74 c.py_version = platform.python_version()
74 c.py_version = platform.python_version()
75 c.platform = platform.platform()
75 c.platform = platform.platform()
76 super(SettingsController, self).__before__()
76 super(SettingsController, self).__before__()
77
77
78 @HasPermissionAllDecorator('hg.admin')
78 @HasPermissionAllDecorator('hg.admin')
79 def index(self, format='html'):
79 def index(self, format='html'):
80 """GET /admin/settings: All items in the collection"""
80 """GET /admin/settings: All items in the collection"""
81 # url('admin_settings')
81 # url('admin_settings')
82
82
83 defaults = RhodeCodeSetting.get_app_settings()
83 defaults = RhodeCodeSetting.get_app_settings()
84 defaults.update(self._get_hg_ui_settings())
84 defaults.update(self._get_hg_ui_settings())
85
85
86 return htmlfill.render(
86 return htmlfill.render(
87 render('admin/settings/settings.html'),
87 render('admin/settings/settings.html'),
88 defaults=defaults,
88 defaults=defaults,
89 encoding="UTF-8",
89 encoding="UTF-8",
90 force_defaults=False
90 force_defaults=False
91 )
91 )
92
92
93 @HasPermissionAllDecorator('hg.admin')
93 @HasPermissionAllDecorator('hg.admin')
94 def create(self):
94 def create(self):
95 """POST /admin/settings: Create a new item"""
95 """POST /admin/settings: Create a new item"""
96 # url('admin_settings')
96 # url('admin_settings')
97
97
98 @HasPermissionAllDecorator('hg.admin')
98 @HasPermissionAllDecorator('hg.admin')
99 def new(self, format='html'):
99 def new(self, format='html'):
100 """GET /admin/settings/new: Form to create a new item"""
100 """GET /admin/settings/new: Form to create a new item"""
101 # url('admin_new_setting')
101 # url('admin_new_setting')
102
102
103 @HasPermissionAllDecorator('hg.admin')
103 @HasPermissionAllDecorator('hg.admin')
104 def update(self, setting_id):
104 def update(self, setting_id):
105 """PUT /admin/settings/setting_id: Update an existing item"""
105 """PUT /admin/settings/setting_id: Update an existing item"""
106 # Forms posted to this method should contain a hidden field:
106 # Forms posted to this method should contain a hidden field:
107 # <input type="hidden" name="_method" value="PUT" />
107 # <input type="hidden" name="_method" value="PUT" />
108 # Or using helpers:
108 # Or using helpers:
109 # h.form(url('admin_setting', setting_id=ID),
109 # h.form(url('admin_setting', setting_id=ID),
110 # method='put')
110 # method='put')
111 # url('admin_setting', setting_id=ID)
111 # url('admin_setting', setting_id=ID)
112
112
113 if setting_id == 'mapping':
113 if setting_id == 'mapping':
114 rm_obsolete = request.POST.get('destroy', False)
114 rm_obsolete = request.POST.get('destroy', False)
115 log.debug('Rescanning directories with destroy=%s' % rm_obsolete)
115 log.debug('Rescanning directories with destroy=%s' % rm_obsolete)
116 initial = ScmModel().repo_scan()
116 initial = ScmModel().repo_scan()
117 log.debug('invalidating all repositories')
117 log.debug('invalidating all repositories')
118 for repo_name in initial.keys():
118 for repo_name in initial.keys():
119 invalidate_cache('get_repo_cached_%s' % repo_name)
119 invalidate_cache('get_repo_cached_%s' % repo_name)
120
120
121 added, removed = repo2db_mapper(initial, rm_obsolete)
121 added, removed = repo2db_mapper(initial, rm_obsolete)
122
122
123 h.flash(_('Repositories successfully'
123 h.flash(_('Repositories successfully'
124 ' rescanned added: %s,removed: %s') % (added, removed),
124 ' rescanned added: %s,removed: %s') % (added, removed),
125 category='success')
125 category='success')
126
126
127 if setting_id == 'whoosh':
127 if setting_id == 'whoosh':
128 repo_location = self._get_hg_ui_settings()['paths_root_path']
128 repo_location = self._get_hg_ui_settings()['paths_root_path']
129 full_index = request.POST.get('full_index', False)
129 full_index = request.POST.get('full_index', False)
130 run_task(tasks.whoosh_index, repo_location, full_index)
130 run_task(tasks.whoosh_index, repo_location, full_index)
131 h.flash(_('Whoosh reindex task scheduled'), category='success')
131 h.flash(_('Whoosh reindex task scheduled'), category='success')
132
132
133 if setting_id == 'global':
133 if setting_id == 'global':
134
134
135 application_form = ApplicationSettingsForm()()
135 application_form = ApplicationSettingsForm()()
136 try:
136 try:
137 form_result = application_form.to_python(dict(request.POST))
137 form_result = application_form.to_python(dict(request.POST))
138 except formencode.Invalid, errors:
138 except formencode.Invalid, errors:
139 return htmlfill.render(
139 return htmlfill.render(
140 render('admin/settings/settings.html'),
140 render('admin/settings/settings.html'),
141 defaults=errors.value,
141 defaults=errors.value,
142 errors=errors.error_dict or {},
142 errors=errors.error_dict or {},
143 prefix_error=False,
143 prefix_error=False,
144 encoding="UTF-8"
144 encoding="UTF-8"
145 )
145 )
146
146
147 try:
147 try:
148 sett1 = RhodeCodeSetting.get_by_name_or_create('title')
148 sett1 = RhodeCodeSetting.get_by_name_or_create('title')
149 sett1.app_settings_value = form_result['rhodecode_title']
149 sett1.app_settings_value = form_result['rhodecode_title']
150 Session().add(sett1)
150 Session().add(sett1)
151
151
152 sett2 = RhodeCodeSetting.get_by_name_or_create('realm')
152 sett2 = RhodeCodeSetting.get_by_name_or_create('realm')
153 sett2.app_settings_value = form_result['rhodecode_realm']
153 sett2.app_settings_value = form_result['rhodecode_realm']
154 Session().add(sett2)
154 Session().add(sett2)
155
155
156 sett3 = RhodeCodeSetting.get_by_name_or_create('ga_code')
156 sett3 = RhodeCodeSetting.get_by_name_or_create('ga_code')
157 sett3.app_settings_value = form_result['rhodecode_ga_code']
157 sett3.app_settings_value = form_result['rhodecode_ga_code']
158 Session().add(sett3)
158 Session().add(sett3)
159
159
160 Session().commit()
160 Session().commit()
161 set_rhodecode_config(config)
161 set_rhodecode_config(config)
162 h.flash(_('Updated application settings'), category='success')
162 h.flash(_('Updated application settings'), category='success')
163
163
164 except Exception:
164 except Exception:
165 log.error(traceback.format_exc())
165 log.error(traceback.format_exc())
166 h.flash(_('error occurred during updating '
166 h.flash(_('error occurred during updating '
167 'application settings'),
167 'application settings'),
168 category='error')
168 category='error')
169
169
170 if setting_id == 'visual':
170 if setting_id == 'visual':
171
171
172 application_form = ApplicationVisualisationForm()()
172 application_form = ApplicationVisualisationForm()()
173 try:
173 try:
174 form_result = application_form.to_python(dict(request.POST))
174 form_result = application_form.to_python(dict(request.POST))
175 except formencode.Invalid, errors:
175 except formencode.Invalid, errors:
176 return htmlfill.render(
176 return htmlfill.render(
177 render('admin/settings/settings.html'),
177 render('admin/settings/settings.html'),
178 defaults=errors.value,
178 defaults=errors.value,
179 errors=errors.error_dict or {},
179 errors=errors.error_dict or {},
180 prefix_error=False,
180 prefix_error=False,
181 encoding="UTF-8"
181 encoding="UTF-8"
182 )
182 )
183
183
184 try:
184 try:
185 sett1 = RhodeCodeSetting.get_by_name_or_create('show_public_icon')
185 sett1 = RhodeCodeSetting.get_by_name_or_create('show_public_icon')
186 sett1.app_settings_value = \
186 sett1.app_settings_value = \
187 form_result['rhodecode_show_public_icon']
187 form_result['rhodecode_show_public_icon']
188 Session().add(sett1)
188 Session().add(sett1)
189
189
190 sett2 = RhodeCodeSetting.get_by_name_or_create('show_private_icon')
190 sett2 = RhodeCodeSetting.get_by_name_or_create('show_private_icon')
191 sett2.app_settings_value = \
191 sett2.app_settings_value = \
192 form_result['rhodecode_show_private_icon']
192 form_result['rhodecode_show_private_icon']
193 Session().add(sett2)
193 Session().add(sett2)
194
194
195 sett3 = RhodeCodeSetting.get_by_name_or_create('stylify_metatags')
195 sett3 = RhodeCodeSetting.get_by_name_or_create('stylify_metatags')
196 sett3.app_settings_value = \
196 sett3.app_settings_value = \
197 form_result['rhodecode_stylify_metatags']
197 form_result['rhodecode_stylify_metatags']
198 Session().add(sett3)
198 Session().add(sett3)
199
199
200 sett4 = RhodeCodeSetting.get_by_name_or_create('lightweight_dashboard')
200 sett4 = RhodeCodeSetting.get_by_name_or_create('lightweight_dashboard')
201 sett4.app_settings_value = \
201 sett4.app_settings_value = \
202 form_result['rhodecode_lightweight_dashboard']
202 form_result['rhodecode_lightweight_dashboard']
203 Session().add(sett4)
203 Session().add(sett4)
204
204
205 sett5 = RhodeCodeSetting.get_by_name_or_create('lightweight_journal')
206 sett5.app_settings_value = \
207 form_result['rhodecode_lightweight_journal']
208 Session().add(sett5)
209
205 Session().commit()
210 Session().commit()
206 set_rhodecode_config(config)
211 set_rhodecode_config(config)
207 h.flash(_('Updated visualisation settings'),
212 h.flash(_('Updated visualisation settings'),
208 category='success')
213 category='success')
209
214
210 except Exception:
215 except Exception:
211 log.error(traceback.format_exc())
216 log.error(traceback.format_exc())
212 h.flash(_('error occurred during updating '
217 h.flash(_('error occurred during updating '
213 'visualisation settings'),
218 'visualisation settings'),
214 category='error')
219 category='error')
215
220
216 if setting_id == 'vcs':
221 if setting_id == 'vcs':
217 application_form = ApplicationUiSettingsForm()()
222 application_form = ApplicationUiSettingsForm()()
218 try:
223 try:
219 form_result = application_form.to_python(dict(request.POST))
224 form_result = application_form.to_python(dict(request.POST))
220 except formencode.Invalid, errors:
225 except formencode.Invalid, errors:
221 return htmlfill.render(
226 return htmlfill.render(
222 render('admin/settings/settings.html'),
227 render('admin/settings/settings.html'),
223 defaults=errors.value,
228 defaults=errors.value,
224 errors=errors.error_dict or {},
229 errors=errors.error_dict or {},
225 prefix_error=False,
230 prefix_error=False,
226 encoding="UTF-8"
231 encoding="UTF-8"
227 )
232 )
228
233
229 try:
234 try:
230 # fix namespaces for hooks and extensions
235 # fix namespaces for hooks and extensions
231 _f = lambda s: s.replace('.', '_')
236 _f = lambda s: s.replace('.', '_')
232
237
233 sett = RhodeCodeUi.get_by_key('push_ssl')
238 sett = RhodeCodeUi.get_by_key('push_ssl')
234 sett.ui_value = form_result['web_push_ssl']
239 sett.ui_value = form_result['web_push_ssl']
235 Session().add(sett)
240 Session().add(sett)
236
241
237 sett = RhodeCodeUi.get_by_key('/')
242 sett = RhodeCodeUi.get_by_key('/')
238 sett.ui_value = form_result['paths_root_path']
243 sett.ui_value = form_result['paths_root_path']
239 Session().add(sett)
244 Session().add(sett)
240
245
241 #HOOKS
246 #HOOKS
242 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_UPDATE)
247 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_UPDATE)
243 sett.ui_active = form_result[_f('hooks_%s' %
248 sett.ui_active = form_result[_f('hooks_%s' %
244 RhodeCodeUi.HOOK_UPDATE)]
249 RhodeCodeUi.HOOK_UPDATE)]
245 Session().add(sett)
250 Session().add(sett)
246
251
247 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_REPO_SIZE)
252 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_REPO_SIZE)
248 sett.ui_active = form_result[_f('hooks_%s' %
253 sett.ui_active = form_result[_f('hooks_%s' %
249 RhodeCodeUi.HOOK_REPO_SIZE)]
254 RhodeCodeUi.HOOK_REPO_SIZE)]
250 Session().add(sett)
255 Session().add(sett)
251
256
252 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PUSH)
257 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PUSH)
253 sett.ui_active = form_result[_f('hooks_%s' %
258 sett.ui_active = form_result[_f('hooks_%s' %
254 RhodeCodeUi.HOOK_PUSH)]
259 RhodeCodeUi.HOOK_PUSH)]
255 Session().add(sett)
260 Session().add(sett)
256
261
257 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PULL)
262 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PULL)
258 sett.ui_active = form_result[_f('hooks_%s' %
263 sett.ui_active = form_result[_f('hooks_%s' %
259 RhodeCodeUi.HOOK_PULL)]
264 RhodeCodeUi.HOOK_PULL)]
260
265
261 Session().add(sett)
266 Session().add(sett)
262
267
263 ## EXTENSIONS
268 ## EXTENSIONS
264 sett = RhodeCodeUi.get_by_key('largefiles')
269 sett = RhodeCodeUi.get_by_key('largefiles')
265 if not sett:
270 if not sett:
266 #make one if it's not there !
271 #make one if it's not there !
267 sett = RhodeCodeUi()
272 sett = RhodeCodeUi()
268 sett.ui_key = 'largefiles'
273 sett.ui_key = 'largefiles'
269 sett.ui_section = 'extensions'
274 sett.ui_section = 'extensions'
270 sett.ui_active = form_result[_f('extensions_largefiles')]
275 sett.ui_active = form_result[_f('extensions_largefiles')]
271 Session().add(sett)
276 Session().add(sett)
272
277
273 sett = RhodeCodeUi.get_by_key('hgsubversion')
278 sett = RhodeCodeUi.get_by_key('hgsubversion')
274 if not sett:
279 if not sett:
275 #make one if it's not there !
280 #make one if it's not there !
276 sett = RhodeCodeUi()
281 sett = RhodeCodeUi()
277 sett.ui_key = 'hgsubversion'
282 sett.ui_key = 'hgsubversion'
278 sett.ui_section = 'extensions'
283 sett.ui_section = 'extensions'
279
284
280 sett.ui_active = form_result[_f('extensions_hgsubversion')]
285 sett.ui_active = form_result[_f('extensions_hgsubversion')]
281 Session().add(sett)
286 Session().add(sett)
282
287
283 # sett = RhodeCodeUi.get_by_key('hggit')
288 # sett = RhodeCodeUi.get_by_key('hggit')
284 # if not sett:
289 # if not sett:
285 # #make one if it's not there !
290 # #make one if it's not there !
286 # sett = RhodeCodeUi()
291 # sett = RhodeCodeUi()
287 # sett.ui_key = 'hggit'
292 # sett.ui_key = 'hggit'
288 # sett.ui_section = 'extensions'
293 # sett.ui_section = 'extensions'
289 #
294 #
290 # sett.ui_active = form_result[_f('extensions_hggit')]
295 # sett.ui_active = form_result[_f('extensions_hggit')]
291 # Session().add(sett)
296 # Session().add(sett)
292
297
293 Session().commit()
298 Session().commit()
294
299
295 h.flash(_('Updated VCS settings'), category='success')
300 h.flash(_('Updated VCS settings'), category='success')
296
301
297 except Exception:
302 except Exception:
298 log.error(traceback.format_exc())
303 log.error(traceback.format_exc())
299 h.flash(_('error occurred during updating '
304 h.flash(_('error occurred during updating '
300 'application settings'), category='error')
305 'application settings'), category='error')
301
306
302 if setting_id == 'hooks':
307 if setting_id == 'hooks':
303 ui_key = request.POST.get('new_hook_ui_key')
308 ui_key = request.POST.get('new_hook_ui_key')
304 ui_value = request.POST.get('new_hook_ui_value')
309 ui_value = request.POST.get('new_hook_ui_value')
305 try:
310 try:
306
311
307 if ui_value and ui_key:
312 if ui_value and ui_key:
308 RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
313 RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
309 h.flash(_('Added new hook'),
314 h.flash(_('Added new hook'),
310 category='success')
315 category='success')
311
316
312 # check for edits
317 # check for edits
313 update = False
318 update = False
314 _d = request.POST.dict_of_lists()
319 _d = request.POST.dict_of_lists()
315 for k, v in zip(_d.get('hook_ui_key', []),
320 for k, v in zip(_d.get('hook_ui_key', []),
316 _d.get('hook_ui_value_new', [])):
321 _d.get('hook_ui_value_new', [])):
317 RhodeCodeUi.create_or_update_hook(k, v)
322 RhodeCodeUi.create_or_update_hook(k, v)
318 update = True
323 update = True
319
324
320 if update:
325 if update:
321 h.flash(_('Updated hooks'), category='success')
326 h.flash(_('Updated hooks'), category='success')
322 Session().commit()
327 Session().commit()
323 except Exception:
328 except Exception:
324 log.error(traceback.format_exc())
329 log.error(traceback.format_exc())
325 h.flash(_('error occurred during hook creation'),
330 h.flash(_('error occurred during hook creation'),
326 category='error')
331 category='error')
327
332
328 return redirect(url('admin_edit_setting', setting_id='hooks'))
333 return redirect(url('admin_edit_setting', setting_id='hooks'))
329
334
330 if setting_id == 'email':
335 if setting_id == 'email':
331 test_email = request.POST.get('test_email')
336 test_email = request.POST.get('test_email')
332 test_email_subj = 'RhodeCode TestEmail'
337 test_email_subj = 'RhodeCode TestEmail'
333 test_email_body = 'RhodeCode Email test'
338 test_email_body = 'RhodeCode Email test'
334
339
335 test_email_html_body = EmailNotificationModel()\
340 test_email_html_body = EmailNotificationModel()\
336 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
341 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
337 body=test_email_body)
342 body=test_email_body)
338
343
339 recipients = [test_email] if [test_email] else None
344 recipients = [test_email] if [test_email] else None
340
345
341 run_task(tasks.send_email, recipients, test_email_subj,
346 run_task(tasks.send_email, recipients, test_email_subj,
342 test_email_body, test_email_html_body)
347 test_email_body, test_email_html_body)
343
348
344 h.flash(_('Email task created'), category='success')
349 h.flash(_('Email task created'), category='success')
345 return redirect(url('admin_settings'))
350 return redirect(url('admin_settings'))
346
351
347 @HasPermissionAllDecorator('hg.admin')
352 @HasPermissionAllDecorator('hg.admin')
348 def delete(self, setting_id):
353 def delete(self, setting_id):
349 """DELETE /admin/settings/setting_id: Delete an existing item"""
354 """DELETE /admin/settings/setting_id: Delete an existing item"""
350 # Forms posted to this method should contain a hidden field:
355 # Forms posted to this method should contain a hidden field:
351 # <input type="hidden" name="_method" value="DELETE" />
356 # <input type="hidden" name="_method" value="DELETE" />
352 # Or using helpers:
357 # Or using helpers:
353 # h.form(url('admin_setting', setting_id=ID),
358 # h.form(url('admin_setting', setting_id=ID),
354 # method='delete')
359 # method='delete')
355 # url('admin_setting', setting_id=ID)
360 # url('admin_setting', setting_id=ID)
356 if setting_id == 'hooks':
361 if setting_id == 'hooks':
357 hook_id = request.POST.get('hook_id')
362 hook_id = request.POST.get('hook_id')
358 RhodeCodeUi.delete(hook_id)
363 RhodeCodeUi.delete(hook_id)
359 Session().commit()
364 Session().commit()
360
365
361 @HasPermissionAllDecorator('hg.admin')
366 @HasPermissionAllDecorator('hg.admin')
362 def show(self, setting_id, format='html'):
367 def show(self, setting_id, format='html'):
363 """
368 """
364 GET /admin/settings/setting_id: Show a specific item"""
369 GET /admin/settings/setting_id: Show a specific item"""
365 # url('admin_setting', setting_id=ID)
370 # url('admin_setting', setting_id=ID)
366
371
367 @HasPermissionAllDecorator('hg.admin')
372 @HasPermissionAllDecorator('hg.admin')
368 def edit(self, setting_id, format='html'):
373 def edit(self, setting_id, format='html'):
369 """
374 """
370 GET /admin/settings/setting_id/edit: Form to
375 GET /admin/settings/setting_id/edit: Form to
371 edit an existing item"""
376 edit an existing item"""
372 # url('admin_edit_setting', setting_id=ID)
377 # url('admin_edit_setting', setting_id=ID)
373
378
374 c.hooks = RhodeCodeUi.get_builtin_hooks()
379 c.hooks = RhodeCodeUi.get_builtin_hooks()
375 c.custom_hooks = RhodeCodeUi.get_custom_hooks()
380 c.custom_hooks = RhodeCodeUi.get_custom_hooks()
376
381
377 return htmlfill.render(
382 return htmlfill.render(
378 render('admin/settings/hooks.html'),
383 render('admin/settings/hooks.html'),
379 defaults={},
384 defaults={},
380 encoding="UTF-8",
385 encoding="UTF-8",
381 force_defaults=False
386 force_defaults=False
382 )
387 )
383
388
384 @NotAnonymous()
389 @NotAnonymous()
385 def my_account(self):
390 def my_account(self):
386 """
391 """
387 GET /_admin/my_account Displays info about my account
392 GET /_admin/my_account Displays info about my account
388 """
393 """
389 # url('admin_settings_my_account')
394 # url('admin_settings_my_account')
390
395
391 c.user = User.get(self.rhodecode_user.user_id)
396 c.user = User.get(self.rhodecode_user.user_id)
392 all_repos = Session().query(Repository)\
397 all_repos = Session().query(Repository)\
393 .filter(Repository.user_id == c.user.user_id)\
398 .filter(Repository.user_id == c.user.user_id)\
394 .order_by(func.lower(Repository.repo_name)).all()
399 .order_by(func.lower(Repository.repo_name)).all()
395
400
396 c.user_repos = ScmModel().get_repos(all_repos)
401 c.user_repos = ScmModel().get_repos(all_repos)
397
402
398 if c.user.username == 'default':
403 if c.user.username == 'default':
399 h.flash(_("You can't edit this user since it's"
404 h.flash(_("You can't edit this user since it's"
400 " crucial for entire application"), category='warning')
405 " crucial for entire application"), category='warning')
401 return redirect(url('users'))
406 return redirect(url('users'))
402
407
403 defaults = c.user.get_dict()
408 defaults = c.user.get_dict()
404
409
405 c.form = htmlfill.render(
410 c.form = htmlfill.render(
406 render('admin/users/user_edit_my_account_form.html'),
411 render('admin/users/user_edit_my_account_form.html'),
407 defaults=defaults,
412 defaults=defaults,
408 encoding="UTF-8",
413 encoding="UTF-8",
409 force_defaults=False
414 force_defaults=False
410 )
415 )
411 return render('admin/users/user_edit_my_account.html')
416 return render('admin/users/user_edit_my_account.html')
412
417
413 @NotAnonymous()
418 @NotAnonymous()
414 def my_account_update(self):
419 def my_account_update(self):
415 """PUT /_admin/my_account_update: Update an existing item"""
420 """PUT /_admin/my_account_update: Update an existing item"""
416 # Forms posted to this method should contain a hidden field:
421 # Forms posted to this method should contain a hidden field:
417 # <input type="hidden" name="_method" value="PUT" />
422 # <input type="hidden" name="_method" value="PUT" />
418 # Or using helpers:
423 # Or using helpers:
419 # h.form(url('admin_settings_my_account_update'),
424 # h.form(url('admin_settings_my_account_update'),
420 # method='put')
425 # method='put')
421 # url('admin_settings_my_account_update', id=ID)
426 # url('admin_settings_my_account_update', id=ID)
422 uid = self.rhodecode_user.user_id
427 uid = self.rhodecode_user.user_id
423 email = self.rhodecode_user.email
428 email = self.rhodecode_user.email
424 _form = UserForm(edit=True,
429 _form = UserForm(edit=True,
425 old_data={'user_id': uid, 'email': email})()
430 old_data={'user_id': uid, 'email': email})()
426 form_result = {}
431 form_result = {}
427 try:
432 try:
428 form_result = _form.to_python(dict(request.POST))
433 form_result = _form.to_python(dict(request.POST))
429 UserModel().update_my_account(uid, form_result)
434 UserModel().update_my_account(uid, form_result)
430 h.flash(_('Your account was updated successfully'),
435 h.flash(_('Your account was updated successfully'),
431 category='success')
436 category='success')
432 Session().commit()
437 Session().commit()
433 except formencode.Invalid, errors:
438 except formencode.Invalid, errors:
434 c.user = User.get(self.rhodecode_user.user_id)
439 c.user = User.get(self.rhodecode_user.user_id)
435
440
436 c.form = htmlfill.render(
441 c.form = htmlfill.render(
437 render('admin/users/user_edit_my_account_form.html'),
442 render('admin/users/user_edit_my_account_form.html'),
438 defaults=errors.value,
443 defaults=errors.value,
439 errors=errors.error_dict or {},
444 errors=errors.error_dict or {},
440 prefix_error=False,
445 prefix_error=False,
441 encoding="UTF-8")
446 encoding="UTF-8")
442 return render('admin/users/user_edit_my_account.html')
447 return render('admin/users/user_edit_my_account.html')
443 except Exception:
448 except Exception:
444 log.error(traceback.format_exc())
449 log.error(traceback.format_exc())
445 h.flash(_('error occurred during update of user %s') \
450 h.flash(_('error occurred during update of user %s') \
446 % form_result.get('username'), category='error')
451 % form_result.get('username'), category='error')
447
452
448 return redirect(url('my_account'))
453 return redirect(url('my_account'))
449
454
450 @NotAnonymous()
455 @NotAnonymous()
451 def my_account_my_repos(self):
456 def my_account_my_repos(self):
452 all_repos = Session().query(Repository)\
457 all_repos = Session().query(Repository)\
453 .filter(Repository.user_id == self.rhodecode_user.user_id)\
458 .filter(Repository.user_id == self.rhodecode_user.user_id)\
454 .order_by(func.lower(Repository.repo_name))\
459 .order_by(func.lower(Repository.repo_name))\
455 .all()
460 .all()
456 c.user_repos = ScmModel().get_repos(all_repos)
461 c.user_repos = ScmModel().get_repos(all_repos)
457 return render('admin/users/user_edit_my_account_repos.html')
462 return render('admin/users/user_edit_my_account_repos.html')
458
463
459 @NotAnonymous()
464 @NotAnonymous()
460 def my_account_my_pullrequests(self):
465 def my_account_my_pullrequests(self):
461 c.my_pull_requests = PullRequest.query()\
466 c.my_pull_requests = PullRequest.query()\
462 .filter(PullRequest.user_id==
467 .filter(PullRequest.user_id==
463 self.rhodecode_user.user_id)\
468 self.rhodecode_user.user_id)\
464 .all()
469 .all()
465 c.participate_in_pull_requests = \
470 c.participate_in_pull_requests = \
466 [x.pull_request for x in PullRequestReviewers.query()\
471 [x.pull_request for x in PullRequestReviewers.query()\
467 .filter(PullRequestReviewers.user_id==
472 .filter(PullRequestReviewers.user_id==
468 self.rhodecode_user.user_id)\
473 self.rhodecode_user.user_id)\
469 .all()]
474 .all()]
470 return render('admin/users/user_edit_my_account_pullrequests.html')
475 return render('admin/users/user_edit_my_account_pullrequests.html')
471
476
472 @NotAnonymous()
477 @NotAnonymous()
473 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
478 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
474 def create_repository(self):
479 def create_repository(self):
475 """GET /_admin/create_repository: Form to create a new item"""
480 """GET /_admin/create_repository: Form to create a new item"""
476
481
477 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
482 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
478 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
483 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
479 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
484 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
480
485
481 new_repo = request.GET.get('repo', '')
486 new_repo = request.GET.get('repo', '')
482 c.new_repo = repo_name_slug(new_repo)
487 c.new_repo = repo_name_slug(new_repo)
483
488
484 return render('admin/repos/repo_add_create_repository.html')
489 return render('admin/repos/repo_add_create_repository.html')
485
490
486 def _get_hg_ui_settings(self):
491 def _get_hg_ui_settings(self):
487 ret = RhodeCodeUi.query().all()
492 ret = RhodeCodeUi.query().all()
488
493
489 if not ret:
494 if not ret:
490 raise Exception('Could not get application ui settings !')
495 raise Exception('Could not get application ui settings !')
491 settings = {}
496 settings = {}
492 for each in ret:
497 for each in ret:
493 k = each.ui_key
498 k = each.ui_key
494 v = each.ui_value
499 v = each.ui_value
495 if k == '/':
500 if k == '/':
496 k = 'root_path'
501 k = 'root_path'
497
502
498 if k == 'push_ssl':
503 if k == 'push_ssl':
499 v = str2bool(v)
504 v = str2bool(v)
500
505
501 if k.find('.') != -1:
506 if k.find('.') != -1:
502 k = k.replace('.', '_')
507 k = k.replace('.', '_')
503
508
504 if each.ui_section in ['hooks', 'extensions']:
509 if each.ui_section in ['hooks', 'extensions']:
505 v = each.ui_active
510 v = each.ui_active
506
511
507 settings[each.ui_section + '_' + k] = v
512 settings[each.ui_section + '_' + k] = v
508 return settings
513 return settings
@@ -1,321 +1,322
1 """The base Controller API
1 """The base Controller API
2
2
3 Provides the BaseController class for subclassing.
3 Provides the BaseController class for subclassing.
4 """
4 """
5 import logging
5 import logging
6 import time
6 import time
7 import traceback
7 import traceback
8
8
9 from paste.auth.basic import AuthBasicAuthenticator
9 from paste.auth.basic import AuthBasicAuthenticator
10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
11 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
11 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
12
12
13 from pylons import config, tmpl_context as c, request, session, url
13 from pylons import config, tmpl_context as c, request, session, url
14 from pylons.controllers import WSGIController
14 from pylons.controllers import WSGIController
15 from pylons.controllers.util import redirect
15 from pylons.controllers.util import redirect
16 from pylons.templating import render_mako as render
16 from pylons.templating import render_mako as render
17
17
18 from rhodecode import __version__, BACKENDS
18 from rhodecode import __version__, BACKENDS
19
19
20 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict,\
20 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict,\
21 safe_str
21 safe_str
22 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
22 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
23 HasPermissionAnyMiddleware, CookieStoreWrapper
23 HasPermissionAnyMiddleware, CookieStoreWrapper
24 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
24 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
25 from rhodecode.model import meta
25 from rhodecode.model import meta
26
26
27 from rhodecode.model.db import Repository, RhodeCodeUi, User
27 from rhodecode.model.db import Repository, RhodeCodeUi, User
28 from rhodecode.model.notification import NotificationModel
28 from rhodecode.model.notification import NotificationModel
29 from rhodecode.model.scm import ScmModel
29 from rhodecode.model.scm import ScmModel
30 from rhodecode.model.meta import Session
30 from rhodecode.model.meta import Session
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 def _get_ip_addr(environ):
35 def _get_ip_addr(environ):
36 proxy_key = 'HTTP_X_REAL_IP'
36 proxy_key = 'HTTP_X_REAL_IP'
37 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
37 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
38 def_key = 'REMOTE_ADDR'
38 def_key = 'REMOTE_ADDR'
39
39
40 ip = environ.get(proxy_key2)
40 ip = environ.get(proxy_key2)
41 if ip:
41 if ip:
42 return ip
42 return ip
43
43
44 ip = environ.get(proxy_key)
44 ip = environ.get(proxy_key)
45
45
46 if ip:
46 if ip:
47 return ip
47 return ip
48
48
49 ip = environ.get(def_key, '0.0.0.0')
49 ip = environ.get(def_key, '0.0.0.0')
50 return ip
50 return ip
51
51
52
52
53 def _get_access_path(environ):
53 def _get_access_path(environ):
54 path = environ.get('PATH_INFO')
54 path = environ.get('PATH_INFO')
55 org_req = environ.get('pylons.original_request')
55 org_req = environ.get('pylons.original_request')
56 if org_req:
56 if org_req:
57 path = org_req.environ.get('PATH_INFO')
57 path = org_req.environ.get('PATH_INFO')
58 return path
58 return path
59
59
60
60
61 class BasicAuth(AuthBasicAuthenticator):
61 class BasicAuth(AuthBasicAuthenticator):
62
62
63 def __init__(self, realm, authfunc, auth_http_code=None):
63 def __init__(self, realm, authfunc, auth_http_code=None):
64 self.realm = realm
64 self.realm = realm
65 self.authfunc = authfunc
65 self.authfunc = authfunc
66 self._rc_auth_http_code = auth_http_code
66 self._rc_auth_http_code = auth_http_code
67
67
68 def build_authentication(self):
68 def build_authentication(self):
69 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
69 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
70 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
70 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
71 # return 403 if alternative http return code is specified in
71 # return 403 if alternative http return code is specified in
72 # RhodeCode config
72 # RhodeCode config
73 return HTTPForbidden(headers=head)
73 return HTTPForbidden(headers=head)
74 return HTTPUnauthorized(headers=head)
74 return HTTPUnauthorized(headers=head)
75
75
76 def authenticate(self, environ):
76 def authenticate(self, environ):
77 authorization = AUTHORIZATION(environ)
77 authorization = AUTHORIZATION(environ)
78 if not authorization:
78 if not authorization:
79 return self.build_authentication()
79 return self.build_authentication()
80 (authmeth, auth) = authorization.split(' ', 1)
80 (authmeth, auth) = authorization.split(' ', 1)
81 if 'basic' != authmeth.lower():
81 if 'basic' != authmeth.lower():
82 return self.build_authentication()
82 return self.build_authentication()
83 auth = auth.strip().decode('base64')
83 auth = auth.strip().decode('base64')
84 _parts = auth.split(':', 1)
84 _parts = auth.split(':', 1)
85 if len(_parts) == 2:
85 if len(_parts) == 2:
86 username, password = _parts
86 username, password = _parts
87 if self.authfunc(environ, username, password):
87 if self.authfunc(environ, username, password):
88 return username
88 return username
89 return self.build_authentication()
89 return self.build_authentication()
90
90
91 __call__ = authenticate
91 __call__ = authenticate
92
92
93
93
94 class BaseVCSController(object):
94 class BaseVCSController(object):
95
95
96 def __init__(self, application, config):
96 def __init__(self, application, config):
97 self.application = application
97 self.application = application
98 self.config = config
98 self.config = config
99 # base path of repo locations
99 # base path of repo locations
100 self.basepath = self.config['base_path']
100 self.basepath = self.config['base_path']
101 #authenticate this mercurial request using authfunc
101 #authenticate this mercurial request using authfunc
102 self.authenticate = BasicAuth('', authfunc,
102 self.authenticate = BasicAuth('', authfunc,
103 config.get('auth_ret_code'))
103 config.get('auth_ret_code'))
104 self.ipaddr = '0.0.0.0'
104 self.ipaddr = '0.0.0.0'
105
105
106 def _handle_request(self, environ, start_response):
106 def _handle_request(self, environ, start_response):
107 raise NotImplementedError()
107 raise NotImplementedError()
108
108
109 def _get_by_id(self, repo_name):
109 def _get_by_id(self, repo_name):
110 """
110 """
111 Get's a special pattern _<ID> from clone url and tries to replace it
111 Get's a special pattern _<ID> from clone url and tries to replace it
112 with a repository_name for support of _<ID> non changable urls
112 with a repository_name for support of _<ID> non changable urls
113
113
114 :param repo_name:
114 :param repo_name:
115 """
115 """
116 try:
116 try:
117 data = repo_name.split('/')
117 data = repo_name.split('/')
118 if len(data) >= 2:
118 if len(data) >= 2:
119 by_id = data[1].split('_')
119 by_id = data[1].split('_')
120 if len(by_id) == 2 and by_id[1].isdigit():
120 if len(by_id) == 2 and by_id[1].isdigit():
121 _repo_name = Repository.get(by_id[1]).repo_name
121 _repo_name = Repository.get(by_id[1]).repo_name
122 data[1] = _repo_name
122 data[1] = _repo_name
123 except:
123 except:
124 log.debug('Failed to extract repo_name from id %s' % (
124 log.debug('Failed to extract repo_name from id %s' % (
125 traceback.format_exc()
125 traceback.format_exc()
126 )
126 )
127 )
127 )
128
128
129 return '/'.join(data)
129 return '/'.join(data)
130
130
131 def _invalidate_cache(self, repo_name):
131 def _invalidate_cache(self, repo_name):
132 """
132 """
133 Set's cache for this repository for invalidation on next access
133 Set's cache for this repository for invalidation on next access
134
134
135 :param repo_name: full repo name, also a cache key
135 :param repo_name: full repo name, also a cache key
136 """
136 """
137 invalidate_cache('get_repo_cached_%s' % repo_name)
137 invalidate_cache('get_repo_cached_%s' % repo_name)
138
138
139 def _check_permission(self, action, user, repo_name):
139 def _check_permission(self, action, user, repo_name):
140 """
140 """
141 Checks permissions using action (push/pull) user and repository
141 Checks permissions using action (push/pull) user and repository
142 name
142 name
143
143
144 :param action: push or pull action
144 :param action: push or pull action
145 :param user: user instance
145 :param user: user instance
146 :param repo_name: repository name
146 :param repo_name: repository name
147 """
147 """
148 if action == 'push':
148 if action == 'push':
149 if not HasPermissionAnyMiddleware('repository.write',
149 if not HasPermissionAnyMiddleware('repository.write',
150 'repository.admin')(user,
150 'repository.admin')(user,
151 repo_name):
151 repo_name):
152 return False
152 return False
153
153
154 else:
154 else:
155 #any other action need at least read permission
155 #any other action need at least read permission
156 if not HasPermissionAnyMiddleware('repository.read',
156 if not HasPermissionAnyMiddleware('repository.read',
157 'repository.write',
157 'repository.write',
158 'repository.admin')(user,
158 'repository.admin')(user,
159 repo_name):
159 repo_name):
160 return False
160 return False
161
161
162 return True
162 return True
163
163
164 def _get_ip_addr(self, environ):
164 def _get_ip_addr(self, environ):
165 return _get_ip_addr(environ)
165 return _get_ip_addr(environ)
166
166
167 def _check_ssl(self, environ, start_response):
167 def _check_ssl(self, environ, start_response):
168 """
168 """
169 Checks the SSL check flag and returns False if SSL is not present
169 Checks the SSL check flag and returns False if SSL is not present
170 and required True otherwise
170 and required True otherwise
171 """
171 """
172 org_proto = environ['wsgi._org_proto']
172 org_proto = environ['wsgi._org_proto']
173 #check if we have SSL required ! if not it's a bad request !
173 #check if we have SSL required ! if not it's a bad request !
174 require_ssl = str2bool(RhodeCodeUi.get_by_key('push_ssl').ui_value)
174 require_ssl = str2bool(RhodeCodeUi.get_by_key('push_ssl').ui_value)
175 if require_ssl and org_proto == 'http':
175 if require_ssl and org_proto == 'http':
176 log.debug('proto is %s and SSL is required BAD REQUEST !'
176 log.debug('proto is %s and SSL is required BAD REQUEST !'
177 % org_proto)
177 % org_proto)
178 return False
178 return False
179 return True
179 return True
180
180
181 def _check_locking_state(self, environ, action, repo, user_id):
181 def _check_locking_state(self, environ, action, repo, user_id):
182 """
182 """
183 Checks locking on this repository, if locking is enabled and lock is
183 Checks locking on this repository, if locking is enabled and lock is
184 present returns a tuple of make_lock, locked, locked_by.
184 present returns a tuple of make_lock, locked, locked_by.
185 make_lock can have 3 states None (do nothing) True, make lock
185 make_lock can have 3 states None (do nothing) True, make lock
186 False release lock, This value is later propagated to hooks, which
186 False release lock, This value is later propagated to hooks, which
187 do the locking. Think about this as signals passed to hooks what to do.
187 do the locking. Think about this as signals passed to hooks what to do.
188
188
189 """
189 """
190 locked = False # defines that locked error should be thrown to user
190 locked = False # defines that locked error should be thrown to user
191 make_lock = None
191 make_lock = None
192 repo = Repository.get_by_repo_name(repo)
192 repo = Repository.get_by_repo_name(repo)
193 user = User.get(user_id)
193 user = User.get(user_id)
194
194
195 # this is kind of hacky, but due to how mercurial handles client-server
195 # this is kind of hacky, but due to how mercurial handles client-server
196 # server see all operation on changeset; bookmarks, phases and
196 # server see all operation on changeset; bookmarks, phases and
197 # obsolescence marker in different transaction, we don't want to check
197 # obsolescence marker in different transaction, we don't want to check
198 # locking on those
198 # locking on those
199 obsolete_call = environ['QUERY_STRING'] in ['cmd=listkeys',]
199 obsolete_call = environ['QUERY_STRING'] in ['cmd=listkeys',]
200 locked_by = repo.locked
200 locked_by = repo.locked
201 if repo and repo.enable_locking and not obsolete_call:
201 if repo and repo.enable_locking and not obsolete_call:
202 if action == 'push':
202 if action == 'push':
203 #check if it's already locked !, if it is compare users
203 #check if it's already locked !, if it is compare users
204 user_id, _date = repo.locked
204 user_id, _date = repo.locked
205 if user.user_id == user_id:
205 if user.user_id == user_id:
206 log.debug('Got push from user %s, now unlocking' % (user))
206 log.debug('Got push from user %s, now unlocking' % (user))
207 # unlock if we have push from user who locked
207 # unlock if we have push from user who locked
208 make_lock = False
208 make_lock = False
209 else:
209 else:
210 # we're not the same user who locked, ban with 423 !
210 # we're not the same user who locked, ban with 423 !
211 locked = True
211 locked = True
212 if action == 'pull':
212 if action == 'pull':
213 if repo.locked[0] and repo.locked[1]:
213 if repo.locked[0] and repo.locked[1]:
214 locked = True
214 locked = True
215 else:
215 else:
216 log.debug('Setting lock on repo %s by %s' % (repo, user))
216 log.debug('Setting lock on repo %s by %s' % (repo, user))
217 make_lock = True
217 make_lock = True
218
218
219 else:
219 else:
220 log.debug('Repository %s do not have locking enabled' % (repo))
220 log.debug('Repository %s do not have locking enabled' % (repo))
221 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s'
221 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s'
222 % (make_lock, locked, locked_by))
222 % (make_lock, locked, locked_by))
223 return make_lock, locked, locked_by
223 return make_lock, locked, locked_by
224
224
225 def __call__(self, environ, start_response):
225 def __call__(self, environ, start_response):
226 start = time.time()
226 start = time.time()
227 try:
227 try:
228 return self._handle_request(environ, start_response)
228 return self._handle_request(environ, start_response)
229 finally:
229 finally:
230 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
230 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
231 log.debug('Request time: %.3fs' % (time.time() - start))
231 log.debug('Request time: %.3fs' % (time.time() - start))
232 meta.Session.remove()
232 meta.Session.remove()
233
233
234
234
235 class BaseController(WSGIController):
235 class BaseController(WSGIController):
236
236
237 def __before__(self):
237 def __before__(self):
238 c.rhodecode_version = __version__
238 c.rhodecode_version = __version__
239 c.rhodecode_instanceid = config.get('instance_id')
239 c.rhodecode_instanceid = config.get('instance_id')
240 c.rhodecode_name = config.get('rhodecode_title')
240 c.rhodecode_name = config.get('rhodecode_title')
241 c.use_gravatar = str2bool(config.get('use_gravatar'))
241 c.use_gravatar = str2bool(config.get('use_gravatar'))
242 c.ga_code = config.get('rhodecode_ga_code')
242 c.ga_code = config.get('rhodecode_ga_code')
243 # Visual options
243 # Visual options
244 c.visual = AttributeDict({})
244 c.visual = AttributeDict({})
245 c.visual.show_public_icon = str2bool(config.get('rhodecode_show_public_icon'))
245 c.visual.show_public_icon = str2bool(config.get('rhodecode_show_public_icon'))
246 c.visual.show_private_icon = str2bool(config.get('rhodecode_show_private_icon'))
246 c.visual.show_private_icon = str2bool(config.get('rhodecode_show_private_icon'))
247 c.visual.stylify_metatags = str2bool(config.get('rhodecode_stylify_metatags'))
247 c.visual.stylify_metatags = str2bool(config.get('rhodecode_stylify_metatags'))
248 c.visual.lightweight_dashboard = str2bool(config.get('rhodecode_lightweight_dashboard'))
248 c.visual.lightweight_dashboard = str2bool(config.get('rhodecode_lightweight_dashboard'))
249 c.visual.lightweight_journal = str2bool(config.get('rhodecode_lightweight_dashboard'))
249
250
250 c.repo_name = get_repo_slug(request)
251 c.repo_name = get_repo_slug(request)
251 c.backends = BACKENDS.keys()
252 c.backends = BACKENDS.keys()
252 c.unread_notifications = NotificationModel()\
253 c.unread_notifications = NotificationModel()\
253 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
254 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
254 self.cut_off_limit = int(config.get('cut_off_limit'))
255 self.cut_off_limit = int(config.get('cut_off_limit'))
255
256
256 self.sa = meta.Session
257 self.sa = meta.Session
257 self.scm_model = ScmModel(self.sa)
258 self.scm_model = ScmModel(self.sa)
258 self.ip_addr = ''
259 self.ip_addr = ''
259
260
260 def __call__(self, environ, start_response):
261 def __call__(self, environ, start_response):
261 """Invoke the Controller"""
262 """Invoke the Controller"""
262 # WSGIController.__call__ dispatches to the Controller method
263 # WSGIController.__call__ dispatches to the Controller method
263 # the request is routed to. This routing information is
264 # the request is routed to. This routing information is
264 # available in environ['pylons.routes_dict']
265 # available in environ['pylons.routes_dict']
265 start = time.time()
266 start = time.time()
266 try:
267 try:
267 self.ip_addr = _get_ip_addr(environ)
268 self.ip_addr = _get_ip_addr(environ)
268 # make sure that we update permissions each time we call controller
269 # make sure that we update permissions each time we call controller
269 api_key = request.GET.get('api_key')
270 api_key = request.GET.get('api_key')
270 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
271 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
271 user_id = cookie_store.get('user_id', None)
272 user_id = cookie_store.get('user_id', None)
272 username = get_container_username(environ, config)
273 username = get_container_username(environ, config)
273 auth_user = AuthUser(user_id, api_key, username)
274 auth_user = AuthUser(user_id, api_key, username)
274 request.user = auth_user
275 request.user = auth_user
275 self.rhodecode_user = c.rhodecode_user = auth_user
276 self.rhodecode_user = c.rhodecode_user = auth_user
276 if not self.rhodecode_user.is_authenticated and \
277 if not self.rhodecode_user.is_authenticated and \
277 self.rhodecode_user.user_id is not None:
278 self.rhodecode_user.user_id is not None:
278 self.rhodecode_user.set_authenticated(
279 self.rhodecode_user.set_authenticated(
279 cookie_store.get('is_authenticated')
280 cookie_store.get('is_authenticated')
280 )
281 )
281 log.info('IP: %s User: %s accessed %s' % (
282 log.info('IP: %s User: %s accessed %s' % (
282 self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
283 self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
283 )
284 )
284 return WSGIController.__call__(self, environ, start_response)
285 return WSGIController.__call__(self, environ, start_response)
285 finally:
286 finally:
286 log.info('IP: %s Request to %s time: %.3fs' % (
287 log.info('IP: %s Request to %s time: %.3fs' % (
287 _get_ip_addr(environ),
288 _get_ip_addr(environ),
288 safe_unicode(_get_access_path(environ)), time.time() - start)
289 safe_unicode(_get_access_path(environ)), time.time() - start)
289 )
290 )
290 meta.Session.remove()
291 meta.Session.remove()
291
292
292
293
293 class BaseRepoController(BaseController):
294 class BaseRepoController(BaseController):
294 """
295 """
295 Base class for controllers responsible for loading all needed data for
296 Base class for controllers responsible for loading all needed data for
296 repository loaded items are
297 repository loaded items are
297
298
298 c.rhodecode_repo: instance of scm repository
299 c.rhodecode_repo: instance of scm repository
299 c.rhodecode_db_repo: instance of db
300 c.rhodecode_db_repo: instance of db
300 c.repository_followers: number of followers
301 c.repository_followers: number of followers
301 c.repository_forks: number of forks
302 c.repository_forks: number of forks
302 """
303 """
303
304
304 def __before__(self):
305 def __before__(self):
305 super(BaseRepoController, self).__before__()
306 super(BaseRepoController, self).__before__()
306 if c.repo_name:
307 if c.repo_name:
307
308
308 dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
309 dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
309 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
310 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
310 # update last change according to VCS data
311 # update last change according to VCS data
311 dbr.update_last_change(c.rhodecode_repo.last_change)
312 dbr.update_last_change(c.rhodecode_repo.last_change)
312 if c.rhodecode_repo is None:
313 if c.rhodecode_repo is None:
313 log.error('%s this repository is present in database but it '
314 log.error('%s this repository is present in database but it '
314 'cannot be created as an scm instance', c.repo_name)
315 'cannot be created as an scm instance', c.repo_name)
315
316
316 redirect(url('home'))
317 redirect(url('home'))
317
318
318 # some globals counter for menu
319 # some globals counter for menu
319 c.repository_followers = self.scm_model.get_followers(dbr)
320 c.repository_followers = self.scm_model.get_followers(dbr)
320 c.repository_forks = self.scm_model.get_forks(dbr)
321 c.repository_forks = self.scm_model.get_forks(dbr)
321 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
322 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
@@ -1,1109 +1,1110
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10 import math
10 import math
11 import logging
11 import logging
12 import re
12 import re
13 import urlparse
13 import urlparse
14
14
15 from datetime import datetime
15 from datetime import datetime
16 from pygments.formatters.html import HtmlFormatter
16 from pygments.formatters.html import HtmlFormatter
17 from pygments import highlight as code_highlight
17 from pygments import highlight as code_highlight
18 from pylons import url, request, config
18 from pylons import url, request, config
19 from pylons.i18n.translation import _, ungettext
19 from pylons.i18n.translation import _, ungettext
20 from hashlib import md5
20 from hashlib import md5
21
21
22 from webhelpers.html import literal, HTML, escape
22 from webhelpers.html import literal, HTML, escape
23 from webhelpers.html.tools import *
23 from webhelpers.html.tools import *
24 from webhelpers.html.builder import make_tag
24 from webhelpers.html.builder import make_tag
25 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
25 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
26 end_form, file, form, hidden, image, javascript_link, link_to, \
26 end_form, file, form, hidden, image, javascript_link, link_to, \
27 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
27 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
28 submit, text, password, textarea, title, ul, xml_declaration, radio
28 submit, text, password, textarea, title, ul, xml_declaration, radio
29 from webhelpers.html.tools import auto_link, button_to, highlight, \
29 from webhelpers.html.tools import auto_link, button_to, highlight, \
30 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
30 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
31 from webhelpers.number import format_byte_size, format_bit_size
31 from webhelpers.number import format_byte_size, format_bit_size
32 from webhelpers.pylonslib import Flash as _Flash
32 from webhelpers.pylonslib import Flash as _Flash
33 from webhelpers.pylonslib.secure_form import secure_form
33 from webhelpers.pylonslib.secure_form import secure_form
34 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
34 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
35 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
35 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
36 replace_whitespace, urlify, truncate, wrap_paragraphs
36 replace_whitespace, urlify, truncate, wrap_paragraphs
37 from webhelpers.date import time_ago_in_words
37 from webhelpers.date import time_ago_in_words
38 from webhelpers.paginate import Page
38 from webhelpers.paginate import Page
39 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
39 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
40 convert_boolean_attrs, NotGiven, _make_safe_id_component
40 convert_boolean_attrs, NotGiven, _make_safe_id_component
41
41
42 from rhodecode.lib.annotate import annotate_highlight
42 from rhodecode.lib.annotate import annotate_highlight
43 from rhodecode.lib.utils import repo_name_slug
43 from rhodecode.lib.utils import repo_name_slug
44 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
44 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
45 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict
45 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict
46 from rhodecode.lib.markup_renderer import MarkupRenderer
46 from rhodecode.lib.markup_renderer import MarkupRenderer
47 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
47 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
48 from rhodecode.lib.vcs.backends.base import BaseChangeset
48 from rhodecode.lib.vcs.backends.base import BaseChangeset
49 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
49 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
50 from rhodecode.model.changeset_status import ChangesetStatusModel
50 from rhodecode.model.changeset_status import ChangesetStatusModel
51 from rhodecode.model.db import URL_SEP, Permission
51 from rhodecode.model.db import URL_SEP, Permission
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 html_escape_table = {
56 html_escape_table = {
57 "&": "&amp;",
57 "&": "&amp;",
58 '"': "&quot;",
58 '"': "&quot;",
59 "'": "&apos;",
59 "'": "&apos;",
60 ">": "&gt;",
60 ">": "&gt;",
61 "<": "&lt;",
61 "<": "&lt;",
62 }
62 }
63
63
64
64
65 def html_escape(text):
65 def html_escape(text):
66 """Produce entities within text."""
66 """Produce entities within text."""
67 return "".join(html_escape_table.get(c,c) for c in text)
67 return "".join(html_escape_table.get(c,c) for c in text)
68
68
69
69
70 def shorter(text, size=20):
70 def shorter(text, size=20):
71 postfix = '...'
71 postfix = '...'
72 if len(text) > size:
72 if len(text) > size:
73 return text[:size - len(postfix)] + postfix
73 return text[:size - len(postfix)] + postfix
74 return text
74 return text
75
75
76
76
77 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
77 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
78 """
78 """
79 Reset button
79 Reset button
80 """
80 """
81 _set_input_attrs(attrs, type, name, value)
81 _set_input_attrs(attrs, type, name, value)
82 _set_id_attr(attrs, id, name)
82 _set_id_attr(attrs, id, name)
83 convert_boolean_attrs(attrs, ["disabled"])
83 convert_boolean_attrs(attrs, ["disabled"])
84 return HTML.input(**attrs)
84 return HTML.input(**attrs)
85
85
86 reset = _reset
86 reset = _reset
87 safeid = _make_safe_id_component
87 safeid = _make_safe_id_component
88
88
89
89
90 def FID(raw_id, path):
90 def FID(raw_id, path):
91 """
91 """
92 Creates a uniqe ID for filenode based on it's hash of path and revision
92 Creates a uniqe ID for filenode based on it's hash of path and revision
93 it's safe to use in urls
93 it's safe to use in urls
94
94
95 :param raw_id:
95 :param raw_id:
96 :param path:
96 :param path:
97 """
97 """
98
98
99 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
99 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
100
100
101
101
102 def get_token():
102 def get_token():
103 """Return the current authentication token, creating one if one doesn't
103 """Return the current authentication token, creating one if one doesn't
104 already exist.
104 already exist.
105 """
105 """
106 token_key = "_authentication_token"
106 token_key = "_authentication_token"
107 from pylons import session
107 from pylons import session
108 if not token_key in session:
108 if not token_key in session:
109 try:
109 try:
110 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
110 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
111 except AttributeError: # Python < 2.4
111 except AttributeError: # Python < 2.4
112 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
112 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
113 session[token_key] = token
113 session[token_key] = token
114 if hasattr(session, 'save'):
114 if hasattr(session, 'save'):
115 session.save()
115 session.save()
116 return session[token_key]
116 return session[token_key]
117
117
118
118
119 class _GetError(object):
119 class _GetError(object):
120 """Get error from form_errors, and represent it as span wrapped error
120 """Get error from form_errors, and represent it as span wrapped error
121 message
121 message
122
122
123 :param field_name: field to fetch errors for
123 :param field_name: field to fetch errors for
124 :param form_errors: form errors dict
124 :param form_errors: form errors dict
125 """
125 """
126
126
127 def __call__(self, field_name, form_errors):
127 def __call__(self, field_name, form_errors):
128 tmpl = """<span class="error_msg">%s</span>"""
128 tmpl = """<span class="error_msg">%s</span>"""
129 if form_errors and field_name in form_errors:
129 if form_errors and field_name in form_errors:
130 return literal(tmpl % form_errors.get(field_name))
130 return literal(tmpl % form_errors.get(field_name))
131
131
132 get_error = _GetError()
132 get_error = _GetError()
133
133
134
134
135 class _ToolTip(object):
135 class _ToolTip(object):
136
136
137 def __call__(self, tooltip_title, trim_at=50):
137 def __call__(self, tooltip_title, trim_at=50):
138 """
138 """
139 Special function just to wrap our text into nice formatted
139 Special function just to wrap our text into nice formatted
140 autowrapped text
140 autowrapped text
141
141
142 :param tooltip_title:
142 :param tooltip_title:
143 """
143 """
144 tooltip_title = escape(tooltip_title)
144 tooltip_title = escape(tooltip_title)
145 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
145 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
146 return tooltip_title
146 return tooltip_title
147 tooltip = _ToolTip()
147 tooltip = _ToolTip()
148
148
149
149
150 class _FilesBreadCrumbs(object):
150 class _FilesBreadCrumbs(object):
151
151
152 def __call__(self, repo_name, rev, paths):
152 def __call__(self, repo_name, rev, paths):
153 if isinstance(paths, str):
153 if isinstance(paths, str):
154 paths = safe_unicode(paths)
154 paths = safe_unicode(paths)
155 url_l = [link_to(repo_name, url('files_home',
155 url_l = [link_to(repo_name, url('files_home',
156 repo_name=repo_name,
156 repo_name=repo_name,
157 revision=rev, f_path=''),
157 revision=rev, f_path=''),
158 class_='ypjax-link')]
158 class_='ypjax-link')]
159 paths_l = paths.split('/')
159 paths_l = paths.split('/')
160 for cnt, p in enumerate(paths_l):
160 for cnt, p in enumerate(paths_l):
161 if p != '':
161 if p != '':
162 url_l.append(link_to(p,
162 url_l.append(link_to(p,
163 url('files_home',
163 url('files_home',
164 repo_name=repo_name,
164 repo_name=repo_name,
165 revision=rev,
165 revision=rev,
166 f_path='/'.join(paths_l[:cnt + 1])
166 f_path='/'.join(paths_l[:cnt + 1])
167 ),
167 ),
168 class_='ypjax-link'
168 class_='ypjax-link'
169 )
169 )
170 )
170 )
171
171
172 return literal('/'.join(url_l))
172 return literal('/'.join(url_l))
173
173
174 files_breadcrumbs = _FilesBreadCrumbs()
174 files_breadcrumbs = _FilesBreadCrumbs()
175
175
176
176
177 class CodeHtmlFormatter(HtmlFormatter):
177 class CodeHtmlFormatter(HtmlFormatter):
178 """
178 """
179 My code Html Formatter for source codes
179 My code Html Formatter for source codes
180 """
180 """
181
181
182 def wrap(self, source, outfile):
182 def wrap(self, source, outfile):
183 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
183 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
184
184
185 def _wrap_code(self, source):
185 def _wrap_code(self, source):
186 for cnt, it in enumerate(source):
186 for cnt, it in enumerate(source):
187 i, t = it
187 i, t = it
188 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
188 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
189 yield i, t
189 yield i, t
190
190
191 def _wrap_tablelinenos(self, inner):
191 def _wrap_tablelinenos(self, inner):
192 dummyoutfile = StringIO.StringIO()
192 dummyoutfile = StringIO.StringIO()
193 lncount = 0
193 lncount = 0
194 for t, line in inner:
194 for t, line in inner:
195 if t:
195 if t:
196 lncount += 1
196 lncount += 1
197 dummyoutfile.write(line)
197 dummyoutfile.write(line)
198
198
199 fl = self.linenostart
199 fl = self.linenostart
200 mw = len(str(lncount + fl - 1))
200 mw = len(str(lncount + fl - 1))
201 sp = self.linenospecial
201 sp = self.linenospecial
202 st = self.linenostep
202 st = self.linenostep
203 la = self.lineanchors
203 la = self.lineanchors
204 aln = self.anchorlinenos
204 aln = self.anchorlinenos
205 nocls = self.noclasses
205 nocls = self.noclasses
206 if sp:
206 if sp:
207 lines = []
207 lines = []
208
208
209 for i in range(fl, fl + lncount):
209 for i in range(fl, fl + lncount):
210 if i % st == 0:
210 if i % st == 0:
211 if i % sp == 0:
211 if i % sp == 0:
212 if aln:
212 if aln:
213 lines.append('<a href="#%s%d" class="special">%*d</a>' %
213 lines.append('<a href="#%s%d" class="special">%*d</a>' %
214 (la, i, mw, i))
214 (la, i, mw, i))
215 else:
215 else:
216 lines.append('<span class="special">%*d</span>' % (mw, i))
216 lines.append('<span class="special">%*d</span>' % (mw, i))
217 else:
217 else:
218 if aln:
218 if aln:
219 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
219 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
220 else:
220 else:
221 lines.append('%*d' % (mw, i))
221 lines.append('%*d' % (mw, i))
222 else:
222 else:
223 lines.append('')
223 lines.append('')
224 ls = '\n'.join(lines)
224 ls = '\n'.join(lines)
225 else:
225 else:
226 lines = []
226 lines = []
227 for i in range(fl, fl + lncount):
227 for i in range(fl, fl + lncount):
228 if i % st == 0:
228 if i % st == 0:
229 if aln:
229 if aln:
230 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
230 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
231 else:
231 else:
232 lines.append('%*d' % (mw, i))
232 lines.append('%*d' % (mw, i))
233 else:
233 else:
234 lines.append('')
234 lines.append('')
235 ls = '\n'.join(lines)
235 ls = '\n'.join(lines)
236
236
237 # in case you wonder about the seemingly redundant <div> here: since the
237 # in case you wonder about the seemingly redundant <div> here: since the
238 # content in the other cell also is wrapped in a div, some browsers in
238 # content in the other cell also is wrapped in a div, some browsers in
239 # some configurations seem to mess up the formatting...
239 # some configurations seem to mess up the formatting...
240 if nocls:
240 if nocls:
241 yield 0, ('<table class="%stable">' % self.cssclass +
241 yield 0, ('<table class="%stable">' % self.cssclass +
242 '<tr><td><div class="linenodiv" '
242 '<tr><td><div class="linenodiv" '
243 'style="background-color: #f0f0f0; padding-right: 10px">'
243 'style="background-color: #f0f0f0; padding-right: 10px">'
244 '<pre style="line-height: 125%">' +
244 '<pre style="line-height: 125%">' +
245 ls + '</pre></div></td><td id="hlcode" class="code">')
245 ls + '</pre></div></td><td id="hlcode" class="code">')
246 else:
246 else:
247 yield 0, ('<table class="%stable">' % self.cssclass +
247 yield 0, ('<table class="%stable">' % self.cssclass +
248 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
248 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
249 ls + '</pre></div></td><td id="hlcode" class="code">')
249 ls + '</pre></div></td><td id="hlcode" class="code">')
250 yield 0, dummyoutfile.getvalue()
250 yield 0, dummyoutfile.getvalue()
251 yield 0, '</td></tr></table>'
251 yield 0, '</td></tr></table>'
252
252
253
253
254 def pygmentize(filenode, **kwargs):
254 def pygmentize(filenode, **kwargs):
255 """pygmentize function using pygments
255 """pygmentize function using pygments
256
256
257 :param filenode:
257 :param filenode:
258 """
258 """
259
259
260 return literal(code_highlight(filenode.content,
260 return literal(code_highlight(filenode.content,
261 filenode.lexer, CodeHtmlFormatter(**kwargs)))
261 filenode.lexer, CodeHtmlFormatter(**kwargs)))
262
262
263
263
264 def pygmentize_annotation(repo_name, filenode, **kwargs):
264 def pygmentize_annotation(repo_name, filenode, **kwargs):
265 """
265 """
266 pygmentize function for annotation
266 pygmentize function for annotation
267
267
268 :param filenode:
268 :param filenode:
269 """
269 """
270
270
271 color_dict = {}
271 color_dict = {}
272
272
273 def gen_color(n=10000):
273 def gen_color(n=10000):
274 """generator for getting n of evenly distributed colors using
274 """generator for getting n of evenly distributed colors using
275 hsv color and golden ratio. It always return same order of colors
275 hsv color and golden ratio. It always return same order of colors
276
276
277 :returns: RGB tuple
277 :returns: RGB tuple
278 """
278 """
279
279
280 def hsv_to_rgb(h, s, v):
280 def hsv_to_rgb(h, s, v):
281 if s == 0.0:
281 if s == 0.0:
282 return v, v, v
282 return v, v, v
283 i = int(h * 6.0) # XXX assume int() truncates!
283 i = int(h * 6.0) # XXX assume int() truncates!
284 f = (h * 6.0) - i
284 f = (h * 6.0) - i
285 p = v * (1.0 - s)
285 p = v * (1.0 - s)
286 q = v * (1.0 - s * f)
286 q = v * (1.0 - s * f)
287 t = v * (1.0 - s * (1.0 - f))
287 t = v * (1.0 - s * (1.0 - f))
288 i = i % 6
288 i = i % 6
289 if i == 0:
289 if i == 0:
290 return v, t, p
290 return v, t, p
291 if i == 1:
291 if i == 1:
292 return q, v, p
292 return q, v, p
293 if i == 2:
293 if i == 2:
294 return p, v, t
294 return p, v, t
295 if i == 3:
295 if i == 3:
296 return p, q, v
296 return p, q, v
297 if i == 4:
297 if i == 4:
298 return t, p, v
298 return t, p, v
299 if i == 5:
299 if i == 5:
300 return v, p, q
300 return v, p, q
301
301
302 golden_ratio = 0.618033988749895
302 golden_ratio = 0.618033988749895
303 h = 0.22717784590367374
303 h = 0.22717784590367374
304
304
305 for _ in xrange(n):
305 for _ in xrange(n):
306 h += golden_ratio
306 h += golden_ratio
307 h %= 1
307 h %= 1
308 HSV_tuple = [h, 0.95, 0.95]
308 HSV_tuple = [h, 0.95, 0.95]
309 RGB_tuple = hsv_to_rgb(*HSV_tuple)
309 RGB_tuple = hsv_to_rgb(*HSV_tuple)
310 yield map(lambda x: str(int(x * 256)), RGB_tuple)
310 yield map(lambda x: str(int(x * 256)), RGB_tuple)
311
311
312 cgenerator = gen_color()
312 cgenerator = gen_color()
313
313
314 def get_color_string(cs):
314 def get_color_string(cs):
315 if cs in color_dict:
315 if cs in color_dict:
316 col = color_dict[cs]
316 col = color_dict[cs]
317 else:
317 else:
318 col = color_dict[cs] = cgenerator.next()
318 col = color_dict[cs] = cgenerator.next()
319 return "color: rgb(%s)! important;" % (', '.join(col))
319 return "color: rgb(%s)! important;" % (', '.join(col))
320
320
321 def url_func(repo_name):
321 def url_func(repo_name):
322
322
323 def _url_func(changeset):
323 def _url_func(changeset):
324 author = changeset.author
324 author = changeset.author
325 date = changeset.date
325 date = changeset.date
326 message = tooltip(changeset.message)
326 message = tooltip(changeset.message)
327
327
328 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
328 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
329 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
329 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
330 "</b> %s<br/></div>")
330 "</b> %s<br/></div>")
331
331
332 tooltip_html = tooltip_html % (author, date, message)
332 tooltip_html = tooltip_html % (author, date, message)
333 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
333 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
334 short_id(changeset.raw_id))
334 short_id(changeset.raw_id))
335 uri = link_to(
335 uri = link_to(
336 lnk_format,
336 lnk_format,
337 url('changeset_home', repo_name=repo_name,
337 url('changeset_home', repo_name=repo_name,
338 revision=changeset.raw_id),
338 revision=changeset.raw_id),
339 style=get_color_string(changeset.raw_id),
339 style=get_color_string(changeset.raw_id),
340 class_='tooltip',
340 class_='tooltip',
341 title=tooltip_html
341 title=tooltip_html
342 )
342 )
343
343
344 uri += '\n'
344 uri += '\n'
345 return uri
345 return uri
346 return _url_func
346 return _url_func
347
347
348 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
348 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
349
349
350
350
351 def is_following_repo(repo_name, user_id):
351 def is_following_repo(repo_name, user_id):
352 from rhodecode.model.scm import ScmModel
352 from rhodecode.model.scm import ScmModel
353 return ScmModel().is_following_repo(repo_name, user_id)
353 return ScmModel().is_following_repo(repo_name, user_id)
354
354
355 flash = _Flash()
355 flash = _Flash()
356
356
357 #==============================================================================
357 #==============================================================================
358 # SCM FILTERS available via h.
358 # SCM FILTERS available via h.
359 #==============================================================================
359 #==============================================================================
360 from rhodecode.lib.vcs.utils import author_name, author_email
360 from rhodecode.lib.vcs.utils import author_name, author_email
361 from rhodecode.lib.utils2 import credentials_filter, age as _age
361 from rhodecode.lib.utils2 import credentials_filter, age as _age
362 from rhodecode.model.db import User, ChangesetStatus
362 from rhodecode.model.db import User, ChangesetStatus
363
363
364 age = lambda x: _age(x)
364 age = lambda x: _age(x)
365 capitalize = lambda x: x.capitalize()
365 capitalize = lambda x: x.capitalize()
366 email = author_email
366 email = author_email
367 short_id = lambda x: x[:12]
367 short_id = lambda x: x[:12]
368 hide_credentials = lambda x: ''.join(credentials_filter(x))
368 hide_credentials = lambda x: ''.join(credentials_filter(x))
369
369
370
370
371 def fmt_date(date):
371 def fmt_date(date):
372 if date:
372 if date:
373 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
373 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
374 return date.strftime(_fmt).decode('utf8')
374 return date.strftime(_fmt).decode('utf8')
375
375
376 return ""
376 return ""
377
377
378
378
379 def is_git(repository):
379 def is_git(repository):
380 if hasattr(repository, 'alias'):
380 if hasattr(repository, 'alias'):
381 _type = repository.alias
381 _type = repository.alias
382 elif hasattr(repository, 'repo_type'):
382 elif hasattr(repository, 'repo_type'):
383 _type = repository.repo_type
383 _type = repository.repo_type
384 else:
384 else:
385 _type = repository
385 _type = repository
386 return _type == 'git'
386 return _type == 'git'
387
387
388
388
389 def is_hg(repository):
389 def is_hg(repository):
390 if hasattr(repository, 'alias'):
390 if hasattr(repository, 'alias'):
391 _type = repository.alias
391 _type = repository.alias
392 elif hasattr(repository, 'repo_type'):
392 elif hasattr(repository, 'repo_type'):
393 _type = repository.repo_type
393 _type = repository.repo_type
394 else:
394 else:
395 _type = repository
395 _type = repository
396 return _type == 'hg'
396 return _type == 'hg'
397
397
398
398
399 def email_or_none(author):
399 def email_or_none(author):
400 # extract email from the commit string
400 # extract email from the commit string
401 _email = email(author)
401 _email = email(author)
402 if _email != '':
402 if _email != '':
403 # check it against RhodeCode database, and use the MAIN email for this
403 # check it against RhodeCode database, and use the MAIN email for this
404 # user
404 # user
405 user = User.get_by_email(_email, case_insensitive=True, cache=True)
405 user = User.get_by_email(_email, case_insensitive=True, cache=True)
406 if user is not None:
406 if user is not None:
407 return user.email
407 return user.email
408 return _email
408 return _email
409
409
410 # See if it contains a username we can get an email from
410 # See if it contains a username we can get an email from
411 user = User.get_by_username(author_name(author), case_insensitive=True,
411 user = User.get_by_username(author_name(author), case_insensitive=True,
412 cache=True)
412 cache=True)
413 if user is not None:
413 if user is not None:
414 return user.email
414 return user.email
415
415
416 # No valid email, not a valid user in the system, none!
416 # No valid email, not a valid user in the system, none!
417 return None
417 return None
418
418
419
419
420 def person(author, show_attr="username_and_name"):
420 def person(author, show_attr="username_and_name"):
421 # attr to return from fetched user
421 # attr to return from fetched user
422 person_getter = lambda usr: getattr(usr, show_attr)
422 person_getter = lambda usr: getattr(usr, show_attr)
423
423
424 # Valid email in the attribute passed, see if they're in the system
424 # Valid email in the attribute passed, see if they're in the system
425 _email = email(author)
425 _email = email(author)
426 if _email != '':
426 if _email != '':
427 user = User.get_by_email(_email, case_insensitive=True, cache=True)
427 user = User.get_by_email(_email, case_insensitive=True, cache=True)
428 if user is not None:
428 if user is not None:
429 return person_getter(user)
429 return person_getter(user)
430 return _email
430 return _email
431
431
432 # Maybe it's a username?
432 # Maybe it's a username?
433 _author = author_name(author)
433 _author = author_name(author)
434 user = User.get_by_username(_author, case_insensitive=True,
434 user = User.get_by_username(_author, case_insensitive=True,
435 cache=True)
435 cache=True)
436 if user is not None:
436 if user is not None:
437 return person_getter(user)
437 return person_getter(user)
438
438
439 # Still nothing? Just pass back the author name then
439 # Still nothing? Just pass back the author name then
440 return _author
440 return _author
441
441
442
442
443 def person_by_id(id_, show_attr="username_and_name"):
443 def person_by_id(id_, show_attr="username_and_name"):
444 # attr to return from fetched user
444 # attr to return from fetched user
445 person_getter = lambda usr: getattr(usr, show_attr)
445 person_getter = lambda usr: getattr(usr, show_attr)
446
446
447 #maybe it's an ID ?
447 #maybe it's an ID ?
448 if str(id_).isdigit() or isinstance(id_, int):
448 if str(id_).isdigit() or isinstance(id_, int):
449 id_ = int(id_)
449 id_ = int(id_)
450 user = User.get(id_)
450 user = User.get(id_)
451 if user is not None:
451 if user is not None:
452 return person_getter(user)
452 return person_getter(user)
453 return id_
453 return id_
454
454
455
455
456 def desc_stylize(value):
456 def desc_stylize(value):
457 """
457 """
458 converts tags from value into html equivalent
458 converts tags from value into html equivalent
459
459
460 :param value:
460 :param value:
461 """
461 """
462 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
462 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
463 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
463 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
464 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
464 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
465 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
465 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
466 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
466 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
467 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
467 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
468 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/]*)\]',
468 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/]*)\]',
469 '<div class="metatag" tag="lang">\\2</div>', value)
469 '<div class="metatag" tag="lang">\\2</div>', value)
470 value = re.sub(r'\[([a-z]+)\]',
470 value = re.sub(r'\[([a-z]+)\]',
471 '<div class="metatag" tag="\\1">\\1</div>', value)
471 '<div class="metatag" tag="\\1">\\1</div>', value)
472
472
473 return value
473 return value
474
474
475
475
476 def bool2icon(value):
476 def bool2icon(value):
477 """Returns True/False values represented as small html image of true/false
477 """Returns True/False values represented as small html image of true/false
478 icons
478 icons
479
479
480 :param value: bool value
480 :param value: bool value
481 """
481 """
482
482
483 if value is True:
483 if value is True:
484 return HTML.tag('img', src=url("/images/icons/accept.png"),
484 return HTML.tag('img', src=url("/images/icons/accept.png"),
485 alt=_('True'))
485 alt=_('True'))
486
486
487 if value is False:
487 if value is False:
488 return HTML.tag('img', src=url("/images/icons/cancel.png"),
488 return HTML.tag('img', src=url("/images/icons/cancel.png"),
489 alt=_('False'))
489 alt=_('False'))
490
490
491 return value
491 return value
492
492
493
493
494 def action_parser(user_log, feed=False, parse_cs=True):
494 def action_parser(user_log, feed=False, parse_cs=True):
495 """
495 """
496 This helper will action_map the specified string action into translated
496 This helper will action_map the specified string action into translated
497 fancy names with icons and links
497 fancy names with icons and links
498
498
499 :param user_log: user log instance
499 :param user_log: user log instance
500 :param feed: use output for feeds (no html and fancy icons)
500 :param feed: use output for feeds (no html and fancy icons)
501 :param parse_cs: parse Changesets into VCS instances
501 :param parse_cs: parse Changesets into VCS instances
502 """
502 """
503 if request.GET.get('lightweight'):
503 from pylons import tmpl_context as c
504 if c.visual.lightweight_journal:
504 parse_cs = False
505 parse_cs = False
505 action = user_log.action
506 action = user_log.action
506 action_params = ' '
507 action_params = ' '
507
508
508 x = action.split(':')
509 x = action.split(':')
509
510
510 if len(x) > 1:
511 if len(x) > 1:
511 action, action_params = x
512 action, action_params = x
512
513
513 def get_cs_links():
514 def get_cs_links():
514 revs_limit = 3 # display this amount always
515 revs_limit = 3 # display this amount always
515 revs_top_limit = 50 # show upto this amount of changesets hidden
516 revs_top_limit = 50 # show upto this amount of changesets hidden
516 revs_ids = action_params.split(',')
517 revs_ids = action_params.split(',')
517 deleted = user_log.repository is None
518 deleted = user_log.repository is None
518 if deleted:
519 if deleted:
519 return ','.join(revs_ids)
520 return ','.join(revs_ids)
520
521
521 repo_name = user_log.repository.repo_name
522 repo_name = user_log.repository.repo_name
522
523
523 def lnk(rev, repo_name):
524 def lnk(rev, repo_name):
524
525
525 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
526 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
526 if rev.revision:
527 if rev.revision:
527 lbl = 'r%s:%s' % (rev.revision, rev.short_id)
528 lbl = 'r%s:%s' % (rev.revision, rev.short_id)
528 else:
529 else:
529 lbl = '%s' % (rev.short_id)
530 lbl = '%s' % (rev.short_id)
530 _url = url('changeset_home', repo_name=repo_name,
531 _url = url('changeset_home', repo_name=repo_name,
531 revision=rev.raw_id)
532 revision=rev.raw_id)
532 title = tooltip(rev.message) if parse_cs else ''
533 title = tooltip(rev.message) if parse_cs else ''
533 else:
534 else:
534 lbl = '%s' % rev
535 lbl = ('%s' % rev)[:12]
535 _url = '#'
536 _url = '#'
536 title = _('Changeset not found')
537 title = _('Changeset not found')
537
538
538 return link_to(lbl, _url, title=title,
539 return link_to(lbl, _url, title=title,
539 class_='tooltip' if parse_cs else '',)
540 class_='tooltip' if parse_cs else '',)
540
541
541 revs = []
542 revs = []
542 if len(filter(lambda v: v != '', revs_ids)) > 0:
543 if len(filter(lambda v: v != '', revs_ids)) > 0:
543 if parse_cs:
544 if parse_cs:
544 repo = user_log.repository.scm_instance
545 repo = user_log.repository.scm_instance
545 for rev in revs_ids[:revs_top_limit]:
546 for rev in revs_ids[:revs_top_limit]:
546 if parse_cs:
547 if parse_cs:
547 try:
548 try:
548 rev = repo.get_changeset(rev)
549 rev = repo.get_changeset(rev)
549 revs.append(rev)
550 revs.append(rev)
550 except ChangesetDoesNotExistError:
551 except ChangesetDoesNotExistError:
551 log.error('cannot find revision %s in this repo' % rev)
552 log.error('cannot find revision %s in this repo' % rev)
552 revs.append(rev)
553 revs.append(rev)
553 continue
554 continue
554 else:
555 else:
555 rev = AttributeDict({
556 rev = AttributeDict({
556 'short_id': rev[:12],
557 'short_id': rev[:12],
557 'raw_id': rev,
558 'raw_id': rev,
558 })
559 })
559 revs.append(rev)
560 revs.append(rev)
560 cs_links = []
561 cs_links = []
561 cs_links.append(" " + ', '.join(
562 cs_links.append(" " + ', '.join(
562 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
563 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
563 )
564 )
564 )
565 )
565
566
566 compare_view = (
567 compare_view = (
567 ' <div class="compare_view tooltip" title="%s">'
568 ' <div class="compare_view tooltip" title="%s">'
568 '<a href="%s">%s</a> </div>' % (
569 '<a href="%s">%s</a> </div>' % (
569 _('Show all combined changesets %s->%s') % (
570 _('Show all combined changesets %s->%s') % (
570 revs_ids[0], revs_ids[-1]
571 revs_ids[0], revs_ids[-1]
571 ),
572 ),
572 url('changeset_home', repo_name=repo_name,
573 url('changeset_home', repo_name=repo_name,
573 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
574 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
574 ),
575 ),
575 _('compare view')
576 _('compare view')
576 )
577 )
577 )
578 )
578
579
579 # if we have exactly one more than normally displayed
580 # if we have exactly one more than normally displayed
580 # just display it, takes less space than displaying
581 # just display it, takes less space than displaying
581 # "and 1 more revisions"
582 # "and 1 more revisions"
582 if len(revs_ids) == revs_limit + 1:
583 if len(revs_ids) == revs_limit + 1:
583 rev = revs[revs_limit]
584 rev = revs[revs_limit]
584 cs_links.append(", " + lnk(rev, repo_name))
585 cs_links.append(", " + lnk(rev, repo_name))
585
586
586 # hidden-by-default ones
587 # hidden-by-default ones
587 if len(revs_ids) > revs_limit + 1:
588 if len(revs_ids) > revs_limit + 1:
588 uniq_id = revs_ids[0]
589 uniq_id = revs_ids[0]
589 html_tmpl = (
590 html_tmpl = (
590 '<span> %s <a class="show_more" id="_%s" '
591 '<span> %s <a class="show_more" id="_%s" '
591 'href="#more">%s</a> %s</span>'
592 'href="#more">%s</a> %s</span>'
592 )
593 )
593 if not feed:
594 if not feed:
594 cs_links.append(html_tmpl % (
595 cs_links.append(html_tmpl % (
595 _('and'),
596 _('and'),
596 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
597 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
597 _('revisions')
598 _('revisions')
598 )
599 )
599 )
600 )
600
601
601 if not feed:
602 if not feed:
602 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
603 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
603 else:
604 else:
604 html_tmpl = '<span id="%s"> %s </span>'
605 html_tmpl = '<span id="%s"> %s </span>'
605
606
606 morelinks = ', '.join(
607 morelinks = ', '.join(
607 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
608 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
608 )
609 )
609
610
610 if len(revs_ids) > revs_top_limit:
611 if len(revs_ids) > revs_top_limit:
611 morelinks += ', ...'
612 morelinks += ', ...'
612
613
613 cs_links.append(html_tmpl % (uniq_id, morelinks))
614 cs_links.append(html_tmpl % (uniq_id, morelinks))
614 if len(revs) > 1:
615 if len(revs) > 1:
615 cs_links.append(compare_view)
616 cs_links.append(compare_view)
616 return ''.join(cs_links)
617 return ''.join(cs_links)
617
618
618 def get_fork_name():
619 def get_fork_name():
619 repo_name = action_params
620 repo_name = action_params
620 _url = url('summary_home', repo_name=repo_name)
621 _url = url('summary_home', repo_name=repo_name)
621 return _('fork name %s') % link_to(action_params, _url)
622 return _('fork name %s') % link_to(action_params, _url)
622
623
623 def get_user_name():
624 def get_user_name():
624 user_name = action_params
625 user_name = action_params
625 return user_name
626 return user_name
626
627
627 def get_users_group():
628 def get_users_group():
628 group_name = action_params
629 group_name = action_params
629 return group_name
630 return group_name
630
631
631 def get_pull_request():
632 def get_pull_request():
632 pull_request_id = action_params
633 pull_request_id = action_params
633 repo_name = user_log.repository.repo_name
634 repo_name = user_log.repository.repo_name
634 return link_to(_('Pull request #%s') % pull_request_id,
635 return link_to(_('Pull request #%s') % pull_request_id,
635 url('pullrequest_show', repo_name=repo_name,
636 url('pullrequest_show', repo_name=repo_name,
636 pull_request_id=pull_request_id))
637 pull_request_id=pull_request_id))
637
638
638 # action : translated str, callback(extractor), icon
639 # action : translated str, callback(extractor), icon
639 action_map = {
640 action_map = {
640 'user_deleted_repo': (_('[deleted] repository'),
641 'user_deleted_repo': (_('[deleted] repository'),
641 None, 'database_delete.png'),
642 None, 'database_delete.png'),
642 'user_created_repo': (_('[created] repository'),
643 'user_created_repo': (_('[created] repository'),
643 None, 'database_add.png'),
644 None, 'database_add.png'),
644 'user_created_fork': (_('[created] repository as fork'),
645 'user_created_fork': (_('[created] repository as fork'),
645 None, 'arrow_divide.png'),
646 None, 'arrow_divide.png'),
646 'user_forked_repo': (_('[forked] repository'),
647 'user_forked_repo': (_('[forked] repository'),
647 get_fork_name, 'arrow_divide.png'),
648 get_fork_name, 'arrow_divide.png'),
648 'user_updated_repo': (_('[updated] repository'),
649 'user_updated_repo': (_('[updated] repository'),
649 None, 'database_edit.png'),
650 None, 'database_edit.png'),
650 'admin_deleted_repo': (_('[delete] repository'),
651 'admin_deleted_repo': (_('[delete] repository'),
651 None, 'database_delete.png'),
652 None, 'database_delete.png'),
652 'admin_created_repo': (_('[created] repository'),
653 'admin_created_repo': (_('[created] repository'),
653 None, 'database_add.png'),
654 None, 'database_add.png'),
654 'admin_forked_repo': (_('[forked] repository'),
655 'admin_forked_repo': (_('[forked] repository'),
655 None, 'arrow_divide.png'),
656 None, 'arrow_divide.png'),
656 'admin_updated_repo': (_('[updated] repository'),
657 'admin_updated_repo': (_('[updated] repository'),
657 None, 'database_edit.png'),
658 None, 'database_edit.png'),
658 'admin_created_user': (_('[created] user'),
659 'admin_created_user': (_('[created] user'),
659 get_user_name, 'user_add.png'),
660 get_user_name, 'user_add.png'),
660 'admin_updated_user': (_('[updated] user'),
661 'admin_updated_user': (_('[updated] user'),
661 get_user_name, 'user_edit.png'),
662 get_user_name, 'user_edit.png'),
662 'admin_created_users_group': (_('[created] users group'),
663 'admin_created_users_group': (_('[created] users group'),
663 get_users_group, 'group_add.png'),
664 get_users_group, 'group_add.png'),
664 'admin_updated_users_group': (_('[updated] users group'),
665 'admin_updated_users_group': (_('[updated] users group'),
665 get_users_group, 'group_edit.png'),
666 get_users_group, 'group_edit.png'),
666 'user_commented_revision': (_('[commented] on revision in repository'),
667 'user_commented_revision': (_('[commented] on revision in repository'),
667 get_cs_links, 'comment_add.png'),
668 get_cs_links, 'comment_add.png'),
668 'user_commented_pull_request': (_('[commented] on pull request for'),
669 'user_commented_pull_request': (_('[commented] on pull request for'),
669 get_pull_request, 'comment_add.png'),
670 get_pull_request, 'comment_add.png'),
670 'user_closed_pull_request': (_('[closed] pull request for'),
671 'user_closed_pull_request': (_('[closed] pull request for'),
671 get_pull_request, 'tick.png'),
672 get_pull_request, 'tick.png'),
672 'push': (_('[pushed] into'),
673 'push': (_('[pushed] into'),
673 get_cs_links, 'script_add.png'),
674 get_cs_links, 'script_add.png'),
674 'push_local': (_('[committed via RhodeCode] into repository'),
675 'push_local': (_('[committed via RhodeCode] into repository'),
675 get_cs_links, 'script_edit.png'),
676 get_cs_links, 'script_edit.png'),
676 'push_remote': (_('[pulled from remote] into repository'),
677 'push_remote': (_('[pulled from remote] into repository'),
677 get_cs_links, 'connect.png'),
678 get_cs_links, 'connect.png'),
678 'pull': (_('[pulled] from'),
679 'pull': (_('[pulled] from'),
679 None, 'down_16.png'),
680 None, 'down_16.png'),
680 'started_following_repo': (_('[started following] repository'),
681 'started_following_repo': (_('[started following] repository'),
681 None, 'heart_add.png'),
682 None, 'heart_add.png'),
682 'stopped_following_repo': (_('[stopped following] repository'),
683 'stopped_following_repo': (_('[stopped following] repository'),
683 None, 'heart_delete.png'),
684 None, 'heart_delete.png'),
684 }
685 }
685
686
686 action_str = action_map.get(action, action)
687 action_str = action_map.get(action, action)
687 if feed:
688 if feed:
688 action = action_str[0].replace('[', '').replace(']', '')
689 action = action_str[0].replace('[', '').replace(']', '')
689 else:
690 else:
690 action = action_str[0]\
691 action = action_str[0]\
691 .replace('[', '<span class="journal_highlight">')\
692 .replace('[', '<span class="journal_highlight">')\
692 .replace(']', '</span>')
693 .replace(']', '</span>')
693
694
694 action_params_func = lambda: ""
695 action_params_func = lambda: ""
695
696
696 if callable(action_str[1]):
697 if callable(action_str[1]):
697 action_params_func = action_str[1]
698 action_params_func = action_str[1]
698
699
699 def action_parser_icon():
700 def action_parser_icon():
700 action = user_log.action
701 action = user_log.action
701 action_params = None
702 action_params = None
702 x = action.split(':')
703 x = action.split(':')
703
704
704 if len(x) > 1:
705 if len(x) > 1:
705 action, action_params = x
706 action, action_params = x
706
707
707 tmpl = """<img src="%s%s" alt="%s"/>"""
708 tmpl = """<img src="%s%s" alt="%s"/>"""
708 ico = action_map.get(action, ['', '', ''])[2]
709 ico = action_map.get(action, ['', '', ''])[2]
709 return literal(tmpl % ((url('/images/icons/')), ico, action))
710 return literal(tmpl % ((url('/images/icons/')), ico, action))
710
711
711 # returned callbacks we need to call to get
712 # returned callbacks we need to call to get
712 return [lambda: literal(action), action_params_func, action_parser_icon]
713 return [lambda: literal(action), action_params_func, action_parser_icon]
713
714
714
715
715
716
716 #==============================================================================
717 #==============================================================================
717 # PERMS
718 # PERMS
718 #==============================================================================
719 #==============================================================================
719 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
720 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
720 HasRepoPermissionAny, HasRepoPermissionAll
721 HasRepoPermissionAny, HasRepoPermissionAll
721
722
722
723
723 #==============================================================================
724 #==============================================================================
724 # GRAVATAR URL
725 # GRAVATAR URL
725 #==============================================================================
726 #==============================================================================
726
727
727 def gravatar_url(email_address, size=30):
728 def gravatar_url(email_address, size=30):
728 from pylons import url ## doh, we need to re-import url to mock it later
729 from pylons import url ## doh, we need to re-import url to mock it later
729 if(str2bool(config['app_conf'].get('use_gravatar')) and
730 if(str2bool(config['app_conf'].get('use_gravatar')) and
730 config['app_conf'].get('alternative_gravatar_url')):
731 config['app_conf'].get('alternative_gravatar_url')):
731 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
732 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
732 parsed_url = urlparse.urlparse(url.current(qualified=True))
733 parsed_url = urlparse.urlparse(url.current(qualified=True))
733 tmpl = tmpl.replace('{email}', email_address)\
734 tmpl = tmpl.replace('{email}', email_address)\
734 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
735 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
735 .replace('{netloc}', parsed_url.netloc)\
736 .replace('{netloc}', parsed_url.netloc)\
736 .replace('{scheme}', parsed_url.scheme)\
737 .replace('{scheme}', parsed_url.scheme)\
737 .replace('{size}', str(size))
738 .replace('{size}', str(size))
738 return tmpl
739 return tmpl
739
740
740 if (not str2bool(config['app_conf'].get('use_gravatar')) or
741 if (not str2bool(config['app_conf'].get('use_gravatar')) or
741 not email_address or email_address == 'anonymous@rhodecode.org'):
742 not email_address or email_address == 'anonymous@rhodecode.org'):
742 f = lambda a, l: min(l, key=lambda x: abs(x - a))
743 f = lambda a, l: min(l, key=lambda x: abs(x - a))
743 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
744 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
744
745
745 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
746 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
746 default = 'identicon'
747 default = 'identicon'
747 baseurl_nossl = "http://www.gravatar.com/avatar/"
748 baseurl_nossl = "http://www.gravatar.com/avatar/"
748 baseurl_ssl = "https://secure.gravatar.com/avatar/"
749 baseurl_ssl = "https://secure.gravatar.com/avatar/"
749 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
750 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
750
751
751 if isinstance(email_address, unicode):
752 if isinstance(email_address, unicode):
752 #hashlib crashes on unicode items
753 #hashlib crashes on unicode items
753 email_address = safe_str(email_address)
754 email_address = safe_str(email_address)
754 # construct the url
755 # construct the url
755 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
756 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
756 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
757 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
757
758
758 return gravatar_url
759 return gravatar_url
759
760
760
761
761 #==============================================================================
762 #==============================================================================
762 # REPO PAGER, PAGER FOR REPOSITORY
763 # REPO PAGER, PAGER FOR REPOSITORY
763 #==============================================================================
764 #==============================================================================
764 class RepoPage(Page):
765 class RepoPage(Page):
765
766
766 def __init__(self, collection, page=1, items_per_page=20,
767 def __init__(self, collection, page=1, items_per_page=20,
767 item_count=None, url=None, **kwargs):
768 item_count=None, url=None, **kwargs):
768
769
769 """Create a "RepoPage" instance. special pager for paging
770 """Create a "RepoPage" instance. special pager for paging
770 repository
771 repository
771 """
772 """
772 self._url_generator = url
773 self._url_generator = url
773
774
774 # Safe the kwargs class-wide so they can be used in the pager() method
775 # Safe the kwargs class-wide so they can be used in the pager() method
775 self.kwargs = kwargs
776 self.kwargs = kwargs
776
777
777 # Save a reference to the collection
778 # Save a reference to the collection
778 self.original_collection = collection
779 self.original_collection = collection
779
780
780 self.collection = collection
781 self.collection = collection
781
782
782 # The self.page is the number of the current page.
783 # The self.page is the number of the current page.
783 # The first page has the number 1!
784 # The first page has the number 1!
784 try:
785 try:
785 self.page = int(page) # make it int() if we get it as a string
786 self.page = int(page) # make it int() if we get it as a string
786 except (ValueError, TypeError):
787 except (ValueError, TypeError):
787 self.page = 1
788 self.page = 1
788
789
789 self.items_per_page = items_per_page
790 self.items_per_page = items_per_page
790
791
791 # Unless the user tells us how many items the collections has
792 # Unless the user tells us how many items the collections has
792 # we calculate that ourselves.
793 # we calculate that ourselves.
793 if item_count is not None:
794 if item_count is not None:
794 self.item_count = item_count
795 self.item_count = item_count
795 else:
796 else:
796 self.item_count = len(self.collection)
797 self.item_count = len(self.collection)
797
798
798 # Compute the number of the first and last available page
799 # Compute the number of the first and last available page
799 if self.item_count > 0:
800 if self.item_count > 0:
800 self.first_page = 1
801 self.first_page = 1
801 self.page_count = int(math.ceil(float(self.item_count) /
802 self.page_count = int(math.ceil(float(self.item_count) /
802 self.items_per_page))
803 self.items_per_page))
803 self.last_page = self.first_page + self.page_count - 1
804 self.last_page = self.first_page + self.page_count - 1
804
805
805 # Make sure that the requested page number is the range of
806 # Make sure that the requested page number is the range of
806 # valid pages
807 # valid pages
807 if self.page > self.last_page:
808 if self.page > self.last_page:
808 self.page = self.last_page
809 self.page = self.last_page
809 elif self.page < self.first_page:
810 elif self.page < self.first_page:
810 self.page = self.first_page
811 self.page = self.first_page
811
812
812 # Note: the number of items on this page can be less than
813 # Note: the number of items on this page can be less than
813 # items_per_page if the last page is not full
814 # items_per_page if the last page is not full
814 self.first_item = max(0, (self.item_count) - (self.page *
815 self.first_item = max(0, (self.item_count) - (self.page *
815 items_per_page))
816 items_per_page))
816 self.last_item = ((self.item_count - 1) - items_per_page *
817 self.last_item = ((self.item_count - 1) - items_per_page *
817 (self.page - 1))
818 (self.page - 1))
818
819
819 self.items = list(self.collection[self.first_item:self.last_item + 1])
820 self.items = list(self.collection[self.first_item:self.last_item + 1])
820
821
821 # Links to previous and next page
822 # Links to previous and next page
822 if self.page > self.first_page:
823 if self.page > self.first_page:
823 self.previous_page = self.page - 1
824 self.previous_page = self.page - 1
824 else:
825 else:
825 self.previous_page = None
826 self.previous_page = None
826
827
827 if self.page < self.last_page:
828 if self.page < self.last_page:
828 self.next_page = self.page + 1
829 self.next_page = self.page + 1
829 else:
830 else:
830 self.next_page = None
831 self.next_page = None
831
832
832 # No items available
833 # No items available
833 else:
834 else:
834 self.first_page = None
835 self.first_page = None
835 self.page_count = 0
836 self.page_count = 0
836 self.last_page = None
837 self.last_page = None
837 self.first_item = None
838 self.first_item = None
838 self.last_item = None
839 self.last_item = None
839 self.previous_page = None
840 self.previous_page = None
840 self.next_page = None
841 self.next_page = None
841 self.items = []
842 self.items = []
842
843
843 # This is a subclass of the 'list' type. Initialise the list now.
844 # This is a subclass of the 'list' type. Initialise the list now.
844 list.__init__(self, reversed(self.items))
845 list.__init__(self, reversed(self.items))
845
846
846
847
847 def changed_tooltip(nodes):
848 def changed_tooltip(nodes):
848 """
849 """
849 Generates a html string for changed nodes in changeset page.
850 Generates a html string for changed nodes in changeset page.
850 It limits the output to 30 entries
851 It limits the output to 30 entries
851
852
852 :param nodes: LazyNodesGenerator
853 :param nodes: LazyNodesGenerator
853 """
854 """
854 if nodes:
855 if nodes:
855 pref = ': <br/> '
856 pref = ': <br/> '
856 suf = ''
857 suf = ''
857 if len(nodes) > 30:
858 if len(nodes) > 30:
858 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
859 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
859 return literal(pref + '<br/> '.join([safe_unicode(x.path)
860 return literal(pref + '<br/> '.join([safe_unicode(x.path)
860 for x in nodes[:30]]) + suf)
861 for x in nodes[:30]]) + suf)
861 else:
862 else:
862 return ': ' + _('No Files')
863 return ': ' + _('No Files')
863
864
864
865
865 def repo_link(groups_and_repos):
866 def repo_link(groups_and_repos):
866 """
867 """
867 Makes a breadcrumbs link to repo within a group
868 Makes a breadcrumbs link to repo within a group
868 joins &raquo; on each group to create a fancy link
869 joins &raquo; on each group to create a fancy link
869
870
870 ex::
871 ex::
871 group >> subgroup >> repo
872 group >> subgroup >> repo
872
873
873 :param groups_and_repos:
874 :param groups_and_repos:
874 """
875 """
875 groups, repo_name = groups_and_repos
876 groups, repo_name = groups_and_repos
876
877
877 if not groups:
878 if not groups:
878 return repo_name
879 return repo_name
879 else:
880 else:
880 def make_link(group):
881 def make_link(group):
881 return link_to(group.name, url('repos_group_home',
882 return link_to(group.name, url('repos_group_home',
882 group_name=group.group_name))
883 group_name=group.group_name))
883 return literal(' &raquo; '.join(map(make_link, groups)) + \
884 return literal(' &raquo; '.join(map(make_link, groups)) + \
884 " &raquo; " + repo_name)
885 " &raquo; " + repo_name)
885
886
886
887
887 def fancy_file_stats(stats):
888 def fancy_file_stats(stats):
888 """
889 """
889 Displays a fancy two colored bar for number of added/deleted
890 Displays a fancy two colored bar for number of added/deleted
890 lines of code on file
891 lines of code on file
891
892
892 :param stats: two element list of added/deleted lines of code
893 :param stats: two element list of added/deleted lines of code
893 """
894 """
894
895
895 a, d, t = stats[0], stats[1], stats[0] + stats[1]
896 a, d, t = stats[0], stats[1], stats[0] + stats[1]
896 width = 100
897 width = 100
897 unit = float(width) / (t or 1)
898 unit = float(width) / (t or 1)
898
899
899 # needs > 9% of width to be visible or 0 to be hidden
900 # needs > 9% of width to be visible or 0 to be hidden
900 a_p = max(9, unit * a) if a > 0 else 0
901 a_p = max(9, unit * a) if a > 0 else 0
901 d_p = max(9, unit * d) if d > 0 else 0
902 d_p = max(9, unit * d) if d > 0 else 0
902 p_sum = a_p + d_p
903 p_sum = a_p + d_p
903
904
904 if p_sum > width:
905 if p_sum > width:
905 #adjust the percentage to be == 100% since we adjusted to 9
906 #adjust the percentage to be == 100% since we adjusted to 9
906 if a_p > d_p:
907 if a_p > d_p:
907 a_p = a_p - (p_sum - width)
908 a_p = a_p - (p_sum - width)
908 else:
909 else:
909 d_p = d_p - (p_sum - width)
910 d_p = d_p - (p_sum - width)
910
911
911 a_v = a if a > 0 else ''
912 a_v = a if a > 0 else ''
912 d_v = d if d > 0 else ''
913 d_v = d if d > 0 else ''
913
914
914 def cgen(l_type):
915 def cgen(l_type):
915 mapping = {'tr': 'top-right-rounded-corner-mid',
916 mapping = {'tr': 'top-right-rounded-corner-mid',
916 'tl': 'top-left-rounded-corner-mid',
917 'tl': 'top-left-rounded-corner-mid',
917 'br': 'bottom-right-rounded-corner-mid',
918 'br': 'bottom-right-rounded-corner-mid',
918 'bl': 'bottom-left-rounded-corner-mid'}
919 'bl': 'bottom-left-rounded-corner-mid'}
919 map_getter = lambda x: mapping[x]
920 map_getter = lambda x: mapping[x]
920
921
921 if l_type == 'a' and d_v:
922 if l_type == 'a' and d_v:
922 #case when added and deleted are present
923 #case when added and deleted are present
923 return ' '.join(map(map_getter, ['tl', 'bl']))
924 return ' '.join(map(map_getter, ['tl', 'bl']))
924
925
925 if l_type == 'a' and not d_v:
926 if l_type == 'a' and not d_v:
926 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
927 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
927
928
928 if l_type == 'd' and a_v:
929 if l_type == 'd' and a_v:
929 return ' '.join(map(map_getter, ['tr', 'br']))
930 return ' '.join(map(map_getter, ['tr', 'br']))
930
931
931 if l_type == 'd' and not a_v:
932 if l_type == 'd' and not a_v:
932 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
933 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
933
934
934 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
935 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
935 cgen('a'), a_p, a_v
936 cgen('a'), a_p, a_v
936 )
937 )
937 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
938 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
938 cgen('d'), d_p, d_v
939 cgen('d'), d_p, d_v
939 )
940 )
940 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
941 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
941
942
942
943
943 def urlify_text(text_):
944 def urlify_text(text_):
944 import re
945 import re
945
946
946 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
947 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
947 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
948 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
948
949
949 def url_func(match_obj):
950 def url_func(match_obj):
950 url_full = match_obj.groups()[0]
951 url_full = match_obj.groups()[0]
951 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
952 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
952
953
953 return literal(url_pat.sub(url_func, text_))
954 return literal(url_pat.sub(url_func, text_))
954
955
955
956
956 def urlify_changesets(text_, repository):
957 def urlify_changesets(text_, repository):
957 """
958 """
958 Extract revision ids from changeset and make link from them
959 Extract revision ids from changeset and make link from them
959
960
960 :param text_:
961 :param text_:
961 :param repository:
962 :param repository:
962 """
963 """
963 import re
964 import re
964 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
965 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
965
966
966 def url_func(match_obj):
967 def url_func(match_obj):
967 rev = match_obj.groups()[0]
968 rev = match_obj.groups()[0]
968 pref = ''
969 pref = ''
969 if match_obj.group().startswith(' '):
970 if match_obj.group().startswith(' '):
970 pref = ' '
971 pref = ' '
971 tmpl = (
972 tmpl = (
972 '%(pref)s<a class="%(cls)s" href="%(url)s">'
973 '%(pref)s<a class="%(cls)s" href="%(url)s">'
973 '%(rev)s'
974 '%(rev)s'
974 '</a>'
975 '</a>'
975 )
976 )
976 return tmpl % {
977 return tmpl % {
977 'pref': pref,
978 'pref': pref,
978 'cls': 'revision-link',
979 'cls': 'revision-link',
979 'url': url('changeset_home', repo_name=repository, revision=rev),
980 'url': url('changeset_home', repo_name=repository, revision=rev),
980 'rev': rev,
981 'rev': rev,
981 }
982 }
982
983
983 newtext = URL_PAT.sub(url_func, text_)
984 newtext = URL_PAT.sub(url_func, text_)
984
985
985 return newtext
986 return newtext
986
987
987
988
988 def urlify_commit(text_, repository=None, link_=None):
989 def urlify_commit(text_, repository=None, link_=None):
989 """
990 """
990 Parses given text message and makes proper links.
991 Parses given text message and makes proper links.
991 issues are linked to given issue-server, and rest is a changeset link
992 issues are linked to given issue-server, and rest is a changeset link
992 if link_ is given, in other case it's a plain text
993 if link_ is given, in other case it's a plain text
993
994
994 :param text_:
995 :param text_:
995 :param repository:
996 :param repository:
996 :param link_: changeset link
997 :param link_: changeset link
997 """
998 """
998 import re
999 import re
999 import traceback
1000 import traceback
1000
1001
1001 def escaper(string):
1002 def escaper(string):
1002 return string.replace('<', '&lt;').replace('>', '&gt;')
1003 return string.replace('<', '&lt;').replace('>', '&gt;')
1003
1004
1004 def linkify_others(t, l):
1005 def linkify_others(t, l):
1005 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1006 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1006 links = []
1007 links = []
1007 for e in urls.split(t):
1008 for e in urls.split(t):
1008 if not urls.match(e):
1009 if not urls.match(e):
1009 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1010 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1010 else:
1011 else:
1011 links.append(e)
1012 links.append(e)
1012
1013
1013 return ''.join(links)
1014 return ''.join(links)
1014
1015
1015 # urlify changesets - extrac revisions and make link out of them
1016 # urlify changesets - extrac revisions and make link out of them
1016 newtext = urlify_changesets(escaper(text_), repository)
1017 newtext = urlify_changesets(escaper(text_), repository)
1017
1018
1018 try:
1019 try:
1019 conf = config['app_conf']
1020 conf = config['app_conf']
1020
1021
1021 # allow multiple issue servers to be used
1022 # allow multiple issue servers to be used
1022 valid_indices = [
1023 valid_indices = [
1023 x.group(1)
1024 x.group(1)
1024 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1025 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1025 if x and 'issue_server_link%s' % x.group(1) in conf
1026 if x and 'issue_server_link%s' % x.group(1) in conf
1026 and 'issue_prefix%s' % x.group(1) in conf
1027 and 'issue_prefix%s' % x.group(1) in conf
1027 ]
1028 ]
1028
1029
1029 log.debug('found issue server suffixes `%s` during valuation of: %s'
1030 log.debug('found issue server suffixes `%s` during valuation of: %s'
1030 % (','.join(valid_indices), newtext))
1031 % (','.join(valid_indices), newtext))
1031
1032
1032 for pattern_index in valid_indices:
1033 for pattern_index in valid_indices:
1033 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1034 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1034 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1035 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1035 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1036 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1036
1037
1037 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1038 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1038 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1039 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1039 ISSUE_PREFIX))
1040 ISSUE_PREFIX))
1040
1041
1041 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1042 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1042
1043
1043 def url_func(match_obj):
1044 def url_func(match_obj):
1044 pref = ''
1045 pref = ''
1045 if match_obj.group().startswith(' '):
1046 if match_obj.group().startswith(' '):
1046 pref = ' '
1047 pref = ' '
1047
1048
1048 issue_id = ''.join(match_obj.groups())
1049 issue_id = ''.join(match_obj.groups())
1049 tmpl = (
1050 tmpl = (
1050 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1051 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1051 '%(issue-prefix)s%(id-repr)s'
1052 '%(issue-prefix)s%(id-repr)s'
1052 '</a>'
1053 '</a>'
1053 )
1054 )
1054 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1055 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1055 if repository:
1056 if repository:
1056 url = url.replace('{repo}', repository)
1057 url = url.replace('{repo}', repository)
1057 repo_name = repository.split(URL_SEP)[-1]
1058 repo_name = repository.split(URL_SEP)[-1]
1058 url = url.replace('{repo_name}', repo_name)
1059 url = url.replace('{repo_name}', repo_name)
1059
1060
1060 return tmpl % {
1061 return tmpl % {
1061 'pref': pref,
1062 'pref': pref,
1062 'cls': 'issue-tracker-link',
1063 'cls': 'issue-tracker-link',
1063 'url': url,
1064 'url': url,
1064 'id-repr': issue_id,
1065 'id-repr': issue_id,
1065 'issue-prefix': ISSUE_PREFIX,
1066 'issue-prefix': ISSUE_PREFIX,
1066 'serv': ISSUE_SERVER_LNK,
1067 'serv': ISSUE_SERVER_LNK,
1067 }
1068 }
1068 newtext = URL_PAT.sub(url_func, newtext)
1069 newtext = URL_PAT.sub(url_func, newtext)
1069 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1070 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1070
1071
1071 # if we actually did something above
1072 # if we actually did something above
1072 if valid_indices:
1073 if valid_indices:
1073 if link_:
1074 if link_:
1074 # wrap not links into final link => link_
1075 # wrap not links into final link => link_
1075 newtext = linkify_others(newtext, link_)
1076 newtext = linkify_others(newtext, link_)
1076
1077
1077 return literal(newtext)
1078 return literal(newtext)
1078 except:
1079 except:
1079 log.error(traceback.format_exc())
1080 log.error(traceback.format_exc())
1080 pass
1081 pass
1081
1082
1082 return newtext
1083 return newtext
1083
1084
1084
1085
1085 def rst(source):
1086 def rst(source):
1086 return literal('<div class="rst-block">%s</div>' %
1087 return literal('<div class="rst-block">%s</div>' %
1087 MarkupRenderer.rst(source))
1088 MarkupRenderer.rst(source))
1088
1089
1089
1090
1090 def rst_w_mentions(source):
1091 def rst_w_mentions(source):
1091 """
1092 """
1092 Wrapped rst renderer with @mention highlighting
1093 Wrapped rst renderer with @mention highlighting
1093
1094
1094 :param source:
1095 :param source:
1095 """
1096 """
1096 return literal('<div class="rst-block">%s</div>' %
1097 return literal('<div class="rst-block">%s</div>' %
1097 MarkupRenderer.rst_with_mentions(source))
1098 MarkupRenderer.rst_with_mentions(source))
1098
1099
1099
1100
1100 def changeset_status(repo, revision):
1101 def changeset_status(repo, revision):
1101 return ChangesetStatusModel().get_status(repo, revision)
1102 return ChangesetStatusModel().get_status(repo, revision)
1102
1103
1103
1104
1104 def changeset_status_lbl(changeset_status):
1105 def changeset_status_lbl(changeset_status):
1105 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1106 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1106
1107
1107
1108
1108 def get_permission_name(key):
1109 def get_permission_name(key):
1109 return dict(Permission.PERMS).get(key)
1110 return dict(Permission.PERMS).get(key)
@@ -1,348 +1,349
1 """ this is forms validation classes
1 """ this is forms validation classes
2 http://formencode.org/module-formencode.validators.html
2 http://formencode.org/module-formencode.validators.html
3 for list off all availible validators
3 for list off all availible validators
4
4
5 we can create our own validators
5 we can create our own validators
6
6
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 pre_validators [] These validators will be applied before the schema
8 pre_validators [] These validators will be applied before the schema
9 chained_validators [] These validators will be applied after the schema
9 chained_validators [] These validators will be applied after the schema
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 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.
12 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.
13 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
13 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
14
14
15
15
16 <name> = formencode.validators.<name of validator>
16 <name> = formencode.validators.<name of validator>
17 <name> must equal form name
17 <name> must equal form name
18 list=[1,2,3,4,5]
18 list=[1,2,3,4,5]
19 for SELECT use formencode.All(OneOf(list), Int())
19 for SELECT use formencode.All(OneOf(list), Int())
20
20
21 """
21 """
22 import logging
22 import logging
23
23
24 import formencode
24 import formencode
25 from formencode import All
25 from formencode import All
26
26
27 from pylons.i18n.translation import _
27 from pylons.i18n.translation import _
28
28
29 from rhodecode.model import validators as v
29 from rhodecode.model import validators as v
30 from rhodecode import BACKENDS
30 from rhodecode import BACKENDS
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 class LoginForm(formencode.Schema):
35 class LoginForm(formencode.Schema):
36 allow_extra_fields = True
36 allow_extra_fields = True
37 filter_extra_fields = True
37 filter_extra_fields = True
38 username = v.UnicodeString(
38 username = v.UnicodeString(
39 strip=True,
39 strip=True,
40 min=1,
40 min=1,
41 not_empty=True,
41 not_empty=True,
42 messages={
42 messages={
43 'empty': _(u'Please enter a login'),
43 'empty': _(u'Please enter a login'),
44 'tooShort': _(u'Enter a value %(min)i characters long or more')}
44 'tooShort': _(u'Enter a value %(min)i characters long or more')}
45 )
45 )
46
46
47 password = v.UnicodeString(
47 password = v.UnicodeString(
48 strip=False,
48 strip=False,
49 min=3,
49 min=3,
50 not_empty=True,
50 not_empty=True,
51 messages={
51 messages={
52 'empty': _(u'Please enter a password'),
52 'empty': _(u'Please enter a password'),
53 'tooShort': _(u'Enter %(min)i characters or more')}
53 'tooShort': _(u'Enter %(min)i characters or more')}
54 )
54 )
55
55
56 remember = v.StringBoolean(if_missing=False)
56 remember = v.StringBoolean(if_missing=False)
57
57
58 chained_validators = [v.ValidAuth()]
58 chained_validators = [v.ValidAuth()]
59
59
60
60
61 def UserForm(edit=False, old_data={}):
61 def UserForm(edit=False, old_data={}):
62 class _UserForm(formencode.Schema):
62 class _UserForm(formencode.Schema):
63 allow_extra_fields = True
63 allow_extra_fields = True
64 filter_extra_fields = True
64 filter_extra_fields = True
65 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
65 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
66 v.ValidUsername(edit, old_data))
66 v.ValidUsername(edit, old_data))
67 if edit:
67 if edit:
68 new_password = All(
68 new_password = All(
69 v.ValidPassword(),
69 v.ValidPassword(),
70 v.UnicodeString(strip=False, min=6, not_empty=False)
70 v.UnicodeString(strip=False, min=6, not_empty=False)
71 )
71 )
72 password_confirmation = All(
72 password_confirmation = All(
73 v.ValidPassword(),
73 v.ValidPassword(),
74 v.UnicodeString(strip=False, min=6, not_empty=False),
74 v.UnicodeString(strip=False, min=6, not_empty=False),
75 )
75 )
76 admin = v.StringBoolean(if_missing=False)
76 admin = v.StringBoolean(if_missing=False)
77 else:
77 else:
78 password = All(
78 password = All(
79 v.ValidPassword(),
79 v.ValidPassword(),
80 v.UnicodeString(strip=False, min=6, not_empty=True)
80 v.UnicodeString(strip=False, min=6, not_empty=True)
81 )
81 )
82 password_confirmation = All(
82 password_confirmation = All(
83 v.ValidPassword(),
83 v.ValidPassword(),
84 v.UnicodeString(strip=False, min=6, not_empty=False)
84 v.UnicodeString(strip=False, min=6, not_empty=False)
85 )
85 )
86
86
87 active = v.StringBoolean(if_missing=False)
87 active = v.StringBoolean(if_missing=False)
88 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
88 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
89 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
89 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
90 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
90 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
91
91
92 chained_validators = [v.ValidPasswordsMatch()]
92 chained_validators = [v.ValidPasswordsMatch()]
93
93
94 return _UserForm
94 return _UserForm
95
95
96
96
97 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
97 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
98 class _UsersGroupForm(formencode.Schema):
98 class _UsersGroupForm(formencode.Schema):
99 allow_extra_fields = True
99 allow_extra_fields = True
100 filter_extra_fields = True
100 filter_extra_fields = True
101
101
102 users_group_name = All(
102 users_group_name = All(
103 v.UnicodeString(strip=True, min=1, not_empty=True),
103 v.UnicodeString(strip=True, min=1, not_empty=True),
104 v.ValidUsersGroup(edit, old_data)
104 v.ValidUsersGroup(edit, old_data)
105 )
105 )
106
106
107 users_group_active = v.StringBoolean(if_missing=False)
107 users_group_active = v.StringBoolean(if_missing=False)
108
108
109 if edit:
109 if edit:
110 users_group_members = v.OneOf(
110 users_group_members = v.OneOf(
111 available_members, hideList=False, testValueList=True,
111 available_members, hideList=False, testValueList=True,
112 if_missing=None, not_empty=False
112 if_missing=None, not_empty=False
113 )
113 )
114
114
115 return _UsersGroupForm
115 return _UsersGroupForm
116
116
117
117
118 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
118 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
119 class _ReposGroupForm(formencode.Schema):
119 class _ReposGroupForm(formencode.Schema):
120 allow_extra_fields = True
120 allow_extra_fields = True
121 filter_extra_fields = False
121 filter_extra_fields = False
122
122
123 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
123 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
124 v.SlugifyName())
124 v.SlugifyName())
125 group_description = v.UnicodeString(strip=True, min=1,
125 group_description = v.UnicodeString(strip=True, min=1,
126 not_empty=True)
126 not_empty=True)
127 group_parent_id = v.OneOf(available_groups, hideList=False,
127 group_parent_id = v.OneOf(available_groups, hideList=False,
128 testValueList=True,
128 testValueList=True,
129 if_missing=None, not_empty=False)
129 if_missing=None, not_empty=False)
130 enable_locking = v.StringBoolean(if_missing=False)
130 enable_locking = v.StringBoolean(if_missing=False)
131 recursive = v.StringBoolean(if_missing=False)
131 recursive = v.StringBoolean(if_missing=False)
132 chained_validators = [v.ValidReposGroup(edit, old_data),
132 chained_validators = [v.ValidReposGroup(edit, old_data),
133 v.ValidPerms('group')]
133 v.ValidPerms('group')]
134
134
135 return _ReposGroupForm
135 return _ReposGroupForm
136
136
137
137
138 def RegisterForm(edit=False, old_data={}):
138 def RegisterForm(edit=False, old_data={}):
139 class _RegisterForm(formencode.Schema):
139 class _RegisterForm(formencode.Schema):
140 allow_extra_fields = True
140 allow_extra_fields = True
141 filter_extra_fields = True
141 filter_extra_fields = True
142 username = All(
142 username = All(
143 v.ValidUsername(edit, old_data),
143 v.ValidUsername(edit, old_data),
144 v.UnicodeString(strip=True, min=1, not_empty=True)
144 v.UnicodeString(strip=True, min=1, not_empty=True)
145 )
145 )
146 password = All(
146 password = All(
147 v.ValidPassword(),
147 v.ValidPassword(),
148 v.UnicodeString(strip=False, min=6, not_empty=True)
148 v.UnicodeString(strip=False, min=6, not_empty=True)
149 )
149 )
150 password_confirmation = All(
150 password_confirmation = All(
151 v.ValidPassword(),
151 v.ValidPassword(),
152 v.UnicodeString(strip=False, min=6, not_empty=True)
152 v.UnicodeString(strip=False, min=6, not_empty=True)
153 )
153 )
154 active = v.StringBoolean(if_missing=False)
154 active = v.StringBoolean(if_missing=False)
155 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
155 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
156 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
156 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
157 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
157 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
158
158
159 chained_validators = [v.ValidPasswordsMatch()]
159 chained_validators = [v.ValidPasswordsMatch()]
160
160
161 return _RegisterForm
161 return _RegisterForm
162
162
163
163
164 def PasswordResetForm():
164 def PasswordResetForm():
165 class _PasswordResetForm(formencode.Schema):
165 class _PasswordResetForm(formencode.Schema):
166 allow_extra_fields = True
166 allow_extra_fields = True
167 filter_extra_fields = True
167 filter_extra_fields = True
168 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
168 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
169 return _PasswordResetForm
169 return _PasswordResetForm
170
170
171
171
172 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
172 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
173 repo_groups=[], landing_revs=[]):
173 repo_groups=[], landing_revs=[]):
174 class _RepoForm(formencode.Schema):
174 class _RepoForm(formencode.Schema):
175 allow_extra_fields = True
175 allow_extra_fields = True
176 filter_extra_fields = False
176 filter_extra_fields = False
177 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
177 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
178 v.SlugifyName())
178 v.SlugifyName())
179 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
179 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
180 repo_group = All(v.CanWriteGroup(),
180 repo_group = All(v.CanWriteGroup(),
181 v.OneOf(repo_groups, hideList=True))
181 v.OneOf(repo_groups, hideList=True))
182 repo_type = v.OneOf(supported_backends)
182 repo_type = v.OneOf(supported_backends)
183 description = v.UnicodeString(strip=True, min=1, not_empty=False)
183 description = v.UnicodeString(strip=True, min=1, not_empty=False)
184 private = v.StringBoolean(if_missing=False)
184 private = v.StringBoolean(if_missing=False)
185 enable_statistics = v.StringBoolean(if_missing=False)
185 enable_statistics = v.StringBoolean(if_missing=False)
186 enable_downloads = v.StringBoolean(if_missing=False)
186 enable_downloads = v.StringBoolean(if_missing=False)
187 enable_locking = v.StringBoolean(if_missing=False)
187 enable_locking = v.StringBoolean(if_missing=False)
188 landing_rev = v.OneOf(landing_revs, hideList=True)
188 landing_rev = v.OneOf(landing_revs, hideList=True)
189
189
190 if edit:
190 if edit:
191 #this is repo owner
191 #this is repo owner
192 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
192 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
193
193
194 chained_validators = [v.ValidCloneUri(),
194 chained_validators = [v.ValidCloneUri(),
195 v.ValidRepoName(edit, old_data),
195 v.ValidRepoName(edit, old_data),
196 v.ValidPerms()]
196 v.ValidPerms()]
197 return _RepoForm
197 return _RepoForm
198
198
199
199
200 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
200 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
201 repo_groups=[], landing_revs=[]):
201 repo_groups=[], landing_revs=[]):
202 class _RepoForkForm(formencode.Schema):
202 class _RepoForkForm(formencode.Schema):
203 allow_extra_fields = True
203 allow_extra_fields = True
204 filter_extra_fields = False
204 filter_extra_fields = False
205 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
205 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
206 v.SlugifyName())
206 v.SlugifyName())
207 repo_group = All(v.CanWriteGroup(),
207 repo_group = All(v.CanWriteGroup(),
208 v.OneOf(repo_groups, hideList=True))
208 v.OneOf(repo_groups, hideList=True))
209 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
209 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
210 description = v.UnicodeString(strip=True, min=1, not_empty=True)
210 description = v.UnicodeString(strip=True, min=1, not_empty=True)
211 private = v.StringBoolean(if_missing=False)
211 private = v.StringBoolean(if_missing=False)
212 copy_permissions = v.StringBoolean(if_missing=False)
212 copy_permissions = v.StringBoolean(if_missing=False)
213 update_after_clone = v.StringBoolean(if_missing=False)
213 update_after_clone = v.StringBoolean(if_missing=False)
214 fork_parent_id = v.UnicodeString()
214 fork_parent_id = v.UnicodeString()
215 chained_validators = [v.ValidForkName(edit, old_data)]
215 chained_validators = [v.ValidForkName(edit, old_data)]
216 landing_rev = v.OneOf(landing_revs, hideList=True)
216 landing_rev = v.OneOf(landing_revs, hideList=True)
217
217
218 return _RepoForkForm
218 return _RepoForkForm
219
219
220
220
221 def RepoSettingsForm(edit=False, old_data={},
221 def RepoSettingsForm(edit=False, old_data={},
222 supported_backends=BACKENDS.keys(), repo_groups=[],
222 supported_backends=BACKENDS.keys(), repo_groups=[],
223 landing_revs=[]):
223 landing_revs=[]):
224 class _RepoForm(formencode.Schema):
224 class _RepoForm(formencode.Schema):
225 allow_extra_fields = True
225 allow_extra_fields = True
226 filter_extra_fields = False
226 filter_extra_fields = False
227 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
227 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
228 v.SlugifyName())
228 v.SlugifyName())
229 description = v.UnicodeString(strip=True, min=1, not_empty=True)
229 description = v.UnicodeString(strip=True, min=1, not_empty=True)
230 repo_group = v.OneOf(repo_groups, hideList=True)
230 repo_group = v.OneOf(repo_groups, hideList=True)
231 private = v.StringBoolean(if_missing=False)
231 private = v.StringBoolean(if_missing=False)
232 landing_rev = v.OneOf(landing_revs, hideList=True)
232 landing_rev = v.OneOf(landing_revs, hideList=True)
233 chained_validators = [v.ValidRepoName(edit, old_data), v.ValidPerms(),
233 chained_validators = [v.ValidRepoName(edit, old_data), v.ValidPerms(),
234 v.ValidSettings()]
234 v.ValidSettings()]
235 return _RepoForm
235 return _RepoForm
236
236
237
237
238 def ApplicationSettingsForm():
238 def ApplicationSettingsForm():
239 class _ApplicationSettingsForm(formencode.Schema):
239 class _ApplicationSettingsForm(formencode.Schema):
240 allow_extra_fields = True
240 allow_extra_fields = True
241 filter_extra_fields = False
241 filter_extra_fields = False
242 rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True)
242 rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True)
243 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
243 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
244 rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
244 rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
245
245
246 return _ApplicationSettingsForm
246 return _ApplicationSettingsForm
247
247
248
248
249 def ApplicationVisualisationForm():
249 def ApplicationVisualisationForm():
250 class _ApplicationVisualisationForm(formencode.Schema):
250 class _ApplicationVisualisationForm(formencode.Schema):
251 allow_extra_fields = True
251 allow_extra_fields = True
252 filter_extra_fields = False
252 filter_extra_fields = False
253 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
253 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
254 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
254 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
255 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
255 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
256
256
257 rhodecode_lightweight_dashboard = v.StringBoolean(if_missing=False)
257 rhodecode_lightweight_dashboard = v.StringBoolean(if_missing=False)
258 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
258
259
259 return _ApplicationVisualisationForm
260 return _ApplicationVisualisationForm
260
261
261
262
262 def ApplicationUiSettingsForm():
263 def ApplicationUiSettingsForm():
263 class _ApplicationUiSettingsForm(formencode.Schema):
264 class _ApplicationUiSettingsForm(formencode.Schema):
264 allow_extra_fields = True
265 allow_extra_fields = True
265 filter_extra_fields = False
266 filter_extra_fields = False
266 web_push_ssl = v.StringBoolean(if_missing=False)
267 web_push_ssl = v.StringBoolean(if_missing=False)
267 paths_root_path = All(
268 paths_root_path = All(
268 v.ValidPath(),
269 v.ValidPath(),
269 v.UnicodeString(strip=True, min=1, not_empty=True)
270 v.UnicodeString(strip=True, min=1, not_empty=True)
270 )
271 )
271 hooks_changegroup_update = v.StringBoolean(if_missing=False)
272 hooks_changegroup_update = v.StringBoolean(if_missing=False)
272 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
273 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
273 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
274 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
274 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
275 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
275
276
276 extensions_largefiles = v.StringBoolean(if_missing=False)
277 extensions_largefiles = v.StringBoolean(if_missing=False)
277 extensions_hgsubversion = v.StringBoolean(if_missing=False)
278 extensions_hgsubversion = v.StringBoolean(if_missing=False)
278 extensions_hggit = v.StringBoolean(if_missing=False)
279 extensions_hggit = v.StringBoolean(if_missing=False)
279
280
280 return _ApplicationUiSettingsForm
281 return _ApplicationUiSettingsForm
281
282
282
283
283 def DefaultPermissionsForm(perms_choices, register_choices, create_choices,
284 def DefaultPermissionsForm(perms_choices, register_choices, create_choices,
284 fork_choices):
285 fork_choices):
285 class _DefaultPermissionsForm(formencode.Schema):
286 class _DefaultPermissionsForm(formencode.Schema):
286 allow_extra_fields = True
287 allow_extra_fields = True
287 filter_extra_fields = True
288 filter_extra_fields = True
288 overwrite_default = v.StringBoolean(if_missing=False)
289 overwrite_default = v.StringBoolean(if_missing=False)
289 anonymous = v.StringBoolean(if_missing=False)
290 anonymous = v.StringBoolean(if_missing=False)
290 default_perm = v.OneOf(perms_choices)
291 default_perm = v.OneOf(perms_choices)
291 default_register = v.OneOf(register_choices)
292 default_register = v.OneOf(register_choices)
292 default_create = v.OneOf(create_choices)
293 default_create = v.OneOf(create_choices)
293 default_fork = v.OneOf(fork_choices)
294 default_fork = v.OneOf(fork_choices)
294
295
295 return _DefaultPermissionsForm
296 return _DefaultPermissionsForm
296
297
297
298
298 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
299 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
299 tls_kind_choices):
300 tls_kind_choices):
300 class _LdapSettingsForm(formencode.Schema):
301 class _LdapSettingsForm(formencode.Schema):
301 allow_extra_fields = True
302 allow_extra_fields = True
302 filter_extra_fields = True
303 filter_extra_fields = True
303 #pre_validators = [LdapLibValidator]
304 #pre_validators = [LdapLibValidator]
304 ldap_active = v.StringBoolean(if_missing=False)
305 ldap_active = v.StringBoolean(if_missing=False)
305 ldap_host = v.UnicodeString(strip=True,)
306 ldap_host = v.UnicodeString(strip=True,)
306 ldap_port = v.Number(strip=True,)
307 ldap_port = v.Number(strip=True,)
307 ldap_tls_kind = v.OneOf(tls_kind_choices)
308 ldap_tls_kind = v.OneOf(tls_kind_choices)
308 ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
309 ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
309 ldap_dn_user = v.UnicodeString(strip=True,)
310 ldap_dn_user = v.UnicodeString(strip=True,)
310 ldap_dn_pass = v.UnicodeString(strip=True,)
311 ldap_dn_pass = v.UnicodeString(strip=True,)
311 ldap_base_dn = v.UnicodeString(strip=True,)
312 ldap_base_dn = v.UnicodeString(strip=True,)
312 ldap_filter = v.UnicodeString(strip=True,)
313 ldap_filter = v.UnicodeString(strip=True,)
313 ldap_search_scope = v.OneOf(search_scope_choices)
314 ldap_search_scope = v.OneOf(search_scope_choices)
314 ldap_attr_login = All(
315 ldap_attr_login = All(
315 v.AttrLoginValidator(),
316 v.AttrLoginValidator(),
316 v.UnicodeString(strip=True,)
317 v.UnicodeString(strip=True,)
317 )
318 )
318 ldap_attr_firstname = v.UnicodeString(strip=True,)
319 ldap_attr_firstname = v.UnicodeString(strip=True,)
319 ldap_attr_lastname = v.UnicodeString(strip=True,)
320 ldap_attr_lastname = v.UnicodeString(strip=True,)
320 ldap_attr_email = v.UnicodeString(strip=True,)
321 ldap_attr_email = v.UnicodeString(strip=True,)
321
322
322 return _LdapSettingsForm
323 return _LdapSettingsForm
323
324
324
325
325 def UserExtraEmailForm():
326 def UserExtraEmailForm():
326 class _UserExtraEmailForm(formencode.Schema):
327 class _UserExtraEmailForm(formencode.Schema):
327 email = All(v.UniqSystemEmail(), v.Email)
328 email = All(v.UniqSystemEmail(), v.Email)
328
329
329 return _UserExtraEmailForm
330 return _UserExtraEmailForm
330
331
331
332
332 def PullRequestForm(repo_id):
333 def PullRequestForm(repo_id):
333 class _PullRequestForm(formencode.Schema):
334 class _PullRequestForm(formencode.Schema):
334 allow_extra_fields = True
335 allow_extra_fields = True
335 filter_extra_fields = True
336 filter_extra_fields = True
336
337
337 user = v.UnicodeString(strip=True, required=True)
338 user = v.UnicodeString(strip=True, required=True)
338 org_repo = v.UnicodeString(strip=True, required=True)
339 org_repo = v.UnicodeString(strip=True, required=True)
339 org_ref = v.UnicodeString(strip=True, required=True)
340 org_ref = v.UnicodeString(strip=True, required=True)
340 other_repo = v.UnicodeString(strip=True, required=True)
341 other_repo = v.UnicodeString(strip=True, required=True)
341 other_ref = v.UnicodeString(strip=True, required=True)
342 other_ref = v.UnicodeString(strip=True, required=True)
342 revisions = All(v.NotReviewedRevisions(repo_id)(), v.UniqueList(not_empty=True))
343 revisions = All(v.NotReviewedRevisions(repo_id)(), v.UniqueList(not_empty=True))
343 review_members = v.UniqueList(not_empty=True)
344 review_members = v.UniqueList(not_empty=True)
344
345
345 pullrequest_title = v.UnicodeString(strip=True, required=True, min=3)
346 pullrequest_title = v.UnicodeString(strip=True, required=True, min=3)
346 pullrequest_desc = v.UnicodeString(strip=True, required=False)
347 pullrequest_desc = v.UnicodeString(strip=True, required=False)
347
348
348 return _PullRequestForm
349 return _PullRequestForm
@@ -1,338 +1,342
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Settings administration')} - ${c.rhodecode_name}
5 ${_('Settings administration')} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Settings')}
9 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Settings')}
10 </%def>
10 </%def>
11
11
12 <%def name="page_nav()">
12 <%def name="page_nav()">
13 ${self.menu('admin')}
13 ${self.menu('admin')}
14 </%def>
14 </%def>
15
15
16 <%def name="main()">
16 <%def name="main()">
17 <div class="box">
17 <div class="box">
18 <!-- box / title -->
18 <!-- box / title -->
19 <div class="title">
19 <div class="title">
20 ${self.breadcrumbs()}
20 ${self.breadcrumbs()}
21 </div>
21 </div>
22 <!-- end box / title -->
22 <!-- end box / title -->
23
23
24 <h3>${_('Remap and rescan repositories')}</h3>
24 <h3>${_('Remap and rescan repositories')}</h3>
25 ${h.form(url('admin_setting', setting_id='mapping'),method='put')}
25 ${h.form(url('admin_setting', setting_id='mapping'),method='put')}
26 <div class="form">
26 <div class="form">
27 <!-- fields -->
27 <!-- fields -->
28
28
29 <div class="fields">
29 <div class="fields">
30 <div class="field">
30 <div class="field">
31 <div class="label label-checkbox">
31 <div class="label label-checkbox">
32 <label for="destroy">${_('rescan option')}:</label>
32 <label for="destroy">${_('rescan option')}:</label>
33 </div>
33 </div>
34 <div class="checkboxes">
34 <div class="checkboxes">
35 <div class="checkbox">
35 <div class="checkbox">
36 ${h.checkbox('destroy',True)}
36 ${h.checkbox('destroy',True)}
37 <label for="destroy">
37 <label for="destroy">
38 <span class="tooltip" title="${h.tooltip(_('In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it.'))}">
38 <span class="tooltip" title="${h.tooltip(_('In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it.'))}">
39 ${_('destroy old data')}</span> </label>
39 ${_('destroy old data')}</span> </label>
40 </div>
40 </div>
41 <span class="help-block">${_('Rescan repositories location for new repositories. Also deletes obsolete if `destroy` flag is checked ')}</span>
41 <span class="help-block">${_('Rescan repositories location for new repositories. Also deletes obsolete if `destroy` flag is checked ')}</span>
42 </div>
42 </div>
43 </div>
43 </div>
44
44
45 <div class="buttons">
45 <div class="buttons">
46 ${h.submit('rescan',_('Rescan repositories'),class_="ui-btn large")}
46 ${h.submit('rescan',_('Rescan repositories'),class_="ui-btn large")}
47 </div>
47 </div>
48 </div>
48 </div>
49 </div>
49 </div>
50 ${h.end_form()}
50 ${h.end_form()}
51
51
52 <h3>${_('Whoosh indexing')}</h3>
52 <h3>${_('Whoosh indexing')}</h3>
53 ${h.form(url('admin_setting', setting_id='whoosh'),method='put')}
53 ${h.form(url('admin_setting', setting_id='whoosh'),method='put')}
54 <div class="form">
54 <div class="form">
55 <!-- fields -->
55 <!-- fields -->
56
56
57 <div class="fields">
57 <div class="fields">
58 <div class="field">
58 <div class="field">
59 <div class="label label-checkbox">
59 <div class="label label-checkbox">
60 <label>${_('index build option')}:</label>
60 <label>${_('index build option')}:</label>
61 </div>
61 </div>
62 <div class="checkboxes">
62 <div class="checkboxes">
63 <div class="checkbox">
63 <div class="checkbox">
64 ${h.checkbox('full_index',True)}
64 ${h.checkbox('full_index',True)}
65 <label for="full_index">${_('build from scratch')}</label>
65 <label for="full_index">${_('build from scratch')}</label>
66 </div>
66 </div>
67 </div>
67 </div>
68 </div>
68 </div>
69
69
70 <div class="buttons">
70 <div class="buttons">
71 ${h.submit('reindex',_('Reindex'),class_="ui-btn large")}
71 ${h.submit('reindex',_('Reindex'),class_="ui-btn large")}
72 </div>
72 </div>
73 </div>
73 </div>
74 </div>
74 </div>
75 ${h.end_form()}
75 ${h.end_form()}
76
76
77 <h3>${_('Global application settings')}</h3>
77 <h3>${_('Global application settings')}</h3>
78 ${h.form(url('admin_setting', setting_id='global'),method='put')}
78 ${h.form(url('admin_setting', setting_id='global'),method='put')}
79 <div class="form">
79 <div class="form">
80 <!-- fields -->
80 <!-- fields -->
81
81
82 <div class="fields">
82 <div class="fields">
83
83
84 <div class="field">
84 <div class="field">
85 <div class="label">
85 <div class="label">
86 <label for="rhodecode_title">${_('Application name')}:</label>
86 <label for="rhodecode_title">${_('Application name')}:</label>
87 </div>
87 </div>
88 <div class="input">
88 <div class="input">
89 ${h.text('rhodecode_title',size=30)}
89 ${h.text('rhodecode_title',size=30)}
90 </div>
90 </div>
91 </div>
91 </div>
92
92
93 <div class="field">
93 <div class="field">
94 <div class="label">
94 <div class="label">
95 <label for="rhodecode_realm">${_('Realm text')}:</label>
95 <label for="rhodecode_realm">${_('Realm text')}:</label>
96 </div>
96 </div>
97 <div class="input">
97 <div class="input">
98 ${h.text('rhodecode_realm',size=30)}
98 ${h.text('rhodecode_realm',size=30)}
99 </div>
99 </div>
100 </div>
100 </div>
101
101
102 <div class="field">
102 <div class="field">
103 <div class="label">
103 <div class="label">
104 <label for="rhodecode_ga_code">${_('GA code')}:</label>
104 <label for="rhodecode_ga_code">${_('GA code')}:</label>
105 </div>
105 </div>
106 <div class="input">
106 <div class="input">
107 ${h.text('rhodecode_ga_code',size=30)}
107 ${h.text('rhodecode_ga_code',size=30)}
108 </div>
108 </div>
109 </div>
109 </div>
110
110
111 <div class="buttons">
111 <div class="buttons">
112 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
112 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
113 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
113 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
114 </div>
114 </div>
115 </div>
115 </div>
116 </div>
116 </div>
117 ${h.end_form()}
117 ${h.end_form()}
118
118
119 <h3>${_('Visualisation settings')}</h3>
119 <h3>${_('Visualisation settings')}</h3>
120 ${h.form(url('admin_setting', setting_id='visual'),method='put')}
120 ${h.form(url('admin_setting', setting_id='visual'),method='put')}
121 <div class="form">
121 <div class="form">
122 <!-- fields -->
122 <!-- fields -->
123
123
124 <div class="fields">
124 <div class="fields">
125 <div class="field">
125 <div class="field">
126 <div class="label label-checkbox">
126 <div class="label label-checkbox">
127 <label>${_('General')}:</label>
127 <label>${_('General')}:</label>
128 </div>
128 </div>
129 <div class="checkboxes">
129 <div class="checkboxes">
130 <div class="checkbox">
130 <div class="checkbox">
131 ${h.checkbox('rhodecode_lightweight_dashboard','True')}
131 ${h.checkbox('rhodecode_lightweight_dashboard','True')}
132 <label for="rhodecode_lightweight_dashboard">${_('Use lightweight dashboard')}</label>
132 <label for="rhodecode_lightweight_dashboard">${_('Use lightweight dashboard')}</label>
133 </div>
133 </div>
134 <div class="checkbox">
135 ${h.checkbox('rhodecode_lightweight_journal','True')}
136 <label for="rhodecode_lightweight_journal">${_('Use lightweight journal')}</label>
137 </div>
134 </div>
138 </div>
135 </div>
139 </div>
136
140
137 <div class="field">
141 <div class="field">
138 <div class="label label-checkbox">
142 <div class="label label-checkbox">
139 <label>${_('Icons')}:</label>
143 <label>${_('Icons')}:</label>
140 </div>
144 </div>
141 <div class="checkboxes">
145 <div class="checkboxes">
142 <div class="checkbox">
146 <div class="checkbox">
143 ${h.checkbox('rhodecode_show_public_icon','True')}
147 ${h.checkbox('rhodecode_show_public_icon','True')}
144 <label for="rhodecode_show_public_icon">${_('Show public repo icon on repositories')}</label>
148 <label for="rhodecode_show_public_icon">${_('Show public repo icon on repositories')}</label>
145 </div>
149 </div>
146 <div class="checkbox">
150 <div class="checkbox">
147 ${h.checkbox('rhodecode_show_private_icon','True')}
151 ${h.checkbox('rhodecode_show_private_icon','True')}
148 <label for="rhodecode_show_private_icon">${_('Show private repo icon on repositories')}</label>
152 <label for="rhodecode_show_private_icon">${_('Show private repo icon on repositories')}</label>
149 </div>
153 </div>
150 </div>
154 </div>
151 </div>
155 </div>
152
156
153 <div class="field">
157 <div class="field">
154 <div class="label label-checkbox">
158 <div class="label label-checkbox">
155 <label>${_('Meta-Tagging')}:</label>
159 <label>${_('Meta-Tagging')}:</label>
156 </div>
160 </div>
157 <div class="checkboxes">
161 <div class="checkboxes">
158 <div class="checkbox">
162 <div class="checkbox">
159 ${h.checkbox('rhodecode_stylify_metatags','True')}
163 ${h.checkbox('rhodecode_stylify_metatags','True')}
160 <label for="rhodecode_stylify_metatags">${_('Stylify recognised metatags:')}</label>
164 <label for="rhodecode_stylify_metatags">${_('Stylify recognised metatags:')}</label>
161 </div>
165 </div>
162 <div style="padding-left: 20px;">
166 <div style="padding-left: 20px;">
163 <ul> <!-- Fix style here -->
167 <ul> <!-- Fix style here -->
164 <li>[featured] <span class="metatag" tag="featured">featured</span></li>
168 <li>[featured] <span class="metatag" tag="featured">featured</span></li>
165 <li>[stale] <span class="metatag" tag="stale">stale</span></li>
169 <li>[stale] <span class="metatag" tag="stale">stale</span></li>
166 <li>[dead] <span class="metatag" tag="dead">dead</span></li>
170 <li>[dead] <span class="metatag" tag="dead">dead</span></li>
167 <li>[lang =&gt; lang] <span class="metatag" tag="lang" >lang</span></li>
171 <li>[lang =&gt; lang] <span class="metatag" tag="lang" >lang</span></li>
168 <li>[license =&gt; License] <span class="metatag" tag="license"><a href="http://www.opensource.org/licenses/License" >License</a></span></li>
172 <li>[license =&gt; License] <span class="metatag" tag="license"><a href="http://www.opensource.org/licenses/License" >License</a></span></li>
169 <li>[requires =&gt; Repo] <span class="metatag" tag="requires" >requires =&gt; <a href="#" >Repo</a></span></li>
173 <li>[requires =&gt; Repo] <span class="metatag" tag="requires" >requires =&gt; <a href="#" >Repo</a></span></li>
170 <li>[recommends =&gt; Repo] <span class="metatag" tag="recommends" >recommends =&gt; <a href="#" >Repo</a></span></li>
174 <li>[recommends =&gt; Repo] <span class="metatag" tag="recommends" >recommends =&gt; <a href="#" >Repo</a></span></li>
171 <li>[see =&gt; URI] <span class="metatag" tag="see">see =&gt; <a href="#">URI</a> </span></li>
175 <li>[see =&gt; URI] <span class="metatag" tag="see">see =&gt; <a href="#">URI</a> </span></li>
172 </ul>
176 </ul>
173 </div>
177 </div>
174 </div>
178 </div>
175 </div>
179 </div>
176
180
177 <div class="buttons">
181 <div class="buttons">
178 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
182 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
179 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
183 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
180 </div>
184 </div>
181
185
182 </div>
186 </div>
183 </div>
187 </div>
184 ${h.end_form()}
188 ${h.end_form()}
185
189
186
190
187 <h3>${_('VCS settings')}</h3>
191 <h3>${_('VCS settings')}</h3>
188 ${h.form(url('admin_setting', setting_id='vcs'),method='put')}
192 ${h.form(url('admin_setting', setting_id='vcs'),method='put')}
189 <div class="form">
193 <div class="form">
190 <!-- fields -->
194 <!-- fields -->
191
195
192 <div class="fields">
196 <div class="fields">
193
197
194 <div class="field">
198 <div class="field">
195 <div class="label label-checkbox">
199 <div class="label label-checkbox">
196 <label>${_('Web')}:</label>
200 <label>${_('Web')}:</label>
197 </div>
201 </div>
198 <div class="checkboxes">
202 <div class="checkboxes">
199 <div class="checkbox">
203 <div class="checkbox">
200 ${h.checkbox('web_push_ssl', 'True')}
204 ${h.checkbox('web_push_ssl', 'True')}
201 <label for="web_push_ssl">${_('require ssl for vcs operations')}</label>
205 <label for="web_push_ssl">${_('require ssl for vcs operations')}</label>
202 </div>
206 </div>
203 <span class="help-block">${_('RhodeCode will require SSL for pushing or pulling. If SSL is missing it will return HTTP Error 406: Not Acceptable')}</span>
207 <span class="help-block">${_('RhodeCode will require SSL for pushing or pulling. If SSL is missing it will return HTTP Error 406: Not Acceptable')}</span>
204 </div>
208 </div>
205 </div>
209 </div>
206
210
207 <div class="field">
211 <div class="field">
208 <div class="label label-checkbox">
212 <div class="label label-checkbox">
209 <label>${_('Hooks')}:</label>
213 <label>${_('Hooks')}:</label>
210 </div>
214 </div>
211 <div class="checkboxes">
215 <div class="checkboxes">
212 <div class="checkbox">
216 <div class="checkbox">
213 ${h.checkbox('hooks_changegroup_update','True')}
217 ${h.checkbox('hooks_changegroup_update','True')}
214 <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
218 <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
215 </div>
219 </div>
216 <div class="checkbox">
220 <div class="checkbox">
217 ${h.checkbox('hooks_changegroup_repo_size','True')}
221 ${h.checkbox('hooks_changegroup_repo_size','True')}
218 <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
222 <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
219 </div>
223 </div>
220 <div class="checkbox">
224 <div class="checkbox">
221 ${h.checkbox('hooks_changegroup_push_logger','True')}
225 ${h.checkbox('hooks_changegroup_push_logger','True')}
222 <label for="hooks_changegroup_push_logger">${_('Log user push commands')}</label>
226 <label for="hooks_changegroup_push_logger">${_('Log user push commands')}</label>
223 </div>
227 </div>
224 <div class="checkbox">
228 <div class="checkbox">
225 ${h.checkbox('hooks_outgoing_pull_logger','True')}
229 ${h.checkbox('hooks_outgoing_pull_logger','True')}
226 <label for="hooks_outgoing_pull_logger">${_('Log user pull commands')}</label>
230 <label for="hooks_outgoing_pull_logger">${_('Log user pull commands')}</label>
227 </div>
231 </div>
228 </div>
232 </div>
229 <div class="input" style="margin-top:10px">
233 <div class="input" style="margin-top:10px">
230 ${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'),class_="ui-btn")}
234 ${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'),class_="ui-btn")}
231 </div>
235 </div>
232 </div>
236 </div>
233 <div class="field">
237 <div class="field">
234 <div class="label label-checkbox">
238 <div class="label label-checkbox">
235 <label>${_('Mercurial Extensions')}:</label>
239 <label>${_('Mercurial Extensions')}:</label>
236 </div>
240 </div>
237 <div class="checkboxes">
241 <div class="checkboxes">
238 <div class="checkbox">
242 <div class="checkbox">
239 ${h.checkbox('extensions_largefiles','True')}
243 ${h.checkbox('extensions_largefiles','True')}
240 <label for="extensions_hgsubversion">${_('largefiles extensions')}</label>
244 <label for="extensions_hgsubversion">${_('largefiles extensions')}</label>
241 </div>
245 </div>
242 <div class="checkbox">
246 <div class="checkbox">
243 ${h.checkbox('extensions_hgsubversion','True')}
247 ${h.checkbox('extensions_hgsubversion','True')}
244 <label for="extensions_hgsubversion">${_('hgsubversion extensions')}</label>
248 <label for="extensions_hgsubversion">${_('hgsubversion extensions')}</label>
245 </div>
249 </div>
246 <span class="help-block">${_('Requires hgsubversion library installed. Allows clonning from svn remote locations')}</span>
250 <span class="help-block">${_('Requires hgsubversion library installed. Allows clonning from svn remote locations')}</span>
247 ##<div class="checkbox">
251 ##<div class="checkbox">
248 ## ${h.checkbox('extensions_hggit','True')}
252 ## ${h.checkbox('extensions_hggit','True')}
249 ## <label for="extensions_hggit">${_('hg-git extensions')}</label>
253 ## <label for="extensions_hggit">${_('hg-git extensions')}</label>
250 ##</div>
254 ##</div>
251 ##<span class="help-block">${_('Requires hg-git library installed. Allows clonning from git remote locations')}</span>
255 ##<span class="help-block">${_('Requires hg-git library installed. Allows clonning from git remote locations')}</span>
252 </div>
256 </div>
253 </div>
257 </div>
254 <div class="field">
258 <div class="field">
255 <div class="label">
259 <div class="label">
256 <label for="paths_root_path">${_('Repositories location')}:</label>
260 <label for="paths_root_path">${_('Repositories location')}:</label>
257 </div>
261 </div>
258 <div class="input">
262 <div class="input">
259 ${h.text('paths_root_path',size=30,readonly="readonly")}
263 ${h.text('paths_root_path',size=30,readonly="readonly")}
260 <span id="path_unlock" class="tooltip"
264 <span id="path_unlock" class="tooltip"
261 title="${h.tooltip(_('This a crucial application setting. If you are really sure you need to change this, you must restart application in order to make this setting take effect. Click this label to unlock.'))}">
265 title="${h.tooltip(_('This a crucial application setting. If you are really sure you need to change this, you must restart application in order to make this setting take effect. Click this label to unlock.'))}">
262 ${_('unlock')}</span>
266 ${_('unlock')}</span>
263 <span class="help-block">${_('Location where repositories are stored. After changing this value a restart, and rescan is required')}</span>
267 <span class="help-block">${_('Location where repositories are stored. After changing this value a restart, and rescan is required')}</span>
264 </div>
268 </div>
265 </div>
269 </div>
266
270
267 <div class="buttons">
271 <div class="buttons">
268 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
272 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
269 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
273 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
270 </div>
274 </div>
271 </div>
275 </div>
272 </div>
276 </div>
273 ${h.end_form()}
277 ${h.end_form()}
274
278
275 <script type="text/javascript">
279 <script type="text/javascript">
276 YAHOO.util.Event.onDOMReady(function(){
280 YAHOO.util.Event.onDOMReady(function(){
277 YAHOO.util.Event.addListener('path_unlock','click',function(){
281 YAHOO.util.Event.addListener('path_unlock','click',function(){
278 YAHOO.util.Dom.get('paths_root_path').removeAttribute('readonly');
282 YAHOO.util.Dom.get('paths_root_path').removeAttribute('readonly');
279 });
283 });
280 });
284 });
281 </script>
285 </script>
282
286
283 <h3>${_('Test Email')}</h3>
287 <h3>${_('Test Email')}</h3>
284 ${h.form(url('admin_setting', setting_id='email'),method='put')}
288 ${h.form(url('admin_setting', setting_id='email'),method='put')}
285 <div class="form">
289 <div class="form">
286 <!-- fields -->
290 <!-- fields -->
287
291
288 <div class="fields">
292 <div class="fields">
289 <div class="field">
293 <div class="field">
290 <div class="label">
294 <div class="label">
291 <label for="test_email">${_('Email to')}:</label>
295 <label for="test_email">${_('Email to')}:</label>
292 </div>
296 </div>
293 <div class="input">
297 <div class="input">
294 ${h.text('test_email',size=30)}
298 ${h.text('test_email',size=30)}
295 </div>
299 </div>
296 </div>
300 </div>
297
301
298 <div class="buttons">
302 <div class="buttons">
299 ${h.submit('send',_('Send'),class_="ui-btn large")}
303 ${h.submit('send',_('Send'),class_="ui-btn large")}
300 </div>
304 </div>
301 </div>
305 </div>
302 </div>
306 </div>
303 ${h.end_form()}
307 ${h.end_form()}
304
308
305 <h3>${_('System Info and Packages')}</h3>
309 <h3>${_('System Info and Packages')}</h3>
306 <div class="form">
310 <div class="form">
307 <div>
311 <div>
308 <h5 id="expand_modules" style="cursor: pointer">&darr; ${_('show')} &darr;</h5>
312 <h5 id="expand_modules" style="cursor: pointer">&darr; ${_('show')} &darr;</h5>
309 </div>
313 </div>
310 <div id="expand_modules_table" style="display:none">
314 <div id="expand_modules_table" style="display:none">
311 <h5>Python - ${c.py_version}</h5>
315 <h5>Python - ${c.py_version}</h5>
312 <h5>System - ${c.platform}</h5>
316 <h5>System - ${c.platform}</h5>
313
317
314 <table class="table" style="margin:0px 0px 0px 20px">
318 <table class="table" style="margin:0px 0px 0px 20px">
315 <colgroup>
319 <colgroup>
316 <col style="width:220px">
320 <col style="width:220px">
317 </colgroup>
321 </colgroup>
318 <tbody>
322 <tbody>
319 %for key, value in c.modules:
323 %for key, value in c.modules:
320 <tr>
324 <tr>
321 <th style="text-align: right;padding-right:5px;">${key}</th>
325 <th style="text-align: right;padding-right:5px;">${key}</th>
322 <td>${value}</td>
326 <td>${value}</td>
323 </tr>
327 </tr>
324 %endfor
328 %endfor
325 </tbody>
329 </tbody>
326 </table>
330 </table>
327 </div>
331 </div>
328 </div>
332 </div>
329
333
330 <script type="text/javascript">
334 <script type="text/javascript">
331 YUE.on('expand_modules','click',function(e){
335 YUE.on('expand_modules','click',function(e){
332 YUD.setStyle('expand_modules_table','display','');
336 YUD.setStyle('expand_modules_table','display','');
333 YUD.setStyle('expand_modules','display','none');
337 YUD.setStyle('expand_modules','display','none');
334 })
338 })
335 </script>
339 </script>
336
340
337 </div>
341 </div>
338 </%def>
342 </%def>
General Comments 0
You need to be logged in to leave comments. Login now