##// END OF EJS Templates
Added hooks managment into application settings
marcink -
r395:e8af467b default
parent child Browse files
Show More
@@ -1,265 +1,280 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # settings controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on July 14, 2010
22 22 settings controller for pylons
23 23 @author: marcink
24 24 """
25 25 from formencode import htmlfill
26 26 from pylons import request, session, tmpl_context as c, url, app_globals as g, \
27 27 config
28 28 from pylons.controllers.util import abort, redirect
29 29 from pylons.i18n.translation import _
30 30 from pylons_app.lib import helpers as h
31 31 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator, \
32 32 HasPermissionAnyDecorator
33 33 from pylons_app.lib.base import BaseController, render
34 34 from pylons_app.lib.utils import repo2db_mapper, invalidate_cache, \
35 35 set_hg_app_config, get_hg_settings, get_hg_ui_settings, make_ui
36 36 from pylons_app.model.db import User, UserLog, HgAppSettings, HgAppUi
37 37 from pylons_app.model.forms import UserForm, ApplicationSettingsForm, \
38 38 ApplicationUiSettingsForm
39 39 from pylons_app.model.hg_model import HgModel
40 40 from pylons_app.model.user_model import UserModel
41 41 import formencode
42 42 import logging
43 43 import traceback
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47
48 48 class SettingsController(BaseController):
49 49 """REST Controller styled on the Atom Publishing Protocol"""
50 50 # To properly map this controller, ensure your config/routing.py
51 51 # file has a resource setup:
52 52 # map.resource('setting', 'settings', controller='admin/settings',
53 53 # path_prefix='/admin', name_prefix='admin_')
54 54
55 55
56 56 @LoginRequired()
57 57 def __before__(self):
58 58 c.admin_user = session.get('admin_user')
59 59 c.admin_username = session.get('admin_username')
60 60 super(SettingsController, self).__before__()
61 61
62 62
63 63 @HasPermissionAllDecorator('hg.admin')
64 64 def index(self, format='html'):
65 65 """GET /admin/settings: All items in the collection"""
66 66 # url('admin_settings')
67 67
68 68 defaults = get_hg_settings()
69 69 defaults.update(get_hg_ui_settings())
70 70 return htmlfill.render(
71 71 render('admin/settings/settings.html'),
72 72 defaults=defaults,
73 73 encoding="UTF-8",
74 74 force_defaults=False
75 75 )
76 76
77 77 @HasPermissionAllDecorator('hg.admin')
78 78 def create(self):
79 79 """POST /admin/settings: Create a new item"""
80 80 # url('admin_settings')
81 81
82 82 @HasPermissionAllDecorator('hg.admin')
83 83 def new(self, format='html'):
84 84 """GET /admin/settings/new: Form to create a new item"""
85 85 # url('admin_new_setting')
86 86
87 87 @HasPermissionAllDecorator('hg.admin')
88 88 def update(self, setting_id):
89 89 """PUT /admin/settings/setting_id: Update an existing item"""
90 90 # Forms posted to this method should contain a hidden field:
91 91 # <input type="hidden" name="_method" value="PUT" />
92 92 # Or using helpers:
93 93 # h.form(url('admin_setting', setting_id=ID),
94 94 # method='put')
95 95 # url('admin_setting', setting_id=ID)
96 96 if setting_id == 'mapping':
97 97 rm_obsolete = request.POST.get('destroy', False)
98 98 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
99 99
100 100 initial = HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
101 101 repo2db_mapper(initial, rm_obsolete)
102 102 invalidate_cache('cached_repo_list')
103 103 h.flash(_('Repositories sucessfully rescanned'), category='success')
104 104
105 105 if setting_id == 'global':
106 106
107 107 application_form = ApplicationSettingsForm()()
108 108 try:
109 109 form_result = application_form.to_python(dict(request.POST))
110 110
111 111 try:
112 112 hgsettings1 = self.sa.query(HgAppSettings)\
113 113 .filter(HgAppSettings.app_settings_name == 'title').one()
114 114 hgsettings1.app_settings_value = form_result['hg_app_title']
115 115
116 116 hgsettings2 = self.sa.query(HgAppSettings)\
117 117 .filter(HgAppSettings.app_settings_name == 'realm').one()
118 118 hgsettings2.app_settings_value = form_result['hg_app_realm']
119 119
120 120
121 121 self.sa.add(hgsettings1)
122 122 self.sa.add(hgsettings2)
123 123 self.sa.commit()
124 124 set_hg_app_config(config)
125 125 h.flash(_('Updated application settings'),
126 126 category='success')
127 127
128 128 except:
129 129 log.error(traceback.format_exc())
130 130 h.flash(_('error occured during updating application settings'),
131 131 category='error')
132 132
133 133 self.sa.rollback()
134 134
135 135
136 136 except formencode.Invalid as errors:
137 137 return htmlfill.render(
138 138 render('admin/settings/settings.html'),
139 139 defaults=errors.value,
140 140 errors=errors.error_dict or {},
141 141 prefix_error=False,
142 142 encoding="UTF-8")
143 143
144 144 if setting_id == 'mercurial':
145 145 application_form = ApplicationUiSettingsForm()()
146 146 try:
147 147 form_result = application_form.to_python(dict(request.POST))
148 148
149 149 try:
150 150
151 151 hgsettings1 = self.sa.query(HgAppUi)\
152 152 .filter(HgAppUi.ui_key == 'push_ssl').one()
153 153 hgsettings1.ui_value = form_result['web_push_ssl']
154 154
155 155 hgsettings2 = self.sa.query(HgAppUi)\
156 156 .filter(HgAppUi.ui_key == '/').one()
157 157 hgsettings2.ui_value = form_result['paths_root_path']
158 158
159
160 #HOOKS
161 hgsettings3 = self.sa.query(HgAppUi)\
162 .filter(HgAppUi.ui_key == 'changegroup.update').one()
163 hgsettings3.ui_active = bool(form_result['hooks_changegroup_update'])
164
165 hgsettings4 = self.sa.query(HgAppUi)\
166 .filter(HgAppUi.ui_key == 'changegroup.repo_size').one()
167 hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size'])
168
169
170
171
159 172 self.sa.add(hgsettings1)
160 173 self.sa.add(hgsettings2)
174 self.sa.add(hgsettings3)
175 self.sa.add(hgsettings4)
161 176 self.sa.commit()
162 177
163 178 h.flash(_('Updated application settings'),
164 179 category='success')
165 180
166 181 except:
167 182 log.error(traceback.format_exc())
168 183 h.flash(_('error occured during updating application settings'),
169 184 category='error')
170 185
171 186 self.sa.rollback()
172 187
173 188
174 189 except formencode.Invalid as errors:
175 190 return htmlfill.render(
176 191 render('admin/settings/settings.html'),
177 192 defaults=errors.value,
178 193 errors=errors.error_dict or {},
179 194 prefix_error=False,
180 195 encoding="UTF-8")
181 196
182 197
183 198
184 199 return redirect(url('admin_settings'))
185 200
186 201 @HasPermissionAllDecorator('hg.admin')
187 202 def delete(self, setting_id):
188 203 """DELETE /admin/settings/setting_id: Delete an existing item"""
189 204 # Forms posted to this method should contain a hidden field:
190 205 # <input type="hidden" name="_method" value="DELETE" />
191 206 # Or using helpers:
192 207 # h.form(url('admin_setting', setting_id=ID),
193 208 # method='delete')
194 209 # url('admin_setting', setting_id=ID)
195 210
196 211 @HasPermissionAllDecorator('hg.admin')
197 212 def show(self, setting_id, format='html'):
198 213 """GET /admin/settings/setting_id: Show a specific item"""
199 214 # url('admin_setting', setting_id=ID)
200 215
201 216 @HasPermissionAllDecorator('hg.admin')
202 217 def edit(self, setting_id, format='html'):
203 218 """GET /admin/settings/setting_id/edit: Form to edit an existing item"""
204 219 # url('admin_edit_setting', setting_id=ID)
205 220
206 221
207 222 def my_account(self):
208 223 """
209 224 GET /_admin/my_account Displays info about my account
210 225 """
211 226 # url('admin_settings_my_account')
212 227 c.user = self.sa.query(User).get(c.hg_app_user.user_id)
213 228 if c.user.username == 'default':
214 229 h.flash(_("You can't edit this user since it's"
215 230 " crucial for entire application"), category='warning')
216 231 return redirect(url('users'))
217 232
218 233 defaults = c.user.__dict__
219 234 return htmlfill.render(
220 235 render('admin/users/user_edit_my_account.html'),
221 236 defaults=defaults,
222 237 encoding="UTF-8",
223 238 force_defaults=False
224 239 )
225 240
226 241 def my_account_update(self):
227 242 """PUT /_admin/my_account_update: Update an existing item"""
228 243 # Forms posted to this method should contain a hidden field:
229 244 # <input type="hidden" name="_method" value="PUT" />
230 245 # Or using helpers:
231 246 # h.form(url('admin_settings_my_account_update'),
232 247 # method='put')
233 248 # url('admin_settings_my_account_update', id=ID)
234 249 user_model = UserModel()
235 250 uid = c.hg_app_user.user_id
236 251 _form = UserForm(edit=True, old_data={'user_id':uid})()
237 252 form_result = {}
238 253 try:
239 254 form_result = _form.to_python(dict(request.POST))
240 255 user_model.update_my_account(uid, form_result)
241 256 h.flash(_('Your account was updated succesfully'), category='success')
242 257
243 258 except formencode.Invalid as errors:
244 259 #c.user = self.sa.query(User).get(c.hg_app_user.user_id)
245 260 return htmlfill.render(
246 261 render('admin/users/user_edit_my_account.html'),
247 262 defaults=errors.value,
248 263 errors=errors.error_dict or {},
249 264 prefix_error=False,
250 265 encoding="UTF-8")
251 266 except Exception:
252 267 log.error(traceback.format_exc())
253 268 h.flash(_('error occured during update of user %s') \
254 269 % form_result.get('username'), category='error')
255 270
256 271 return redirect(url('my_account'))
257 272
258 273 @HasPermissionAnyDecorator('repository.create', 'hg.admin')
259 274 def create_repository(self):
260 275 """GET /_admin/create_repository: Form to create a new item"""
261 276 new_repo = request.GET.get('repo', '')
262 277 c.new_repo = h.repo_name_slug(new_repo)
263 278
264 279 return render('admin/repos/repo_add_create_repository.html')
265 280
@@ -1,354 +1,364 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # Utilities for hg app
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 # This program is free software; you can redistribute it and/or
6 6 # modify it under the terms of the GNU General Public License
7 7 # as published by the Free Software Foundation; version 2
8 8 # of the License or (at your opinion) any later version of the license.
9 9 #
10 10 # This program is distributed in the hope that it will be useful,
11 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 # GNU General Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License
16 16 # along with this program; if not, write to the Free Software
17 17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 18 # MA 02110-1301, USA.
19 19
20 20 """
21 21 Created on April 18, 2010
22 22 Utilities for hg app
23 23 @author: marcink
24 24 """
25 25 from beaker.cache import cache_region
26 26 from mercurial import ui, config, hg
27 27 from mercurial.error import RepoError
28 28 from pylons_app.model import meta
29 29 from pylons_app.model.db import Repository, User, HgAppUi, HgAppSettings
30 30 from vcs.backends.base import BaseChangeset
31 31 from vcs.utils.lazy import LazyProperty
32 32 import logging
33 33 import os
34 34 log = logging.getLogger(__name__)
35 35
36 36
37 37 def get_repo_slug(request):
38 38 return request.environ['pylons.routes_dict'].get('repo_name')
39 39
40 40 def is_mercurial(environ):
41 41 """
42 42 Returns True if request's target is mercurial server - header
43 43 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
44 44 """
45 45 http_accept = environ.get('HTTP_ACCEPT')
46 46 if http_accept and http_accept.startswith('application/mercurial'):
47 47 return True
48 48 return False
49 49
50 50 def check_repo_dir(paths):
51 51 repos_path = paths[0][1].split('/')
52 52 if repos_path[-1] in ['*', '**']:
53 53 repos_path = repos_path[:-1]
54 54 if repos_path[0] != '/':
55 55 repos_path[0] = '/'
56 56 if not os.path.isdir(os.path.join(*repos_path)):
57 57 raise Exception('Not a valid repository in %s' % paths[0][1])
58 58
59 59 def check_repo_fast(repo_name, base_path):
60 60 if os.path.isdir(os.path.join(base_path, repo_name)):return False
61 61 return True
62 62
63 63 def check_repo(repo_name, base_path, verify=True):
64 64
65 65 repo_path = os.path.join(base_path, repo_name)
66 66
67 67 try:
68 68 if not check_repo_fast(repo_name, base_path):
69 69 return False
70 70 r = hg.repository(ui.ui(), repo_path)
71 71 if verify:
72 72 hg.verify(r)
73 73 #here we hnow that repo exists it was verified
74 74 log.info('%s repo is already created', repo_name)
75 75 return False
76 76 except RepoError:
77 77 #it means that there is no valid repo there...
78 78 log.info('%s repo is free for creation', repo_name)
79 79 return True
80 80
81 81 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
82 82 while True:
83 83 ok = raw_input(prompt)
84 84 if ok in ('y', 'ye', 'yes'): return True
85 85 if ok in ('n', 'no', 'nop', 'nope'): return False
86 86 retries = retries - 1
87 87 if retries < 0: raise IOError
88 88 print complaint
89 89
90 90 @cache_region('super_short_term', 'cached_hg_ui')
91 91 def get_hg_ui_cached():
92 92 try:
93 93 sa = meta.Session
94 94 ret = sa.query(HgAppUi).all()
95 95 finally:
96 96 meta.Session.remove()
97 97 return ret
98 98
99 99
100 100 def get_hg_settings():
101 101 try:
102 102 sa = meta.Session
103 103 ret = sa.query(HgAppSettings).all()
104 104 finally:
105 105 meta.Session.remove()
106 106
107 107 if not ret:
108 108 raise Exception('Could not get application settings !')
109 109 settings = {}
110 110 for each in ret:
111 111 settings['hg_app_' + each.app_settings_name] = each.app_settings_value
112 112
113 113 return settings
114 114
115 115 def get_hg_ui_settings():
116 116 try:
117 117 sa = meta.Session
118 118 ret = sa.query(HgAppUi).all()
119 119 finally:
120 120 meta.Session.remove()
121 121
122 122 if not ret:
123 123 raise Exception('Could not get application ui settings !')
124 124 settings = {}
125 125 for each in ret:
126 k = each.ui_key if each.ui_key != '/' else 'root_path'
127 settings[each.ui_section + '_' + k] = each.ui_value
126 k = each.ui_key
127 v = each.ui_value
128 if k == '/':
129 k = 'root_path'
130
131 if k.find('.') != -1:
132 k = k.replace('.', '_')
133
134 if each.ui_section == 'hooks':
135 v = each.ui_active
136
137 settings[each.ui_section + '_' + k] = v
128 138
129 139 return settings
130 140
131 141 #propagated from mercurial documentation
132 142 ui_sections = ['alias', 'auth',
133 143 'decode/encode', 'defaults',
134 144 'diff', 'email',
135 145 'extensions', 'format',
136 146 'merge-patterns', 'merge-tools',
137 147 'hooks', 'http_proxy',
138 148 'smtp', 'patch',
139 149 'paths', 'profiling',
140 150 'server', 'trusted',
141 151 'ui', 'web', ]
142 152
143 153 def make_ui(read_from='file', path=None, checkpaths=True):
144 154 """
145 155 A function that will read python rc files or database
146 156 and make an mercurial ui object from read options
147 157
148 158 @param path: path to mercurial config file
149 159 @param checkpaths: check the path
150 160 @param read_from: read from 'file' or 'db'
151 161 """
152 162
153 163 baseui = ui.ui()
154 164
155 165 if read_from == 'file':
156 166 if not os.path.isfile(path):
157 167 log.warning('Unable to read config file %s' % path)
158 168 return False
159 169 log.debug('reading hgrc from %s', path)
160 170 cfg = config.config()
161 171 cfg.read(path)
162 172 for section in ui_sections:
163 173 for k, v in cfg.items(section):
164 174 baseui.setconfig(section, k, v)
165 175 log.debug('settings ui from file[%s]%s:%s', section, k, v)
166 176 if checkpaths:check_repo_dir(cfg.items('paths'))
167 177
168 178
169 179 elif read_from == 'db':
170 180 hg_ui = get_hg_ui_cached()
171 181 for ui_ in hg_ui:
172 182 if ui_.ui_active:
173 183 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value)
174 184 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
175 185
176 186
177 187 return baseui
178 188
179 189
180 190 def set_hg_app_config(config):
181 191 hgsettings = get_hg_settings()
182 192
183 193 for k, v in hgsettings.items():
184 194 config[k] = v
185 195
186 196 def invalidate_cache(name, *args):
187 197 """Invalidates given name cache"""
188 198
189 199 from beaker.cache import region_invalidate
190 200 log.info('INVALIDATING CACHE FOR %s', name)
191 201
192 202 """propagate our arguments to make sure invalidation works. First
193 203 argument has to be the name of cached func name give to cache decorator
194 204 without that the invalidation would not work"""
195 205 tmp = [name]
196 206 tmp.extend(args)
197 207 args = tuple(tmp)
198 208
199 209 if name == 'cached_repo_list':
200 210 from pylons_app.model.hg_model import _get_repos_cached
201 211 region_invalidate(_get_repos_cached, None, *args)
202 212
203 213 if name == 'full_changelog':
204 214 from pylons_app.model.hg_model import _full_changelog_cached
205 215 region_invalidate(_full_changelog_cached, None, *args)
206 216
207 217 class EmptyChangeset(BaseChangeset):
208 218
209 219 revision = -1
210 220 message = ''
211 221
212 222 @LazyProperty
213 223 def raw_id(self):
214 224 """
215 225 Returns raw string identifing this changeset, useful for web
216 226 representation.
217 227 """
218 228 return '0' * 12
219 229
220 230
221 231 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
222 232 """
223 233 maps all found repositories into db
224 234 """
225 235 from pylons_app.model.repo_model import RepoModel
226 236
227 237 sa = meta.Session
228 238 user = sa.query(User).filter(User.admin == True).first()
229 239
230 240 rm = RepoModel()
231 241
232 242 for name, repo in initial_repo_list.items():
233 243 if not sa.query(Repository).filter(Repository.repo_name == name).scalar():
234 244 log.info('repository %s not found creating default', name)
235 245
236 246 form_data = {
237 247 'repo_name':name,
238 248 'description':repo.description if repo.description != 'unknown' else \
239 249 'auto description for %s' % name,
240 250 'private':False
241 251 }
242 252 rm.create(form_data, user, just_db=True)
243 253
244 254
245 255 if remove_obsolete:
246 256 #remove from database those repositories that are not in the filesystem
247 257 for repo in sa.query(Repository).all():
248 258 if repo.repo_name not in initial_repo_list.keys():
249 259 sa.delete(repo)
250 260 sa.commit()
251 261
252 262
253 263 meta.Session.remove()
254 264
255 265 from UserDict import DictMixin
256 266
257 267 class OrderedDict(dict, DictMixin):
258 268
259 269 def __init__(self, *args, **kwds):
260 270 if len(args) > 1:
261 271 raise TypeError('expected at most 1 arguments, got %d' % len(args))
262 272 try:
263 273 self.__end
264 274 except AttributeError:
265 275 self.clear()
266 276 self.update(*args, **kwds)
267 277
268 278 def clear(self):
269 279 self.__end = end = []
270 280 end += [None, end, end] # sentinel node for doubly linked list
271 281 self.__map = {} # key --> [key, prev, next]
272 282 dict.clear(self)
273 283
274 284 def __setitem__(self, key, value):
275 285 if key not in self:
276 286 end = self.__end
277 287 curr = end[1]
278 288 curr[2] = end[1] = self.__map[key] = [key, curr, end]
279 289 dict.__setitem__(self, key, value)
280 290
281 291 def __delitem__(self, key):
282 292 dict.__delitem__(self, key)
283 293 key, prev, next = self.__map.pop(key)
284 294 prev[2] = next
285 295 next[1] = prev
286 296
287 297 def __iter__(self):
288 298 end = self.__end
289 299 curr = end[2]
290 300 while curr is not end:
291 301 yield curr[0]
292 302 curr = curr[2]
293 303
294 304 def __reversed__(self):
295 305 end = self.__end
296 306 curr = end[1]
297 307 while curr is not end:
298 308 yield curr[0]
299 309 curr = curr[1]
300 310
301 311 def popitem(self, last=True):
302 312 if not self:
303 313 raise KeyError('dictionary is empty')
304 314 if last:
305 315 key = reversed(self).next()
306 316 else:
307 317 key = iter(self).next()
308 318 value = self.pop(key)
309 319 return key, value
310 320
311 321 def __reduce__(self):
312 322 items = [[k, self[k]] for k in self]
313 323 tmp = self.__map, self.__end
314 324 del self.__map, self.__end
315 325 inst_dict = vars(self).copy()
316 326 self.__map, self.__end = tmp
317 327 if inst_dict:
318 328 return (self.__class__, (items,), inst_dict)
319 329 return self.__class__, (items,)
320 330
321 331 def keys(self):
322 332 return list(self)
323 333
324 334 setdefault = DictMixin.setdefault
325 335 update = DictMixin.update
326 336 pop = DictMixin.pop
327 337 values = DictMixin.values
328 338 items = DictMixin.items
329 339 iterkeys = DictMixin.iterkeys
330 340 itervalues = DictMixin.itervalues
331 341 iteritems = DictMixin.iteritems
332 342
333 343 def __repr__(self):
334 344 if not self:
335 345 return '%s()' % (self.__class__.__name__,)
336 346 return '%s(%r)' % (self.__class__.__name__, self.items())
337 347
338 348 def copy(self):
339 349 return self.__class__(self)
340 350
341 351 @classmethod
342 352 def fromkeys(cls, iterable, value=None):
343 353 d = cls()
344 354 for key in iterable:
345 355 d[key] = value
346 356 return d
347 357
348 358 def __eq__(self, other):
349 359 if isinstance(other, OrderedDict):
350 360 return len(self) == len(other) and self.items() == other.items()
351 361 return dict.__eq__(self, other)
352 362
353 363 def __ne__(self, other):
354 364 return not self == other
@@ -1,328 +1,330 b''
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 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 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 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 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 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 from formencode import All
23 23 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
24 24 Email, Bool, StringBoolean
25 25 from pylons import session
26 26 from pylons.i18n.translation import _
27 27 from pylons_app.lib.auth import get_crypt_password
28 28 from pylons_app.model import meta
29 29 from pylons_app.model.db import User, Repository
30 30 from sqlalchemy.exc import OperationalError
31 31 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
32 32 from webhelpers.pylonslib.secure_form import authentication_token
33 33 import datetime
34 34 import formencode
35 35 import logging
36 36 import os
37 37 import pylons_app.lib.helpers as h
38 38 log = logging.getLogger(__name__)
39 39
40 40
41 41 #this is needed to translate the messages using _() in validators
42 42 class State_obj(object):
43 43 _ = staticmethod(_)
44 44
45 45 #===============================================================================
46 46 # VALIDATORS
47 47 #===============================================================================
48 48 class ValidAuthToken(formencode.validators.FancyValidator):
49 49 messages = {'invalid_token':_('Token mismatch')}
50 50
51 51 def validate_python(self, value, state):
52 52
53 53 if value != authentication_token():
54 54 raise formencode.Invalid(self.message('invalid_token', state,
55 55 search_number=value), value, state)
56 56
57 57 def ValidUsername(edit, old_data):
58 58 class _ValidUsername(formencode.validators.FancyValidator):
59 59
60 60 def validate_python(self, value, state):
61 61 if value in ['default', 'new_user']:
62 62 raise formencode.Invalid(_('Invalid username'), value, state)
63 63 #check if user is uniq
64 64 sa = meta.Session
65 65 old_un = None
66 66 if edit:
67 67 old_un = sa.query(User).get(old_data.get('user_id')).username
68 68
69 69 if old_un != value or not edit:
70 70 if sa.query(User).filter(User.username == value).scalar():
71 71 raise formencode.Invalid(_('This username already exists') ,
72 72 value, state)
73 73 meta.Session.remove()
74 74
75 75 return _ValidUsername
76 76
77 77 class ValidPassword(formencode.validators.FancyValidator):
78 78
79 79 def to_python(self, value, state):
80 80 if value:
81 81 return get_crypt_password(value)
82 82
83 83 class ValidAuth(formencode.validators.FancyValidator):
84 84 messages = {
85 85 'invalid_password':_('invalid password'),
86 86 'invalid_login':_('invalid user name'),
87 87 'disabled_account':_('Your acccount is disabled')
88 88
89 89 }
90 90 #error mapping
91 91 e_dict = {'username':messages['invalid_login'],
92 92 'password':messages['invalid_password']}
93 93 e_dict_disable = {'username':messages['disabled_account']}
94 94
95 95 def validate_python(self, value, state):
96 96 sa = meta.Session
97 97 crypted_passwd = get_crypt_password(value['password'])
98 98 username = value['username']
99 99 try:
100 100 user = sa.query(User).filter(User.username == username).one()
101 101 except (NoResultFound, MultipleResultsFound, OperationalError) as e:
102 102 log.error(e)
103 103 user = None
104 104 raise formencode.Invalid(self.message('invalid_password',
105 105 state=State_obj), value, state,
106 106 error_dict=self.e_dict)
107 107 if user:
108 108 if user.active:
109 109 if user.username == username and user.password == crypted_passwd:
110 110 from pylons_app.lib.auth import AuthUser
111 111 auth_user = AuthUser()
112 112 auth_user.username = username
113 113 auth_user.is_authenticated = True
114 114 auth_user.is_admin = user.admin
115 115 auth_user.user_id = user.user_id
116 116 auth_user.name = user.name
117 117 auth_user.lastname = user.lastname
118 118 session['hg_app_user'] = auth_user
119 119 session.save()
120 120 log.info('user %s is now authenticated', username)
121 121
122 122 try:
123 123 user.last_login = datetime.datetime.now()
124 124 sa.add(user)
125 125 sa.commit()
126 126 except (OperationalError) as e:
127 127 log.error(e)
128 128 sa.rollback()
129 129
130 130 return value
131 131 else:
132 132 log.warning('user %s not authenticated', username)
133 133 raise formencode.Invalid(self.message('invalid_password',
134 134 state=State_obj), value, state,
135 135 error_dict=self.e_dict)
136 136 else:
137 137 log.warning('user %s is disabled', username)
138 138 raise formencode.Invalid(self.message('disabled_account',
139 139 state=State_obj),
140 140 value, state,
141 141 error_dict=self.e_dict_disable)
142 142
143 143 meta.Session.remove()
144 144
145 145
146 146 class ValidRepoUser(formencode.validators.FancyValidator):
147 147
148 148 def to_python(self, value, state):
149 149 sa = meta.Session
150 150 try:
151 151 self.user_db = sa.query(User)\
152 152 .filter(User.active == True)\
153 153 .filter(User.username == value).one()
154 154 except Exception:
155 155 raise formencode.Invalid(_('This username is not valid'),
156 156 value, state)
157 157 meta.Session.remove()
158 158 return self.user_db.user_id
159 159
160 160 def ValidRepoName(edit, old_data):
161 161 class _ValidRepoName(formencode.validators.FancyValidator):
162 162
163 163 def to_python(self, value, state):
164 164 slug = h.repo_name_slug(value)
165 165 if slug in ['_admin']:
166 166 raise formencode.Invalid(_('This repository name is disallowed'),
167 167 value, state)
168 168 if old_data.get('repo_name') != value or not edit:
169 169 sa = meta.Session
170 170 if sa.query(Repository).filter(Repository.repo_name == slug).scalar():
171 171 raise formencode.Invalid(_('This repository already exists') ,
172 172 value, state)
173 173 meta.Session.remove()
174 174 return slug
175 175
176 176
177 177 return _ValidRepoName
178 178
179 179 class ValidPerms(formencode.validators.FancyValidator):
180 180 messages = {'perm_new_user_name':_('This username is not valid')}
181 181
182 182 def to_python(self, value, state):
183 183 perms_update = []
184 184 perms_new = []
185 185 #build a list of permission to update and new permission to create
186 186 for k, v in value.items():
187 187 if k.startswith('perm_'):
188 188 if k.startswith('perm_new_user'):
189 189 new_perm = value.get('perm_new_user', False)
190 190 new_user = value.get('perm_new_user_name', False)
191 191 if new_user and new_perm:
192 192 if (new_user, new_perm) not in perms_new:
193 193 perms_new.append((new_user, new_perm))
194 194 else:
195 195 usr = k[5:]
196 196 if usr == 'default':
197 197 if value['private']:
198 198 #set none for default when updating to private repo
199 199 v = 'repository.none'
200 200 perms_update.append((usr, v))
201 201 value['perms_updates'] = perms_update
202 202 value['perms_new'] = perms_new
203 203 sa = meta.Session
204 204 for k, v in perms_new:
205 205 try:
206 206 self.user_db = sa.query(User)\
207 207 .filter(User.active == True)\
208 208 .filter(User.username == k).one()
209 209 except Exception:
210 210 msg = self.message('perm_new_user_name',
211 211 state=State_obj)
212 212 raise formencode.Invalid(msg, value, state, error_dict={'perm_new_user_name':msg})
213 213 return value
214 214
215 215 class ValidSettings(formencode.validators.FancyValidator):
216 216
217 217 def to_python(self, value, state):
218 218 #settings form can't edit user
219 219 if value.has_key('user'):
220 220 del['value']['user']
221 221
222 222 return value
223 223
224 224 class ValidPath(formencode.validators.FancyValidator):
225 225 def to_python(self, value, state):
226 226 isdir = os.path.isdir(value.replace('*', ''))
227 227 if (value.endswith('/*') or value.endswith('/**')) and isdir:
228 228 return value
229 229 elif not isdir:
230 230 msg = _('This is not a valid path')
231 231 else:
232 232 msg = _('You need to specify * or ** at the end of path (ie. /tmp/*)')
233 233
234 234 raise formencode.Invalid(msg, value, state,
235 235 error_dict={'paths_root_path':msg})
236 236
237 237 #===============================================================================
238 238 # FORMS
239 239 #===============================================================================
240 240 class LoginForm(formencode.Schema):
241 241 allow_extra_fields = True
242 242 filter_extra_fields = True
243 243 username = UnicodeString(
244 244 strip=True,
245 245 min=3,
246 246 not_empty=True,
247 247 messages={
248 248 'empty':_('Please enter a login'),
249 249 'tooShort':_('Enter a value %(min)i characters long or more')}
250 250 )
251 251
252 252 password = UnicodeString(
253 253 strip=True,
254 254 min=3,
255 255 not_empty=True,
256 256 messages={
257 257 'empty':_('Please enter a password'),
258 258 'tooShort':_('Enter a value %(min)i characters long or more')}
259 259 )
260 260
261 261
262 262 #chained validators have access to all data
263 263 chained_validators = [ValidAuth]
264 264
265 265 def UserForm(edit=False, old_data={}):
266 266 class _UserForm(formencode.Schema):
267 267 allow_extra_fields = True
268 268 filter_extra_fields = True
269 269 username = All(UnicodeString(strip=True, min=3, not_empty=True), ValidUsername(edit, old_data))
270 270 if edit:
271 271 new_password = All(UnicodeString(strip=True, min=3, not_empty=False), ValidPassword)
272 272 admin = StringBoolean(if_missing=False)
273 273 else:
274 274 password = All(UnicodeString(strip=True, min=8, not_empty=True), ValidPassword)
275 275 active = StringBoolean(if_missing=False)
276 276 name = UnicodeString(strip=True, min=3, not_empty=True)
277 277 lastname = UnicodeString(strip=True, min=3, not_empty=True)
278 278 email = Email(not_empty=True)
279 279
280 280 return _UserForm
281 281
282 282 RegisterForm = UserForm
283 283
284 284
285 285 def RepoForm(edit=False, old_data={}):
286 286 class _RepoForm(formencode.Schema):
287 287 allow_extra_fields = True
288 288 filter_extra_fields = False
289 289 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
290 290 description = UnicodeString(strip=True, min=3, not_empty=True)
291 291 private = StringBoolean(if_missing=False)
292 292
293 293 if edit:
294 294 user = All(Int(not_empty=True), ValidRepoUser)
295 295
296 296 chained_validators = [ValidPerms]
297 297 return _RepoForm
298 298
299 299 def RepoSettingsForm(edit=False, old_data={}):
300 300 class _RepoForm(formencode.Schema):
301 301 allow_extra_fields = True
302 302 filter_extra_fields = False
303 303 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
304 304 description = UnicodeString(strip=True, min=3, not_empty=True)
305 305 private = StringBoolean(if_missing=False)
306 306
307 307 chained_validators = [ValidPerms, ValidSettings]
308 308 return _RepoForm
309 309
310 310
311 311 def ApplicationSettingsForm():
312 312 class _ApplicationSettingsForm(formencode.Schema):
313 313 allow_extra_fields = True
314 314 filter_extra_fields = False
315 315 hg_app_title = UnicodeString(strip=True, min=3, not_empty=True)
316 316 hg_app_realm = UnicodeString(strip=True, min=3, not_empty=True)
317 317
318 318 return _ApplicationSettingsForm
319 319
320 320 def ApplicationUiSettingsForm():
321 321 class _ApplicationUiSettingsForm(formencode.Schema):
322 322 allow_extra_fields = True
323 323 filter_extra_fields = False
324 324 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
325 325 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=3, not_empty=True))
326 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
327 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
326 328
327 329 return _ApplicationUiSettingsForm
328 330
@@ -1,129 +1,145 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Settings administration')}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Settings')}
10 10 </%def>
11 11
12 12 <%def name="page_nav()">
13 13 ${self.menu('admin')}
14 14 </%def>
15 15
16 16 <%def name="main()">
17 17 <div class="box">
18 18 <!-- box / title -->
19 19 <div class="title">
20 20 ${self.breadcrumbs()}
21 21 </div>
22 22 <!-- end box / title -->
23 23
24 24 <h3>${_('Remap and rescan repositories')}</h3>
25 25 ${h.form(url('admin_setting', setting_id='mapping'),method='put')}
26 26 <div class="form">
27 27 <!-- fields -->
28 28
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label label-checkbox">
32 32 <label for="destroy">${_('rescan option')}:</label>
33 33 </div>
34 34 <div class="checkboxes">
35 35 <div class="checkbox">
36 36 ${h.checkbox('destroy',True)}
37 37 <label for="checkbox-1">
38 38 <span class="tooltip" 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 39 ${_('destroy old data')}</span> </label>
40 40 </div>
41 41 </div>
42 42 </div>
43 43
44 44 <div class="buttons">
45 45 ${h.submit('rescan','rescan repositories',class_="ui-button ui-widget ui-state-default ui-corner-all")}
46 46 </div>
47 47 </div>
48 48 </div>
49 49 ${h.end_form()}
50 50
51 51 <h3>${_('Global application settings')}</h3>
52 52 ${h.form(url('admin_setting', setting_id='global'),method='put')}
53 53 <div class="form">
54 54 <!-- fields -->
55 55
56 56 <div class="fields">
57 57
58 58 <div class="field">
59 59 <div class="label">
60 60 <label for="hg_app_title">${_('Application name')}:</label>
61 61 </div>
62 62 <div class="input">
63 63 ${h.text('hg_app_title',size=30)}
64 64 </div>
65 65 </div>
66 66
67 67 <div class="field">
68 68 <div class="label">
69 69 <label for="hg_app_realm">${_('Realm text')}:</label>
70 70 </div>
71 71 <div class="input">
72 72 ${h.text('hg_app_realm',size=30)}
73 73 </div>
74 74 </div>
75 75
76 76 <div class="buttons">
77 77 ${h.submit('save','save settings',class_="ui-button ui-widget ui-state-default ui-corner-all")}
78 78 </div>
79 79 </div>
80 80 </div>
81 81 ${h.end_form()}
82 82
83 83 <h3>${_('Mercurial settings')}</h3>
84 84 ${h.form(url('admin_setting', setting_id='mercurial'),method='put')}
85 85 <div class="form">
86 86 <!-- fields -->
87 87
88 88 <div class="fields">
89 89
90 90 <div class="field">
91 91 <div class="label label-checkbox">
92 <label for="web_push_ssl">${_('Push ssl')}:</label>
92 <label for="web_push_ssl">${_('Web')}:</label>
93 93 </div>
94 94 <div class="checkboxes">
95 95 <div class="checkbox">
96 96 ${h.checkbox('web_push_ssl','true')}
97 97 <label for="web_push_ssl">${_('require ssl for pushing')}</label>
98 98 </div>
99 99 </div>
100 100 </div>
101
102 <div class="field">
103 <div class="label label-checkbox">
104 <label for="web_push_ssl">${_('Hooks')}:</label>
105 </div>
106 <div class="checkboxes">
107 <div class="checkbox">
108 ${h.checkbox('hooks_changegroup_update','True')}
109 <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
110 </div>
111 <div class="checkbox">
112 ${h.checkbox('hooks_changegroup_repo_size','True')}
113 <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
114 </div>
115 </div>
116 </div>
101 117
102 118 <div class="field">
103 119 <div class="label">
104 120 <label for="paths_root_path">${_('Repositories location')}:</label>
105 121 </div>
106 122 <div class="input">
107 ${h.text('paths_root_path',size=30,disabled="disabled")}
123 ${h.text('paths_root_path',size=30,readonly="readonly")}
108 124 <span id="path_unlock" class="tooltip" tooltip_title="${h.tooltip(_('This a crucial application setting. If You really sure you need to change this, you must restart application in order to make this settings take effect. Click this label to unlock.'))}">
109 125 ${_('unlock')}</span>
110 126 </div>
111 127 </div>
112 128
113 129 <div class="buttons">
114 130 ${h.submit('save','save settings',class_="ui-button ui-widget ui-state-default ui-corner-all")}
115 131 </div>
116 132 </div>
117 133 </div>
118 134 ${h.end_form()}
119 135
120 136 <script type="text/javascript">
121 137 YAHOO.util.Event.onDOMReady(function(){
122 138 YAHOO.util.Event.addListener('path_unlock','click',function(){
123 YAHOO.util.Dom.get('paths_root_path').disabled=false;
139 YAHOO.util.Dom.get('paths_root_path').readonly=false;
124 140 });
125 141 });
126 142 </script>
127 143
128 144 </div>
129 145 </%def>
General Comments 0
You need to be logged in to leave comments. Login now