##// END OF EJS Templates
fixed bug when new repo had no last commiter,...
marcink -
r489:460ad816 celery
parent child Browse files
Show More
@@ -1,293 +1,298 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # settings controller for pylons
3 # settings controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on July 14, 2010
21 Created on July 14, 2010
22 settings controller for pylons
22 settings controller for pylons
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from formencode import htmlfill
25 from formencode import htmlfill
26 from pylons import request, session, tmpl_context as c, url, app_globals as g, \
26 from pylons import request, session, tmpl_context as c, url, app_globals as g, \
27 config
27 config
28 from pylons.controllers.util import abort, redirect
28 from pylons.controllers.util import abort, redirect
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30 from pylons_app.lib import helpers as h
30 from pylons_app.lib import helpers as h
31 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator, \
31 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator, \
32 HasPermissionAnyDecorator
32 HasPermissionAnyDecorator
33 from pylons_app.lib.base import BaseController, render
33 from pylons_app.lib.base import BaseController, render
34 from pylons_app.lib.utils import repo2db_mapper, invalidate_cache, \
34 from pylons_app.lib.utils import repo2db_mapper, invalidate_cache, \
35 set_hg_app_config, get_hg_settings, get_hg_ui_settings, make_ui
35 set_hg_app_config, get_hg_settings, get_hg_ui_settings, make_ui
36 from pylons_app.model.db import User, UserLog, HgAppSettings, HgAppUi
36 from pylons_app.model.db import User, UserLog, HgAppSettings, HgAppUi
37 from pylons_app.model.forms import UserForm, ApplicationSettingsForm, \
37 from pylons_app.model.forms import UserForm, ApplicationSettingsForm, \
38 ApplicationUiSettingsForm
38 ApplicationUiSettingsForm
39 from pylons_app.model.hg_model import HgModel
39 from pylons_app.model.hg_model import HgModel
40 from pylons_app.model.user_model import UserModel
40 from pylons_app.model.user_model import UserModel
41 from pylons_app.lib.celerylib import tasks,run_task
41 from pylons_app.lib.celerylib import tasks, run_task
42 import formencode
42 import formencode
43 import logging
43 import logging
44 import traceback
44 import traceback
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class SettingsController(BaseController):
49 class SettingsController(BaseController):
50 """REST Controller styled on the Atom Publishing Protocol"""
50 """REST Controller styled on the Atom Publishing Protocol"""
51 # To properly map this controller, ensure your config/routing.py
51 # To properly map this controller, ensure your config/routing.py
52 # file has a resource setup:
52 # file has a resource setup:
53 # map.resource('setting', 'settings', controller='admin/settings',
53 # map.resource('setting', 'settings', controller='admin/settings',
54 # path_prefix='/admin', name_prefix='admin_')
54 # path_prefix='/admin', name_prefix='admin_')
55
55
56
56
57 @LoginRequired()
57 @LoginRequired()
58 def __before__(self):
58 def __before__(self):
59 c.admin_user = session.get('admin_user')
59 c.admin_user = session.get('admin_user')
60 c.admin_username = session.get('admin_username')
60 c.admin_username = session.get('admin_username')
61 super(SettingsController, self).__before__()
61 super(SettingsController, self).__before__()
62
62
63
63
64 @HasPermissionAllDecorator('hg.admin')
64 @HasPermissionAllDecorator('hg.admin')
65 def index(self, format='html'):
65 def index(self, format='html'):
66 """GET /admin/settings: All items in the collection"""
66 """GET /admin/settings: All items in the collection"""
67 # url('admin_settings')
67 # url('admin_settings')
68
68
69 defaults = get_hg_settings()
69 defaults = get_hg_settings()
70 defaults.update(get_hg_ui_settings())
70 defaults.update(get_hg_ui_settings())
71 return htmlfill.render(
71 return htmlfill.render(
72 render('admin/settings/settings.html'),
72 render('admin/settings/settings.html'),
73 defaults=defaults,
73 defaults=defaults,
74 encoding="UTF-8",
74 encoding="UTF-8",
75 force_defaults=False
75 force_defaults=False
76 )
76 )
77
77
78 @HasPermissionAllDecorator('hg.admin')
78 @HasPermissionAllDecorator('hg.admin')
79 def create(self):
79 def create(self):
80 """POST /admin/settings: Create a new item"""
80 """POST /admin/settings: Create a new item"""
81 # url('admin_settings')
81 # url('admin_settings')
82
82
83 @HasPermissionAllDecorator('hg.admin')
83 @HasPermissionAllDecorator('hg.admin')
84 def new(self, format='html'):
84 def new(self, format='html'):
85 """GET /admin/settings/new: Form to create a new item"""
85 """GET /admin/settings/new: Form to create a new item"""
86 # url('admin_new_setting')
86 # url('admin_new_setting')
87
87
88 @HasPermissionAllDecorator('hg.admin')
88 @HasPermissionAllDecorator('hg.admin')
89 def update(self, setting_id):
89 def update(self, setting_id):
90 """PUT /admin/settings/setting_id: Update an existing item"""
90 """PUT /admin/settings/setting_id: Update an existing item"""
91 # Forms posted to this method should contain a hidden field:
91 # Forms posted to this method should contain a hidden field:
92 # <input type="hidden" name="_method" value="PUT" />
92 # <input type="hidden" name="_method" value="PUT" />
93 # Or using helpers:
93 # Or using helpers:
94 # h.form(url('admin_setting', setting_id=ID),
94 # h.form(url('admin_setting', setting_id=ID),
95 # method='put')
95 # method='put')
96 # url('admin_setting', setting_id=ID)
96 # url('admin_setting', setting_id=ID)
97 if setting_id == 'mapping':
97 if setting_id == 'mapping':
98 rm_obsolete = request.POST.get('destroy', False)
98 rm_obsolete = request.POST.get('destroy', False)
99 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
99 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
100
100
101 initial = HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
101 initial = HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
102 repo2db_mapper(initial, rm_obsolete)
102 repo2db_mapper(initial, rm_obsolete)
103 invalidate_cache('cached_repo_list')
103 invalidate_cache('cached_repo_list')
104 h.flash(_('Repositories sucessfully rescanned'), category='success')
104 h.flash(_('Repositories sucessfully rescanned'), category='success')
105
105
106 if setting_id == 'whoosh':
106 if setting_id == 'whoosh':
107 repo_location = get_hg_ui_settings()['paths_root_path']
107 repo_location = get_hg_ui_settings()['paths_root_path']
108 full_index = request.POST.get('full_index',False)
108 full_index = request.POST.get('full_index', False)
109 task = run_task(tasks.whoosh_index,repo_location,full_index)
109 task = run_task(tasks.whoosh_index, repo_location, full_index)
110
110
111 h.flash(_('Whoosh reindex task scheduled'), category='success')
111 h.flash(_('Whoosh reindex task scheduled'), category='success')
112 if setting_id == 'global':
112 if setting_id == 'global':
113
113
114 application_form = ApplicationSettingsForm()()
114 application_form = ApplicationSettingsForm()()
115 try:
115 try:
116 form_result = application_form.to_python(dict(request.POST))
116 form_result = application_form.to_python(dict(request.POST))
117
117
118 try:
118 try:
119 hgsettings1 = self.sa.query(HgAppSettings)\
119 hgsettings1 = self.sa.query(HgAppSettings)\
120 .filter(HgAppSettings.app_settings_name == 'title').one()
120 .filter(HgAppSettings.app_settings_name == 'title').one()
121 hgsettings1.app_settings_value = form_result['hg_app_title']
121 hgsettings1.app_settings_value = form_result['hg_app_title']
122
122
123 hgsettings2 = self.sa.query(HgAppSettings)\
123 hgsettings2 = self.sa.query(HgAppSettings)\
124 .filter(HgAppSettings.app_settings_name == 'realm').one()
124 .filter(HgAppSettings.app_settings_name == 'realm').one()
125 hgsettings2.app_settings_value = form_result['hg_app_realm']
125 hgsettings2.app_settings_value = form_result['hg_app_realm']
126
126
127
127
128 self.sa.add(hgsettings1)
128 self.sa.add(hgsettings1)
129 self.sa.add(hgsettings2)
129 self.sa.add(hgsettings2)
130 self.sa.commit()
130 self.sa.commit()
131 set_hg_app_config(config)
131 set_hg_app_config(config)
132 h.flash(_('Updated application settings'),
132 h.flash(_('Updated application settings'),
133 category='success')
133 category='success')
134
134
135 except:
135 except:
136 log.error(traceback.format_exc())
136 log.error(traceback.format_exc())
137 h.flash(_('error occured during updating application settings'),
137 h.flash(_('error occured during updating application settings'),
138 category='error')
138 category='error')
139
139
140 self.sa.rollback()
140 self.sa.rollback()
141
141
142
142
143 except formencode.Invalid as errors:
143 except formencode.Invalid as errors:
144 return htmlfill.render(
144 return htmlfill.render(
145 render('admin/settings/settings.html'),
145 render('admin/settings/settings.html'),
146 defaults=errors.value,
146 defaults=errors.value,
147 errors=errors.error_dict or {},
147 errors=errors.error_dict or {},
148 prefix_error=False,
148 prefix_error=False,
149 encoding="UTF-8")
149 encoding="UTF-8")
150
150
151 if setting_id == 'mercurial':
151 if setting_id == 'mercurial':
152 application_form = ApplicationUiSettingsForm()()
152 application_form = ApplicationUiSettingsForm()()
153 try:
153 try:
154 form_result = application_form.to_python(dict(request.POST))
154 form_result = application_form.to_python(dict(request.POST))
155
155
156 try:
156 try:
157
157
158 hgsettings1 = self.sa.query(HgAppUi)\
158 hgsettings1 = self.sa.query(HgAppUi)\
159 .filter(HgAppUi.ui_key == 'push_ssl').one()
159 .filter(HgAppUi.ui_key == 'push_ssl').one()
160 hgsettings1.ui_value = form_result['web_push_ssl']
160 hgsettings1.ui_value = form_result['web_push_ssl']
161
161
162 hgsettings2 = self.sa.query(HgAppUi)\
162 hgsettings2 = self.sa.query(HgAppUi)\
163 .filter(HgAppUi.ui_key == '/').one()
163 .filter(HgAppUi.ui_key == '/').one()
164 hgsettings2.ui_value = form_result['paths_root_path']
164 hgsettings2.ui_value = form_result['paths_root_path']
165
165
166
166
167 #HOOKS
167 #HOOKS
168 hgsettings3 = self.sa.query(HgAppUi)\
168 hgsettings3 = self.sa.query(HgAppUi)\
169 .filter(HgAppUi.ui_key == 'changegroup.update').one()
169 .filter(HgAppUi.ui_key == 'changegroup.update').one()
170 hgsettings3.ui_active = bool(form_result['hooks_changegroup_update'])
170 hgsettings3.ui_active = bool(form_result['hooks_changegroup_update'])
171
171
172 hgsettings4 = self.sa.query(HgAppUi)\
172 hgsettings4 = self.sa.query(HgAppUi)\
173 .filter(HgAppUi.ui_key == 'changegroup.repo_size').one()
173 .filter(HgAppUi.ui_key == 'changegroup.repo_size').one()
174 hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size'])
174 hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size'])
175
175
176
176
177
177
178
178
179 self.sa.add(hgsettings1)
179 self.sa.add(hgsettings1)
180 self.sa.add(hgsettings2)
180 self.sa.add(hgsettings2)
181 self.sa.add(hgsettings3)
181 self.sa.add(hgsettings3)
182 self.sa.add(hgsettings4)
182 self.sa.add(hgsettings4)
183 self.sa.commit()
183 self.sa.commit()
184
184
185 h.flash(_('Updated mercurial settings'),
185 h.flash(_('Updated mercurial settings'),
186 category='success')
186 category='success')
187
187
188 except:
188 except:
189 log.error(traceback.format_exc())
189 log.error(traceback.format_exc())
190 h.flash(_('error occured during updating application settings'),
190 h.flash(_('error occured during updating application settings'),
191 category='error')
191 category='error')
192
192
193 self.sa.rollback()
193 self.sa.rollback()
194
194
195
195
196 except formencode.Invalid as errors:
196 except formencode.Invalid as errors:
197 return htmlfill.render(
197 return htmlfill.render(
198 render('admin/settings/settings.html'),
198 render('admin/settings/settings.html'),
199 defaults=errors.value,
199 defaults=errors.value,
200 errors=errors.error_dict or {},
200 errors=errors.error_dict or {},
201 prefix_error=False,
201 prefix_error=False,
202 encoding="UTF-8")
202 encoding="UTF-8")
203
203
204
204
205
205
206 return redirect(url('admin_settings'))
206 return redirect(url('admin_settings'))
207
207
208 @HasPermissionAllDecorator('hg.admin')
208 @HasPermissionAllDecorator('hg.admin')
209 def delete(self, setting_id):
209 def delete(self, setting_id):
210 """DELETE /admin/settings/setting_id: Delete an existing item"""
210 """DELETE /admin/settings/setting_id: Delete an existing item"""
211 # Forms posted to this method should contain a hidden field:
211 # Forms posted to this method should contain a hidden field:
212 # <input type="hidden" name="_method" value="DELETE" />
212 # <input type="hidden" name="_method" value="DELETE" />
213 # Or using helpers:
213 # Or using helpers:
214 # h.form(url('admin_setting', setting_id=ID),
214 # h.form(url('admin_setting', setting_id=ID),
215 # method='delete')
215 # method='delete')
216 # url('admin_setting', setting_id=ID)
216 # url('admin_setting', setting_id=ID)
217
217
218 @HasPermissionAllDecorator('hg.admin')
218 @HasPermissionAllDecorator('hg.admin')
219 def show(self, setting_id, format='html'):
219 def show(self, setting_id, format='html'):
220 """GET /admin/settings/setting_id: Show a specific item"""
220 """GET /admin/settings/setting_id: Show a specific item"""
221 # url('admin_setting', setting_id=ID)
221 # url('admin_setting', setting_id=ID)
222
222
223 @HasPermissionAllDecorator('hg.admin')
223 @HasPermissionAllDecorator('hg.admin')
224 def edit(self, setting_id, format='html'):
224 def edit(self, setting_id, format='html'):
225 """GET /admin/settings/setting_id/edit: Form to edit an existing item"""
225 """GET /admin/settings/setting_id/edit: Form to edit an existing item"""
226 # url('admin_edit_setting', setting_id=ID)
226 # url('admin_edit_setting', setting_id=ID)
227
227
228
228
229 def my_account(self):
229 def my_account(self):
230 """
230 """
231 GET /_admin/my_account Displays info about my account
231 GET /_admin/my_account Displays info about my account
232 """
232 """
233 # url('admin_settings_my_account')
233 # url('admin_settings_my_account')
234 c.user = self.sa.query(User).get(c.hg_app_user.user_id)
234 c.user = self.sa.query(User).get(c.hg_app_user.user_id)
235 c.user_repos = []
235 c.user_repos = []
236 for repo in c.cached_repo_list.values():
236 for repo in c.cached_repo_list.values():
237 if repo.dbrepo.user.username == c.user.username:
237 if repo.dbrepo.user.username == c.user.username:
238 c.user_repos.append(repo)
238 c.user_repos.append(repo)
239
239
240 if c.user.username == 'default':
240 if c.user.username == 'default':
241 h.flash(_("You can't edit this user since it's"
241 h.flash(_("You can't edit this user since it's"
242 " crucial for entire application"), category='warning')
242 " crucial for entire application"), category='warning')
243 return redirect(url('users'))
243 return redirect(url('users'))
244
244
245 defaults = c.user.__dict__
245 defaults = c.user.__dict__
246 return htmlfill.render(
246 return htmlfill.render(
247 render('admin/users/user_edit_my_account.html'),
247 render('admin/users/user_edit_my_account.html'),
248 defaults=defaults,
248 defaults=defaults,
249 encoding="UTF-8",
249 encoding="UTF-8",
250 force_defaults=False
250 force_defaults=False
251 )
251 )
252
252
253 def my_account_update(self):
253 def my_account_update(self):
254 """PUT /_admin/my_account_update: Update an existing item"""
254 """PUT /_admin/my_account_update: Update an existing item"""
255 # Forms posted to this method should contain a hidden field:
255 # Forms posted to this method should contain a hidden field:
256 # <input type="hidden" name="_method" value="PUT" />
256 # <input type="hidden" name="_method" value="PUT" />
257 # Or using helpers:
257 # Or using helpers:
258 # h.form(url('admin_settings_my_account_update'),
258 # h.form(url('admin_settings_my_account_update'),
259 # method='put')
259 # method='put')
260 # url('admin_settings_my_account_update', id=ID)
260 # url('admin_settings_my_account_update', id=ID)
261 user_model = UserModel()
261 user_model = UserModel()
262 uid = c.hg_app_user.user_id
262 uid = c.hg_app_user.user_id
263 _form = UserForm(edit=True, old_data={'user_id':uid})()
263 _form = UserForm(edit=True, old_data={'user_id':uid,
264 'email':c.hg_app_user.email})()
264 form_result = {}
265 form_result = {}
265 try:
266 try:
266 form_result = _form.to_python(dict(request.POST))
267 form_result = _form.to_python(dict(request.POST))
267 user_model.update_my_account(uid, form_result)
268 user_model.update_my_account(uid, form_result)
268 h.flash(_('Your account was updated succesfully'),
269 h.flash(_('Your account was updated succesfully'),
269 category='success')
270 category='success')
270
271
271 except formencode.Invalid as errors:
272 except formencode.Invalid as errors:
272 #c.user = self.sa.query(User).get(c.hg_app_user.user_id)
273 c.user = self.sa.query(User).get(c.hg_app_user.user_id)
274 c.user_repos = []
275 for repo in c.cached_repo_list.values():
276 if repo.dbrepo.user.username == c.user.username:
277 c.user_repos.append(repo)
273 return htmlfill.render(
278 return htmlfill.render(
274 render('admin/users/user_edit_my_account.html'),
279 render('admin/users/user_edit_my_account.html'),
275 defaults=errors.value,
280 defaults=errors.value,
276 errors=errors.error_dict or {},
281 errors=errors.error_dict or {},
277 prefix_error=False,
282 prefix_error=False,
278 encoding="UTF-8")
283 encoding="UTF-8")
279 except Exception:
284 except Exception:
280 log.error(traceback.format_exc())
285 log.error(traceback.format_exc())
281 h.flash(_('error occured during update of user %s') \
286 h.flash(_('error occured during update of user %s') \
282 % form_result.get('username'), category='error')
287 % form_result.get('username'), category='error')
283
288
284 return redirect(url('my_account'))
289 return redirect(url('my_account'))
285
290
286 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
291 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
287 def create_repository(self):
292 def create_repository(self):
288 """GET /_admin/create_repository: Form to create a new item"""
293 """GET /_admin/create_repository: Form to create a new item"""
289 new_repo = request.GET.get('repo', '')
294 new_repo = request.GET.get('repo', '')
290 c.new_repo = h.repo_name_slug(new_repo)
295 c.new_repo = h.repo_name_slug(new_repo)
291
296
292 return render('admin/repos/repo_add_create_repository.html')
297 return render('admin/repos/repo_add_create_repository.html')
293
298
@@ -1,432 +1,433 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # Utilities for hg app
3 # Utilities for hg app
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 # This program is free software; you can redistribute it and/or
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; version 2
7 # as published by the Free Software Foundation; version 2
8 # of the License or (at your opinion) any later version of the license.
8 # of the License or (at your opinion) any later version of the license.
9 #
9 #
10 # This program is distributed in the hope that it will be useful,
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
13 # GNU General Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # MA 02110-1301, USA.
18 # MA 02110-1301, USA.
19 import shutil
19 import shutil
20
20
21 """
21 """
22 Created on April 18, 2010
22 Created on April 18, 2010
23 Utilities for hg app
23 Utilities for hg app
24 @author: marcink
24 @author: marcink
25 """
25 """
26 from beaker.cache import cache_region
26 from beaker.cache import cache_region
27 from mercurial import ui, config, hg
27 from mercurial import ui, config, hg
28 from mercurial.error import RepoError
28 from mercurial.error import RepoError
29 from pylons_app.model import meta
29 from pylons_app.model import meta
30 from pylons_app.model.db import Repository, User, HgAppUi, HgAppSettings
30 from pylons_app.model.db import Repository, User, HgAppUi, HgAppSettings
31 from vcs.backends.base import BaseChangeset
31 from vcs.backends.base import BaseChangeset
32 from vcs.utils.lazy import LazyProperty
32 from vcs.utils.lazy import LazyProperty
33 import logging
33 import logging
34 import os
34 import os
35 from os.path import dirname as dn, join as jn
35 from os.path import dirname as dn, join as jn
36 import tarfile
36 import tarfile
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 def get_repo_slug(request):
40 def get_repo_slug(request):
41 return request.environ['pylons.routes_dict'].get('repo_name')
41 return request.environ['pylons.routes_dict'].get('repo_name')
42
42
43 def is_mercurial(environ):
43 def is_mercurial(environ):
44 """
44 """
45 Returns True if request's target is mercurial server - header
45 Returns True if request's target is mercurial server - header
46 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
46 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
47 """
47 """
48 http_accept = environ.get('HTTP_ACCEPT')
48 http_accept = environ.get('HTTP_ACCEPT')
49 if http_accept and http_accept.startswith('application/mercurial'):
49 if http_accept and http_accept.startswith('application/mercurial'):
50 return True
50 return True
51 return False
51 return False
52
52
53 def check_repo_dir(paths):
53 def check_repo_dir(paths):
54 repos_path = paths[0][1].split('/')
54 repos_path = paths[0][1].split('/')
55 if repos_path[-1] in ['*', '**']:
55 if repos_path[-1] in ['*', '**']:
56 repos_path = repos_path[:-1]
56 repos_path = repos_path[:-1]
57 if repos_path[0] != '/':
57 if repos_path[0] != '/':
58 repos_path[0] = '/'
58 repos_path[0] = '/'
59 if not os.path.isdir(os.path.join(*repos_path)):
59 if not os.path.isdir(os.path.join(*repos_path)):
60 raise Exception('Not a valid repository in %s' % paths[0][1])
60 raise Exception('Not a valid repository in %s' % paths[0][1])
61
61
62 def check_repo_fast(repo_name, base_path):
62 def check_repo_fast(repo_name, base_path):
63 if os.path.isdir(os.path.join(base_path, repo_name)):return False
63 if os.path.isdir(os.path.join(base_path, repo_name)):return False
64 return True
64 return True
65
65
66 def check_repo(repo_name, base_path, verify=True):
66 def check_repo(repo_name, base_path, verify=True):
67
67
68 repo_path = os.path.join(base_path, repo_name)
68 repo_path = os.path.join(base_path, repo_name)
69
69
70 try:
70 try:
71 if not check_repo_fast(repo_name, base_path):
71 if not check_repo_fast(repo_name, base_path):
72 return False
72 return False
73 r = hg.repository(ui.ui(), repo_path)
73 r = hg.repository(ui.ui(), repo_path)
74 if verify:
74 if verify:
75 hg.verify(r)
75 hg.verify(r)
76 #here we hnow that repo exists it was verified
76 #here we hnow that repo exists it was verified
77 log.info('%s repo is already created', repo_name)
77 log.info('%s repo is already created', repo_name)
78 return False
78 return False
79 except RepoError:
79 except RepoError:
80 #it means that there is no valid repo there...
80 #it means that there is no valid repo there...
81 log.info('%s repo is free for creation', repo_name)
81 log.info('%s repo is free for creation', repo_name)
82 return True
82 return True
83
83
84 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
84 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
85 while True:
85 while True:
86 ok = raw_input(prompt)
86 ok = raw_input(prompt)
87 if ok in ('y', 'ye', 'yes'): return True
87 if ok in ('y', 'ye', 'yes'): return True
88 if ok in ('n', 'no', 'nop', 'nope'): return False
88 if ok in ('n', 'no', 'nop', 'nope'): return False
89 retries = retries - 1
89 retries = retries - 1
90 if retries < 0: raise IOError
90 if retries < 0: raise IOError
91 print complaint
91 print complaint
92
92
93 @cache_region('super_short_term', 'cached_hg_ui')
93 @cache_region('super_short_term', 'cached_hg_ui')
94 def get_hg_ui_cached():
94 def get_hg_ui_cached():
95 try:
95 try:
96 sa = meta.Session
96 sa = meta.Session
97 ret = sa.query(HgAppUi).all()
97 ret = sa.query(HgAppUi).all()
98 finally:
98 finally:
99 meta.Session.remove()
99 meta.Session.remove()
100 return ret
100 return ret
101
101
102
102
103 def get_hg_settings():
103 def get_hg_settings():
104 try:
104 try:
105 sa = meta.Session
105 sa = meta.Session
106 ret = sa.query(HgAppSettings).all()
106 ret = sa.query(HgAppSettings).all()
107 finally:
107 finally:
108 meta.Session.remove()
108 meta.Session.remove()
109
109
110 if not ret:
110 if not ret:
111 raise Exception('Could not get application settings !')
111 raise Exception('Could not get application settings !')
112 settings = {}
112 settings = {}
113 for each in ret:
113 for each in ret:
114 settings['hg_app_' + each.app_settings_name] = each.app_settings_value
114 settings['hg_app_' + each.app_settings_name] = each.app_settings_value
115
115
116 return settings
116 return settings
117
117
118 def get_hg_ui_settings():
118 def get_hg_ui_settings():
119 try:
119 try:
120 sa = meta.Session
120 sa = meta.Session
121 ret = sa.query(HgAppUi).all()
121 ret = sa.query(HgAppUi).all()
122 finally:
122 finally:
123 meta.Session.remove()
123 meta.Session.remove()
124
124
125 if not ret:
125 if not ret:
126 raise Exception('Could not get application ui settings !')
126 raise Exception('Could not get application ui settings !')
127 settings = {}
127 settings = {}
128 for each in ret:
128 for each in ret:
129 k = each.ui_key
129 k = each.ui_key
130 v = each.ui_value
130 v = each.ui_value
131 if k == '/':
131 if k == '/':
132 k = 'root_path'
132 k = 'root_path'
133
133
134 if k.find('.') != -1:
134 if k.find('.') != -1:
135 k = k.replace('.', '_')
135 k = k.replace('.', '_')
136
136
137 if each.ui_section == 'hooks':
137 if each.ui_section == 'hooks':
138 v = each.ui_active
138 v = each.ui_active
139
139
140 settings[each.ui_section + '_' + k] = v
140 settings[each.ui_section + '_' + k] = v
141
141
142 return settings
142 return settings
143
143
144 #propagated from mercurial documentation
144 #propagated from mercurial documentation
145 ui_sections = ['alias', 'auth',
145 ui_sections = ['alias', 'auth',
146 'decode/encode', 'defaults',
146 'decode/encode', 'defaults',
147 'diff', 'email',
147 'diff', 'email',
148 'extensions', 'format',
148 'extensions', 'format',
149 'merge-patterns', 'merge-tools',
149 'merge-patterns', 'merge-tools',
150 'hooks', 'http_proxy',
150 'hooks', 'http_proxy',
151 'smtp', 'patch',
151 'smtp', 'patch',
152 'paths', 'profiling',
152 'paths', 'profiling',
153 'server', 'trusted',
153 'server', 'trusted',
154 'ui', 'web', ]
154 'ui', 'web', ]
155
155
156 def make_ui(read_from='file', path=None, checkpaths=True):
156 def make_ui(read_from='file', path=None, checkpaths=True):
157 """
157 """
158 A function that will read python rc files or database
158 A function that will read python rc files or database
159 and make an mercurial ui object from read options
159 and make an mercurial ui object from read options
160
160
161 @param path: path to mercurial config file
161 @param path: path to mercurial config file
162 @param checkpaths: check the path
162 @param checkpaths: check the path
163 @param read_from: read from 'file' or 'db'
163 @param read_from: read from 'file' or 'db'
164 """
164 """
165
165
166 baseui = ui.ui()
166 baseui = ui.ui()
167
167
168 if read_from == 'file':
168 if read_from == 'file':
169 if not os.path.isfile(path):
169 if not os.path.isfile(path):
170 log.warning('Unable to read config file %s' % path)
170 log.warning('Unable to read config file %s' % path)
171 return False
171 return False
172 log.debug('reading hgrc from %s', path)
172 log.debug('reading hgrc from %s', path)
173 cfg = config.config()
173 cfg = config.config()
174 cfg.read(path)
174 cfg.read(path)
175 for section in ui_sections:
175 for section in ui_sections:
176 for k, v in cfg.items(section):
176 for k, v in cfg.items(section):
177 baseui.setconfig(section, k, v)
177 baseui.setconfig(section, k, v)
178 log.debug('settings ui from file[%s]%s:%s', section, k, v)
178 log.debug('settings ui from file[%s]%s:%s', section, k, v)
179 if checkpaths:check_repo_dir(cfg.items('paths'))
179 if checkpaths:check_repo_dir(cfg.items('paths'))
180
180
181
181
182 elif read_from == 'db':
182 elif read_from == 'db':
183 hg_ui = get_hg_ui_cached()
183 hg_ui = get_hg_ui_cached()
184 for ui_ in hg_ui:
184 for ui_ in hg_ui:
185 if ui_.ui_active:
185 if ui_.ui_active:
186 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value)
186 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value)
187 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
187 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
188
188
189
189
190 return baseui
190 return baseui
191
191
192
192
193 def set_hg_app_config(config):
193 def set_hg_app_config(config):
194 hgsettings = get_hg_settings()
194 hgsettings = get_hg_settings()
195
195
196 for k, v in hgsettings.items():
196 for k, v in hgsettings.items():
197 config[k] = v
197 config[k] = v
198
198
199 def invalidate_cache(name, *args):
199 def invalidate_cache(name, *args):
200 """Invalidates given name cache"""
200 """Invalidates given name cache"""
201
201
202 from beaker.cache import region_invalidate
202 from beaker.cache import region_invalidate
203 log.info('INVALIDATING CACHE FOR %s', name)
203 log.info('INVALIDATING CACHE FOR %s', name)
204
204
205 """propagate our arguments to make sure invalidation works. First
205 """propagate our arguments to make sure invalidation works. First
206 argument has to be the name of cached func name give to cache decorator
206 argument has to be the name of cached func name give to cache decorator
207 without that the invalidation would not work"""
207 without that the invalidation would not work"""
208 tmp = [name]
208 tmp = [name]
209 tmp.extend(args)
209 tmp.extend(args)
210 args = tuple(tmp)
210 args = tuple(tmp)
211
211
212 if name == 'cached_repo_list':
212 if name == 'cached_repo_list':
213 from pylons_app.model.hg_model import _get_repos_cached
213 from pylons_app.model.hg_model import _get_repos_cached
214 region_invalidate(_get_repos_cached, None, *args)
214 region_invalidate(_get_repos_cached, None, *args)
215
215
216 if name == 'full_changelog':
216 if name == 'full_changelog':
217 from pylons_app.model.hg_model import _full_changelog_cached
217 from pylons_app.model.hg_model import _full_changelog_cached
218 region_invalidate(_full_changelog_cached, None, *args)
218 region_invalidate(_full_changelog_cached, None, *args)
219
219
220 class EmptyChangeset(BaseChangeset):
220 class EmptyChangeset(BaseChangeset):
221
221
222 revision = -1
222 revision = -1
223 message = ''
223 message = ''
224 author = ''
224
225
225 @LazyProperty
226 @LazyProperty
226 def raw_id(self):
227 def raw_id(self):
227 """
228 """
228 Returns raw string identifing this changeset, useful for web
229 Returns raw string identifing this changeset, useful for web
229 representation.
230 representation.
230 """
231 """
231 return '0' * 12
232 return '0' * 12
232
233
233
234
234 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
235 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
235 """
236 """
236 maps all found repositories into db
237 maps all found repositories into db
237 """
238 """
238 from pylons_app.model.repo_model import RepoModel
239 from pylons_app.model.repo_model import RepoModel
239
240
240 sa = meta.Session
241 sa = meta.Session
241 user = sa.query(User).filter(User.admin == True).first()
242 user = sa.query(User).filter(User.admin == True).first()
242
243
243 rm = RepoModel()
244 rm = RepoModel()
244
245
245 for name, repo in initial_repo_list.items():
246 for name, repo in initial_repo_list.items():
246 if not sa.query(Repository).filter(Repository.repo_name == name).scalar():
247 if not sa.query(Repository).filter(Repository.repo_name == name).scalar():
247 log.info('repository %s not found creating default', name)
248 log.info('repository %s not found creating default', name)
248
249
249 form_data = {
250 form_data = {
250 'repo_name':name,
251 'repo_name':name,
251 'description':repo.description if repo.description != 'unknown' else \
252 'description':repo.description if repo.description != 'unknown' else \
252 'auto description for %s' % name,
253 'auto description for %s' % name,
253 'private':False
254 'private':False
254 }
255 }
255 rm.create(form_data, user, just_db=True)
256 rm.create(form_data, user, just_db=True)
256
257
257
258
258 if remove_obsolete:
259 if remove_obsolete:
259 #remove from database those repositories that are not in the filesystem
260 #remove from database those repositories that are not in the filesystem
260 for repo in sa.query(Repository).all():
261 for repo in sa.query(Repository).all():
261 if repo.repo_name not in initial_repo_list.keys():
262 if repo.repo_name not in initial_repo_list.keys():
262 sa.delete(repo)
263 sa.delete(repo)
263 sa.commit()
264 sa.commit()
264
265
265
266
266 meta.Session.remove()
267 meta.Session.remove()
267
268
268 from UserDict import DictMixin
269 from UserDict import DictMixin
269
270
270 class OrderedDict(dict, DictMixin):
271 class OrderedDict(dict, DictMixin):
271
272
272 def __init__(self, *args, **kwds):
273 def __init__(self, *args, **kwds):
273 if len(args) > 1:
274 if len(args) > 1:
274 raise TypeError('expected at most 1 arguments, got %d' % len(args))
275 raise TypeError('expected at most 1 arguments, got %d' % len(args))
275 try:
276 try:
276 self.__end
277 self.__end
277 except AttributeError:
278 except AttributeError:
278 self.clear()
279 self.clear()
279 self.update(*args, **kwds)
280 self.update(*args, **kwds)
280
281
281 def clear(self):
282 def clear(self):
282 self.__end = end = []
283 self.__end = end = []
283 end += [None, end, end] # sentinel node for doubly linked list
284 end += [None, end, end] # sentinel node for doubly linked list
284 self.__map = {} # key --> [key, prev, next]
285 self.__map = {} # key --> [key, prev, next]
285 dict.clear(self)
286 dict.clear(self)
286
287
287 def __setitem__(self, key, value):
288 def __setitem__(self, key, value):
288 if key not in self:
289 if key not in self:
289 end = self.__end
290 end = self.__end
290 curr = end[1]
291 curr = end[1]
291 curr[2] = end[1] = self.__map[key] = [key, curr, end]
292 curr[2] = end[1] = self.__map[key] = [key, curr, end]
292 dict.__setitem__(self, key, value)
293 dict.__setitem__(self, key, value)
293
294
294 def __delitem__(self, key):
295 def __delitem__(self, key):
295 dict.__delitem__(self, key)
296 dict.__delitem__(self, key)
296 key, prev, next = self.__map.pop(key)
297 key, prev, next = self.__map.pop(key)
297 prev[2] = next
298 prev[2] = next
298 next[1] = prev
299 next[1] = prev
299
300
300 def __iter__(self):
301 def __iter__(self):
301 end = self.__end
302 end = self.__end
302 curr = end[2]
303 curr = end[2]
303 while curr is not end:
304 while curr is not end:
304 yield curr[0]
305 yield curr[0]
305 curr = curr[2]
306 curr = curr[2]
306
307
307 def __reversed__(self):
308 def __reversed__(self):
308 end = self.__end
309 end = self.__end
309 curr = end[1]
310 curr = end[1]
310 while curr is not end:
311 while curr is not end:
311 yield curr[0]
312 yield curr[0]
312 curr = curr[1]
313 curr = curr[1]
313
314
314 def popitem(self, last=True):
315 def popitem(self, last=True):
315 if not self:
316 if not self:
316 raise KeyError('dictionary is empty')
317 raise KeyError('dictionary is empty')
317 if last:
318 if last:
318 key = reversed(self).next()
319 key = reversed(self).next()
319 else:
320 else:
320 key = iter(self).next()
321 key = iter(self).next()
321 value = self.pop(key)
322 value = self.pop(key)
322 return key, value
323 return key, value
323
324
324 def __reduce__(self):
325 def __reduce__(self):
325 items = [[k, self[k]] for k in self]
326 items = [[k, self[k]] for k in self]
326 tmp = self.__map, self.__end
327 tmp = self.__map, self.__end
327 del self.__map, self.__end
328 del self.__map, self.__end
328 inst_dict = vars(self).copy()
329 inst_dict = vars(self).copy()
329 self.__map, self.__end = tmp
330 self.__map, self.__end = tmp
330 if inst_dict:
331 if inst_dict:
331 return (self.__class__, (items,), inst_dict)
332 return (self.__class__, (items,), inst_dict)
332 return self.__class__, (items,)
333 return self.__class__, (items,)
333
334
334 def keys(self):
335 def keys(self):
335 return list(self)
336 return list(self)
336
337
337 setdefault = DictMixin.setdefault
338 setdefault = DictMixin.setdefault
338 update = DictMixin.update
339 update = DictMixin.update
339 pop = DictMixin.pop
340 pop = DictMixin.pop
340 values = DictMixin.values
341 values = DictMixin.values
341 items = DictMixin.items
342 items = DictMixin.items
342 iterkeys = DictMixin.iterkeys
343 iterkeys = DictMixin.iterkeys
343 itervalues = DictMixin.itervalues
344 itervalues = DictMixin.itervalues
344 iteritems = DictMixin.iteritems
345 iteritems = DictMixin.iteritems
345
346
346 def __repr__(self):
347 def __repr__(self):
347 if not self:
348 if not self:
348 return '%s()' % (self.__class__.__name__,)
349 return '%s()' % (self.__class__.__name__,)
349 return '%s(%r)' % (self.__class__.__name__, self.items())
350 return '%s(%r)' % (self.__class__.__name__, self.items())
350
351
351 def copy(self):
352 def copy(self):
352 return self.__class__(self)
353 return self.__class__(self)
353
354
354 @classmethod
355 @classmethod
355 def fromkeys(cls, iterable, value=None):
356 def fromkeys(cls, iterable, value=None):
356 d = cls()
357 d = cls()
357 for key in iterable:
358 for key in iterable:
358 d[key] = value
359 d[key] = value
359 return d
360 return d
360
361
361 def __eq__(self, other):
362 def __eq__(self, other):
362 if isinstance(other, OrderedDict):
363 if isinstance(other, OrderedDict):
363 return len(self) == len(other) and self.items() == other.items()
364 return len(self) == len(other) and self.items() == other.items()
364 return dict.__eq__(self, other)
365 return dict.__eq__(self, other)
365
366
366 def __ne__(self, other):
367 def __ne__(self, other):
367 return not self == other
368 return not self == other
368
369
369 def make_test_env():
370 def make_test_env():
370 """Makes a fresh database from base64+zlib dump and
371 """Makes a fresh database from base64+zlib dump and
371 install test repository into tmp dir
372 install test repository into tmp dir
372 """
373 """
373 new_db_dump = """
374 new_db_dump = """
374 eJztXN1vE8sVn9nxR+wAIXDpFiiXjSAXfEOc2ElwQkVLPjYf5NNOAklUydrYG3tv1t5ldx0nuUJV\noL
375 eJztXN1vE8sVn9nxR+wAIXDpFiiXjSAXfEOc2ElwQkVLPjYf5NNOAklUydrYG3tv1t5ldx0nuUJV\noL
375 cPrVr1X7jSfUJ96nMfK1Xty23VqlWlPlRIlahUXbXqFUL0pTNjx5614xAoKEDmJ3t2zpkzM2fO\neHe+
376 cPrVr1X7jSfUJ96nMfK1Xty23VqlWlPlRIlahUXbXqFUL0pTNjx5614xAoKEDmJ3t2zpkzM2fO\neHe+
376 zno+PqU5qrRmWDnFkXqAB0AIbkkSAKANf8+BKprwFzI0G28ECXQ+PufFEYT+Tehz6L/oaSnK\nwcFxGP
377 zno+PqU5qrRmWDnFkXqAB0AIbkkSAKANf8+BKprwFzI0G28ECXQ+PufFEYT+Tehz6L/oaSnK\nwcFxGP
377 igFQfHjuMg4CehH7UA9Af0Y2ShWdSPLmOSg+N9x7U9eKf9PiC2nIWm4mTtri4nZ3Z9DE/5\nfOD0+RZY
378 igFQfHjuMg4CehH7UA9Af0Y2ShWdSPLmOSg+N9x7U9eKf9PiC2nIWm4mTtri4nZ3Z9DE/5\nfOD0+RZY
378 VFdXFVstWHoXPOPFvDbKU3TdKCbNgp39GLZ5MPtKW5WtWKmstqFmtqVtzZRWt6NQRFjk\ngkhESJ6kbe
379 VFdXFVstWHoXPOPFvDbKU3TdKCbNgp39GLZ5MPtKW5WtWKmstqFmtqVtzZRWt6NQRFjk\ngkhESJ6kbe
379 trim6rcFTAdcfuwqxhrNuprJLPqBnLKJhhSzWNpK1tq+aWkzXyN8wt3cjbScU0w7q2\nGqbyVSHYAXE5
380 trim6rcFTAdcfuwqxhrNuprJLPqBnLKJhhSzWNpK1tq+aWkzXyN8wt3cjbScU0w7q2\nGqbyVSHYAXE5
380 kSv15RTMtOKo2YxUikjf+SgKg4Dc/38C6Dn6Gn2FnqDH6K+Y5ODgeGfhRRD6/ST0\n+Ujo9ZLQ4yEhQi
381 kSv15RTMtOKo2YxUikjf+SgKg4Dc/38C6Dn6Gn2FnqDH6K+Y5ODgeGfhRRD6/ST0\n+Ujo9ZLQ4yEhQi
381 QUBBJCeFy4BLywHaCfCEXM+AJHOWpx39sMrux4IbzQ3gMc1XaSlpop6IoVvRxV\nLke6L4/cmx7vjedG
382 QUBBJCeFy4BLywHaCfCEXM+AJHOWpx39sMrux4IbzQ3gMc1XaSlpop6IoVvRxV\nLke6L4/cmx7vjedG
382 4qmVmXvTW5nl7PDaSmFEXR6ejC+YVrpnsNi1fn17fHldj06p6YH84tzaGKBF\n5ZWcSq66Uorn8Iih5W
383 4qmVmXvTW5nl7PDaSmFEXR6ejC+YVrpnsNi1fn17fHldj06p6YH84tzaGKBF\n5ZWcSq66Uorn8Iih5W
383 /ZBolqejhl5O57mkEPqf6sOFCq3lRsu2hYaayHrTplJeJD/Uu3p7u3Er19\nS4sb26PmemQiE54vLKfn
384 /ZBolqejhl5O57mkEPqf6sOFCq3lRsu2hYaayHrTplJeJD/Uu3p7u3Er19\nS4sb26PmemQiE54vLKfn
384 I8Wx2/Nd+XurmbH4TOpupHdk25I/sYbmCgDQstK0oHLdpWGmc1U3MqR6\nbICF123RHb/QDNpIm1rFnk
385 I8Wx2/Nd+XurmbH4TOpupHdk25I/sYbmCgDQstK0oHLdpWGmc1U3MqR6\nbICF123RHb/QDNpIm1rFnk
385 HaJiWd0/Llpgzq41lzIJMrjMXi2/JmdyGxMDKnjs1FR9WMcduMb3TZ\nfZuZTXVs1uiS53NxY9yan4Vw
386 HaJiWd0/Llpgzq41lzIJMrjMXi2/JmdyGxMDKnjs1FR9WMcduMb3TZ\nfZuZTXVs1uiS53NxY9yan4Vw
386 PDNICqEl3dKNlKJnDdshbYh2R7o7uwc6I1EpGr3RHbvREwn3D/T3\nd/fuBFAzaHdpUu7csi6Tw4ou94
387 PDNICqEl3dKNlKJnDdshbYh2R7o7uwc6I1EpGr3RHbvREwn3D/T3\nd/fuBFAzaHdpUu7csi6Tw4ou94
387 zOLt3JxTNZo7g8muvV1Lg6sNj/SX4dD7srqenpfCJ6d3g5vKRM\njq/Ob3VHIXgJXaKx8PWBvoHrvfdg
388 zOLt3JxTNZo7g8muvV1Lg6sNj/SX4dD7srqenpfCJ6d3g5vKRM\njq/Ob3VHIXgJXaKx8PWBvoHrvfdg
388 MzhPVDl/vgek1TWloO927tbUdsqeNzfurK5Frq+v5NbHZ1bG\nCnZxdnxxbGStmOudnwub6+rQYNxZku
389 MzhPVDl/vgek1TWloO927tbUdsqeNzfurK5Frq+v5NbHZ1bG\nCnZxdnxxbGStmOudnwub6+rQYNxZku
389 Wh28Ph9Nos2C3EfblVvhJlyPjvRY+Z8f91dzUHB8fhYf/x\nv3T/PwL47v87+iX6I45ycHC8dWhFV6Br
390 Wh28Ph9Nos2C3EfblVvhJlyPjvRY+Z8f91dzUHB8fhYf/x\nv3T/PwL47v87+iX6I45ycHC8dWhFV6Br
390 7ukVUQ/cYzroOYnaXZLoBGqD1TmW0IzOw/IUAJL9v6Dg\nA+jP6Ofo+yiBelFA+IvwC2EFMzmOCBJBD/
391 7ukVUQ/cYzroOYnaXZLoBGqD1TmW0IzOw/IUAJL9v6Dg\nA+jP6Ofo+yiBelFA+IvwC2EFMzmOCBJBD/
391 huMZsJ41+MZjuqFVYKjpFUUo62oThqosyV8mpRKtg4\nUtScrJTNdCqmSeNGwZFIFqmcRTPydwIeMPwp
392 huMZsJ41+MZjuqFVYKjpFUUo62oThqosyV8mpRKtg4\nUtScrJTNdCqmSeNGwZFIFqmcRTPydwIeMPwp
392 W2ZOyRcU/SVLLWViym1v8oDOLrbcvJGvFpbWbGVV\nV9NhvweEZCyWslRcWVnINGzNMawtiXJxaRX5kM
393 W2ZOyRcU/SVLLWViym1v8oDOLrbcvJGvFpbWbGVV\nV9NhvweEZCyWslRcWVnINGzNMawtiXJxaRX5kM
393 8D+rqq8lZFtjaX+i2vB1zoxKL0dhrPSHSmj6u3\nFCzV4cH6fbuavSTFFEJp3KCUatsdqEa4aGkOqyel
394 8D+rqq8lZFtjaX+i2vB1zoxKL0dhrPSHSmj6u3\nFCzV4cH6fbuavSTFFEJp3KCUatsdqEa4aGkOqyel
394 y8IhwQM6BhhhrE2akSVkWfQKxKJ9jGhN8/NG\nWZCM/0H0q5r9P/Q79FvM5ODgeOtBZvLBIAkDARI2Nb
395 y8IhwQM6BhhhrE2akSVkWfQKxKJ9jGhN8/NG\nWZCM/0H0q5r9P/Q79FvM5ODgeOtBZvLBIAkDARI2Nb
395 3E/h/O7wdDAAzBj+Cy8IXwpfAc/eZlat9R\noF+8eBE+bHXIgzSbIQcTyYJWiQjDCXlwQZYWBoemZKnC
396 3E/h/O7wdDAAzBj+Cy8IXwpfAc/eZlat9R\noF+8eBE+bHXIgzSbIQcTyYJWiQjDCXlwQZYWBoemZKnC
396 lq4GAwUtqaWliZkFeUxOSDOzC9LM4tTU\nNYmm2GqKPqEX5KWFMmtd3WLJDUUvqCyDjhKqNDQ7OyUPzh
397 lq4GAwUtqaWliZkFeUxOSDOzC9LM4tTU\nNYmm2GqKPqEX5KWFMmtd3WLJDUUvqCyDjhKqNDQ7OyUPzh
397 DmXGJiejCxLE3Ky9JVWl2IsBdnJuKL\nMssZHpeHJymjXMjEjHS1+5oUCYWCoYjgE+WLEGj5tLpp39Px
398 DmXGJiejCxLE3Ky9JVWl2IsBdnJuKL\nMssZHpeHJymjXMjEjHS1+5oUCYWCoYjgE+WLEGj5tLpp39Px
398 MzlJhjtKJytNSkYqUfRgHPlFUYQ/\nMKhZyPhm08DjMgdlUVPgSENj4DSyN1hp6u6Er8Kob3hplGEYrg
399 MzlJhjtKJytNSkYqUfRgHPlFUYQ/\nMKhZyPhm08DjMgdlUVPgSENj4DSyN1hp6u6Er8Kob3hplGEYrg
399 J2dxsrDLrZ6EpO6kYGlzCCdV2Y\nmJbrjVlS2G1Ohlc2aJ012TSqozuJLYpoiK0f8vjEm2Ij61MLJiP0
400 J2dxsrDLrZ6EpO6kYGlzCCdV2Y\nmJbrjVlS2G1Ohlc2aJ012TSqozuJLYpoiK0f8vjEm2Ij61MLJiP0
400 4g15XywapRffzpTPL166BB8k\naQeZqpXTbBv/4Gwm6nd1FpNAuqxKNuo4RsLdf1W+buQzrjSXkV1VuO
401 4g15XywapRffzpTPL166BB8k\naQeZqpXTbBv/4Gwm6nd1FpNAuqxKNuo4RsLdf1W+buQzrjSXkV1VuO
401 zjTgmG+vw+ceJSo5Yzmicj\nDNFE7n8BfQnQ33DAwcHxLqMFLxHEs47mkIGYrKM+xAsBMYZXBnquvLDC
402 zjTgmG+vw+ceJSo5Yzmicj\nDNFE7n8BfQnQ33DAwcHxLqMFLxHEs47mkIGYrKM+xAsBMYZXBnquvLDC
402 D4Wsmne0FF3/kPm/gL6m\n8//DVp6Dg+PNo3b+7wOPAHgEH8F/CFfRT9GvD1u/vbFzv8kvdnTAhxF2nW
403 D4Wsmne0FF3/kPm/gL6m\n8//DVp6Dg+PNo3b+7wOPAHgEH8F/CFfRT9GvD1u/vbFzv8kvdnTAhxF2nW
403 GrjqPlM3YNGdxrzbGb\nSOZuLN1o9uaScc3RXCnuVYhr+lZTi2sCd+C08iz4ZsAnxjtesAapZIrUMJpv
404 GrjqPlM3YNGdxrzbGb\nSOZuLN1o9uaScc3RXCnuVYhr+lZTi2sCd+C08iz4ZsAnxjtesAapZIrUMJpv
404 Bl8me7SGcfxBqtkv\ntrfDzwLU+pWdJU212fgJl93ZFGJ06qPWwNg0rWLkuuVPwxm2RfcS2YVOWrVTlm
405 Bl8me7SGcfxBqtkv\ntrfDzwLU+pWdJU212fgJl93ZFGJ06qPWwNg0rWLkuuVPwxm2RfcS2YVOWrVTlm
405 a61o6uXimr4bJ4\npfp67r6So7MJeWJshhRcWf1ICXlUTsgzw/L87vpuj4XRrubsOjN2zCdOtjfqJNac
406 a61o6uXimr4bJ4\npfp67r6So7MJeWJshhRcWf1ICXlUTsgzw/L87vpuj4XRrubsOjN2zCdOtjfqJNac
406 yQhLtcSOHzhj\nlKVOlsb/fwL0FAccHBzvLQJIhHRpIJAYXRPQ8R+i3wP84eDgeNfRCX3gAoRjGyk7Sc
407 yQhLtcSOHzhj\nlKVOlsb/fwL0FAccHBzvLQJIhHRpIJAYXRPQ8R+i3wP84eDgeNfRCX3gAoRjGyk7Sc
407 78BUDPZdlJ\n0ZphSbvJZPyH6D8Afzg4ON5/HEMX4O7tD0v3/3OAPxwcHEcG1f0/hJ4A9Az9C184ODje
408 78BUDPZdlJ\n0ZphSbvJZPyH6D8Afzg4ON5/HEMX4O7tD0v3/3OAPxwcHEcG1f0/hJ4A9Az9C184ODje
408 Q/gQ+WcP\nKPgEevX5IL0GyPiP0Fdl/7/D1pKDg+PNYe/3f+j4/wSP/88OWz8ODo43Ab+H3O0CKl19Qu
409 Q/gQ+WcP\nKPgEevX5IL0GyPiP0Fdl/7/D1pKDg+PNYe/3f+j4/wSP/88OWz8ODo43Ab+H3O0CKl19Qu
409 kaoPN/\nD/gcgM+FD4W7ws8OW886PNg+UTp4jlX8aJOOQR0a2XhrnVftbkrFubZM7+dkewA/zgYS9a6x
410 kaoPN/\nD/gcgM+FD4W7ws8OW886PNg+UTp4jlX8aJOOQR0a2XhrnVftbkrFubZM7+dkewA/zgYS9a6x
410 1erq\nXWRr0thDZLdfJ3uU7PI+rXcMfYWT6Bq33WtSrVNprGW/Y2VXUyIsdSp28sAZoyx1+kGulXqTfx
411 1erq\nXWRr0thDZLdfJ3uU7PI+rXcMfYWT6Bq33WtSrVNprGW/Y2VXUyIsdSp28sAZoyx1+kGulXqTfx
411 aq\ndrduZOxK5Ex9RxN2pZcx8So9XEozKw4D1Vdn6v0RFLdfeolM0r/U2d9buqRbvekZ/iv0IpulqrYr
412 aq\ndrduZOxK5Ex9RxN2pZcx8So9XEozKw4D1Vdn6v0RFLdfeolM0r/U2d9buqRbvekZ/iv0IpulqrYr
412 \nl9sRo+rBEAyR+x8/ADg4OI4gyPyf3/8cHEcTJf+fpwB/ODg4jgSaoBfQ/QB+/s/BcSRR3f+H6Bng\n
413 \nl9sRo+rBEAyR+x8/ADg4OI4gyPyf3/8cHEcTJf+fpwB/ODg4jgSaoBfQ/QB+/s/BcSRR3f+H6Bng\n
413 e/8cHEcHpf1/CI+jHwEP3AToLtx8e9/9e//w8Hun6bHGDz+tvE+3uwfOxsW69+nYYw2WfjPHGtX9\n5A
414 e/8cHEcHpf1/CI+jHwEP3AToLtx8e9/9e//w8Hun6bHGDz+tvE+3uwfOxsW69+nYYw2WfjPHGtX9\n5A
414 MdfNQo9P+eS7youNdyVuJq4ot2zRsdnLgLCYYip/b7w5jKqUX51IREv4F/FJ7YBy96ja963sJS\n34yd
415 MdfNQo9P+eS7youNdyVuJq4ot2zRsdnLgLCYYip/b7w5jKqUX51IREv4F/FJ7YBy96ja963sJS\n34yd
415 OXDGKEud/R8efZUt\n
416 OXDGKEud/R8efZUt\n
416 """
417 """
417 newdb = open('test.db','wb')
418 newdb = open('test.db', 'wb')
418 newdb.write(new_db_dump.decode('base64').decode('zlib'))
419 newdb.write(new_db_dump.decode('base64').decode('zlib'))
419 newdb.close()
420 newdb.close()
420
421
421
422
422 #PART TWO make test repo
423 #PART TWO make test repo
423 if os.path.isdir('/tmp/vcs_test'):
424 if os.path.isdir('/tmp/vcs_test'):
424 shutil.rmtree('/tmp/vcs_test')
425 shutil.rmtree('/tmp/vcs_test')
425
426
426 cur_dir = dn(dn(os.path.abspath(__file__)))
427 cur_dir = dn(dn(os.path.abspath(__file__)))
427 tar = tarfile.open(jn(cur_dir,'tests',"vcs_test.tar.gz"))
428 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test.tar.gz"))
428 tar.extractall('/tmp')
429 tar.extractall('/tmp')
429 tar.close()
430 tar.close()
430
431
431
432
432 No newline at end of file
433
@@ -1,347 +1,351 b''
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 from formencode import All
22 from formencode import All
23 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
23 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
24 Email, Bool, StringBoolean
24 Email, Bool, StringBoolean
25 from pylons import session
25 from pylons import session
26 from pylons.i18n.translation import _
26 from pylons.i18n.translation import _
27 from pylons_app.lib.auth import check_password, get_crypt_password
27 from pylons_app.lib.auth import check_password, get_crypt_password
28 from pylons_app.model import meta
28 from pylons_app.model import meta
29 from pylons_app.model.user_model import UserModel
29 from pylons_app.model.user_model import UserModel
30 from pylons_app.model.db import User, Repository
30 from pylons_app.model.db import User, Repository
31 from sqlalchemy.exc import OperationalError
31 from sqlalchemy.exc import OperationalError
32 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
32 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
33 from webhelpers.pylonslib.secure_form import authentication_token
33 from webhelpers.pylonslib.secure_form import authentication_token
34 import formencode
34 import formencode
35 import logging
35 import logging
36 import os
36 import os
37 import pylons_app.lib.helpers as h
37 import pylons_app.lib.helpers as h
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 #this is needed to translate the messages using _() in validators
41 #this is needed to translate the messages using _() in validators
42 class State_obj(object):
42 class State_obj(object):
43 _ = staticmethod(_)
43 _ = staticmethod(_)
44
44
45 #===============================================================================
45 #===============================================================================
46 # VALIDATORS
46 # VALIDATORS
47 #===============================================================================
47 #===============================================================================
48 class ValidAuthToken(formencode.validators.FancyValidator):
48 class ValidAuthToken(formencode.validators.FancyValidator):
49 messages = {'invalid_token':_('Token mismatch')}
49 messages = {'invalid_token':_('Token mismatch')}
50
50
51 def validate_python(self, value, state):
51 def validate_python(self, value, state):
52
52
53 if value != authentication_token():
53 if value != authentication_token():
54 raise formencode.Invalid(self.message('invalid_token', state,
54 raise formencode.Invalid(self.message('invalid_token', state,
55 search_number=value), value, state)
55 search_number=value), value, state)
56
56
57 def ValidUsername(edit, old_data):
57 def ValidUsername(edit, old_data):
58 class _ValidUsername(formencode.validators.FancyValidator):
58 class _ValidUsername(formencode.validators.FancyValidator):
59
59
60 def validate_python(self, value, state):
60 def validate_python(self, value, state):
61 if value in ['default', 'new_user']:
61 if value in ['default', 'new_user']:
62 raise formencode.Invalid(_('Invalid username'), value, state)
62 raise formencode.Invalid(_('Invalid username'), value, state)
63 #check if user is uniq
63 #check if user is uniq
64 sa = meta.Session
64 sa = meta.Session
65 old_un = None
65 old_un = None
66 if edit:
66 if edit:
67 old_un = sa.query(User).get(old_data.get('user_id')).username
67 old_un = sa.query(User).get(old_data.get('user_id')).username
68
68
69 if old_un != value or not edit:
69 if old_un != value or not edit:
70 if sa.query(User).filter(User.username == value).scalar():
70 if sa.query(User).filter(User.username == value).scalar():
71 raise formencode.Invalid(_('This username already exists') ,
71 raise formencode.Invalid(_('This username already exists') ,
72 value, state)
72 value, state)
73 meta.Session.remove()
73 meta.Session.remove()
74
74
75 return _ValidUsername
75 return _ValidUsername
76
76
77 class ValidPassword(formencode.validators.FancyValidator):
77 class ValidPassword(formencode.validators.FancyValidator):
78
78
79 def to_python(self, value, state):
79 def to_python(self, value, state):
80 if value:
80 if value:
81 return get_crypt_password(value)
81 return get_crypt_password(value)
82
82
83 class ValidAuth(formencode.validators.FancyValidator):
83 class ValidAuth(formencode.validators.FancyValidator):
84 messages = {
84 messages = {
85 'invalid_password':_('invalid password'),
85 'invalid_password':_('invalid password'),
86 'invalid_login':_('invalid user name'),
86 'invalid_login':_('invalid user name'),
87 'disabled_account':_('Your acccount is disabled')
87 'disabled_account':_('Your acccount is disabled')
88
88
89 }
89 }
90 #error mapping
90 #error mapping
91 e_dict = {'username':messages['invalid_login'],
91 e_dict = {'username':messages['invalid_login'],
92 'password':messages['invalid_password']}
92 'password':messages['invalid_password']}
93 e_dict_disable = {'username':messages['disabled_account']}
93 e_dict_disable = {'username':messages['disabled_account']}
94
94
95 def validate_python(self, value, state):
95 def validate_python(self, value, state):
96 password = value['password']
96 password = value['password']
97 username = value['username']
97 username = value['username']
98 user = UserModel().get_user_by_name(username)
98 user = UserModel().get_user_by_name(username)
99 if user is None:
99 if user is None:
100 raise formencode.Invalid(self.message('invalid_password',
100 raise formencode.Invalid(self.message('invalid_password',
101 state=State_obj), value, state,
101 state=State_obj), value, state,
102 error_dict=self.e_dict)
102 error_dict=self.e_dict)
103 if user:
103 if user:
104 if user.active:
104 if user.active:
105 if user.username == username and check_password(password,
105 if user.username == username and check_password(password,
106 user.password):
106 user.password):
107 return value
107 return value
108 else:
108 else:
109 log.warning('user %s not authenticated', username)
109 log.warning('user %s not authenticated', username)
110 raise formencode.Invalid(self.message('invalid_password',
110 raise formencode.Invalid(self.message('invalid_password',
111 state=State_obj), value, state,
111 state=State_obj), value, state,
112 error_dict=self.e_dict)
112 error_dict=self.e_dict)
113 else:
113 else:
114 log.warning('user %s is disabled', username)
114 log.warning('user %s is disabled', username)
115 raise formencode.Invalid(self.message('disabled_account',
115 raise formencode.Invalid(self.message('disabled_account',
116 state=State_obj),
116 state=State_obj),
117 value, state,
117 value, state,
118 error_dict=self.e_dict_disable)
118 error_dict=self.e_dict_disable)
119
119
120 class ValidRepoUser(formencode.validators.FancyValidator):
120 class ValidRepoUser(formencode.validators.FancyValidator):
121
121
122 def to_python(self, value, state):
122 def to_python(self, value, state):
123 try:
123 try:
124 self.user_db = meta.Session.query(User)\
124 self.user_db = meta.Session.query(User)\
125 .filter(User.active == True)\
125 .filter(User.active == True)\
126 .filter(User.username == value).one()
126 .filter(User.username == value).one()
127 except Exception:
127 except Exception:
128 raise formencode.Invalid(_('This username is not valid'),
128 raise formencode.Invalid(_('This username is not valid'),
129 value, state)
129 value, state)
130 finally:
130 finally:
131 meta.Session.remove()
131 meta.Session.remove()
132
132
133 return self.user_db.user_id
133 return self.user_db.user_id
134
134
135 def ValidRepoName(edit, old_data):
135 def ValidRepoName(edit, old_data):
136 class _ValidRepoName(formencode.validators.FancyValidator):
136 class _ValidRepoName(formencode.validators.FancyValidator):
137
137
138 def to_python(self, value, state):
138 def to_python(self, value, state):
139 slug = h.repo_name_slug(value)
139 slug = h.repo_name_slug(value)
140 if slug in ['_admin']:
140 if slug in ['_admin']:
141 raise formencode.Invalid(_('This repository name is disallowed'),
141 raise formencode.Invalid(_('This repository name is disallowed'),
142 value, state)
142 value, state)
143 if old_data.get('repo_name') != value or not edit:
143 if old_data.get('repo_name') != value or not edit:
144 sa = meta.Session
144 sa = meta.Session
145 if sa.query(Repository).filter(Repository.repo_name == slug).scalar():
145 if sa.query(Repository).filter(Repository.repo_name == slug).scalar():
146 raise formencode.Invalid(_('This repository already exists') ,
146 raise formencode.Invalid(_('This repository already exists') ,
147 value, state)
147 value, state)
148 meta.Session.remove()
148 meta.Session.remove()
149 return slug
149 return slug
150
150
151
151
152 return _ValidRepoName
152 return _ValidRepoName
153
153
154 class ValidPerms(formencode.validators.FancyValidator):
154 class ValidPerms(formencode.validators.FancyValidator):
155 messages = {'perm_new_user_name':_('This username is not valid')}
155 messages = {'perm_new_user_name':_('This username is not valid')}
156
156
157 def to_python(self, value, state):
157 def to_python(self, value, state):
158 perms_update = []
158 perms_update = []
159 perms_new = []
159 perms_new = []
160 #build a list of permission to update and new permission to create
160 #build a list of permission to update and new permission to create
161 for k, v in value.items():
161 for k, v in value.items():
162 if k.startswith('perm_'):
162 if k.startswith('perm_'):
163 if k.startswith('perm_new_user'):
163 if k.startswith('perm_new_user'):
164 new_perm = value.get('perm_new_user', False)
164 new_perm = value.get('perm_new_user', False)
165 new_user = value.get('perm_new_user_name', False)
165 new_user = value.get('perm_new_user_name', False)
166 if new_user and new_perm:
166 if new_user and new_perm:
167 if (new_user, new_perm) not in perms_new:
167 if (new_user, new_perm) not in perms_new:
168 perms_new.append((new_user, new_perm))
168 perms_new.append((new_user, new_perm))
169 else:
169 else:
170 usr = k[5:]
170 usr = k[5:]
171 if usr == 'default':
171 if usr == 'default':
172 if value['private']:
172 if value['private']:
173 #set none for default when updating to private repo
173 #set none for default when updating to private repo
174 v = 'repository.none'
174 v = 'repository.none'
175 perms_update.append((usr, v))
175 perms_update.append((usr, v))
176 value['perms_updates'] = perms_update
176 value['perms_updates'] = perms_update
177 value['perms_new'] = perms_new
177 value['perms_new'] = perms_new
178 sa = meta.Session
178 sa = meta.Session
179 for k, v in perms_new:
179 for k, v in perms_new:
180 try:
180 try:
181 self.user_db = sa.query(User)\
181 self.user_db = sa.query(User)\
182 .filter(User.active == True)\
182 .filter(User.active == True)\
183 .filter(User.username == k).one()
183 .filter(User.username == k).one()
184 except Exception:
184 except Exception:
185 msg = self.message('perm_new_user_name',
185 msg = self.message('perm_new_user_name',
186 state=State_obj)
186 state=State_obj)
187 raise formencode.Invalid(msg, value, state, error_dict={'perm_new_user_name':msg})
187 raise formencode.Invalid(msg, value, state, error_dict={'perm_new_user_name':msg})
188 return value
188 return value
189
189
190 class ValidSettings(formencode.validators.FancyValidator):
190 class ValidSettings(formencode.validators.FancyValidator):
191
191
192 def to_python(self, value, state):
192 def to_python(self, value, state):
193 #settings form can't edit user
193 #settings form can't edit user
194 if value.has_key('user'):
194 if value.has_key('user'):
195 del['value']['user']
195 del['value']['user']
196
196
197 return value
197 return value
198
198
199 class ValidPath(formencode.validators.FancyValidator):
199 class ValidPath(formencode.validators.FancyValidator):
200 def to_python(self, value, state):
200 def to_python(self, value, state):
201 isdir = os.path.isdir(value.replace('*', ''))
201 isdir = os.path.isdir(value.replace('*', ''))
202 if (value.endswith('/*') or value.endswith('/**')) and isdir:
202 if (value.endswith('/*') or value.endswith('/**')) and isdir:
203 return value
203 return value
204 elif not isdir:
204 elif not isdir:
205 msg = _('This is not a valid path')
205 msg = _('This is not a valid path')
206 else:
206 else:
207 msg = _('You need to specify * or ** at the end of path (ie. /tmp/*)')
207 msg = _('You need to specify * or ** at the end of path (ie. /tmp/*)')
208
208
209 raise formencode.Invalid(msg, value, state,
209 raise formencode.Invalid(msg, value, state,
210 error_dict={'paths_root_path':msg})
210 error_dict={'paths_root_path':msg})
211
211
212 class UniqSystemEmail(formencode.validators.FancyValidator):
212 def UniqSystemEmail(old_data):
213 def to_python(self, value, state):
213 class _UniqSystemEmail(formencode.validators.FancyValidator):
214 sa = meta.Session
214 def to_python(self, value, state):
215 try:
215 if old_data['email'] != value:
216 user = sa.query(User).filter(User.email == value).scalar()
216 sa = meta.Session
217 if user:
217 try:
218 raise formencode.Invalid(_("That e-mail address is already taken") ,
218 user = sa.query(User).filter(User.email == value).scalar()
219 value, state)
219 if user:
220 finally:
220 raise formencode.Invalid(_("That e-mail address is already taken") ,
221 meta.Session.remove()
221 value, state)
222
222 finally:
223 return value
223 meta.Session.remove()
224
225 return value
226
227 return _UniqSystemEmail
224
228
225 class ValidSystemEmail(formencode.validators.FancyValidator):
229 class ValidSystemEmail(formencode.validators.FancyValidator):
226 def to_python(self, value, state):
230 def to_python(self, value, state):
227 sa = meta.Session
231 sa = meta.Session
228 try:
232 try:
229 user = sa.query(User).filter(User.email == value).scalar()
233 user = sa.query(User).filter(User.email == value).scalar()
230 if user is None:
234 if user is None:
231 raise formencode.Invalid(_("That e-mail address doesn't exist.") ,
235 raise formencode.Invalid(_("That e-mail address doesn't exist.") ,
232 value, state)
236 value, state)
233 finally:
237 finally:
234 meta.Session.remove()
238 meta.Session.remove()
235
239
236 return value
240 return value
237
241
238 #===============================================================================
242 #===============================================================================
239 # FORMS
243 # FORMS
240 #===============================================================================
244 #===============================================================================
241 class LoginForm(formencode.Schema):
245 class LoginForm(formencode.Schema):
242 allow_extra_fields = True
246 allow_extra_fields = True
243 filter_extra_fields = True
247 filter_extra_fields = True
244 username = UnicodeString(
248 username = UnicodeString(
245 strip=True,
249 strip=True,
246 min=3,
250 min=3,
247 not_empty=True,
251 not_empty=True,
248 messages={
252 messages={
249 'empty':_('Please enter a login'),
253 'empty':_('Please enter a login'),
250 'tooShort':_('Enter a value %(min)i characters long or more')}
254 'tooShort':_('Enter a value %(min)i characters long or more')}
251 )
255 )
252
256
253 password = UnicodeString(
257 password = UnicodeString(
254 strip=True,
258 strip=True,
255 min=3,
259 min=3,
256 not_empty=True,
260 not_empty=True,
257 messages={
261 messages={
258 'empty':_('Please enter a password'),
262 'empty':_('Please enter a password'),
259 'tooShort':_('Enter a value %(min)i characters long or more')}
263 'tooShort':_('Enter a value %(min)i characters long or more')}
260 )
264 )
261
265
262
266
263 #chained validators have access to all data
267 #chained validators have access to all data
264 chained_validators = [ValidAuth]
268 chained_validators = [ValidAuth]
265
269
266 def UserForm(edit=False, old_data={}):
270 def UserForm(edit=False, old_data={}):
267 class _UserForm(formencode.Schema):
271 class _UserForm(formencode.Schema):
268 allow_extra_fields = True
272 allow_extra_fields = True
269 filter_extra_fields = True
273 filter_extra_fields = True
270 username = All(UnicodeString(strip=True, min=3, not_empty=True), ValidUsername(edit, old_data))
274 username = All(UnicodeString(strip=True, min=3, not_empty=True), ValidUsername(edit, old_data))
271 if edit:
275 if edit:
272 new_password = All(UnicodeString(strip=True, min=3, not_empty=False), ValidPassword)
276 new_password = All(UnicodeString(strip=True, min=3, not_empty=False), ValidPassword)
273 admin = StringBoolean(if_missing=False)
277 admin = StringBoolean(if_missing=False)
274 else:
278 else:
275 password = All(UnicodeString(strip=True, min=8, not_empty=True), ValidPassword)
279 password = All(UnicodeString(strip=True, min=8, not_empty=True), ValidPassword)
276 active = StringBoolean(if_missing=False)
280 active = StringBoolean(if_missing=False)
277 name = UnicodeString(strip=True, min=3, not_empty=True)
281 name = UnicodeString(strip=True, min=3, not_empty=True)
278 lastname = UnicodeString(strip=True, min=3, not_empty=True)
282 lastname = UnicodeString(strip=True, min=3, not_empty=True)
279 email = All(Email(not_empty=True), UniqSystemEmail())
283 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
280
284
281 return _UserForm
285 return _UserForm
282
286
283 RegisterForm = UserForm
287 RegisterForm = UserForm
284
288
285 def PasswordResetForm():
289 def PasswordResetForm():
286 class _PasswordResetForm(formencode.Schema):
290 class _PasswordResetForm(formencode.Schema):
287 allow_extra_fields = True
291 allow_extra_fields = True
288 filter_extra_fields = True
292 filter_extra_fields = True
289 email = All(ValidSystemEmail(), Email(not_empty=True))
293 email = All(ValidSystemEmail(), Email(not_empty=True))
290 return _PasswordResetForm
294 return _PasswordResetForm
291
295
292 def RepoForm(edit=False, old_data={}):
296 def RepoForm(edit=False, old_data={}):
293 class _RepoForm(formencode.Schema):
297 class _RepoForm(formencode.Schema):
294 allow_extra_fields = True
298 allow_extra_fields = True
295 filter_extra_fields = False
299 filter_extra_fields = False
296 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
300 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
297 description = UnicodeString(strip=True, min=3, not_empty=True)
301 description = UnicodeString(strip=True, min=3, not_empty=True)
298 private = StringBoolean(if_missing=False)
302 private = StringBoolean(if_missing=False)
299
303
300 if edit:
304 if edit:
301 user = All(Int(not_empty=True), ValidRepoUser)
305 user = All(Int(not_empty=True), ValidRepoUser)
302
306
303 chained_validators = [ValidPerms]
307 chained_validators = [ValidPerms]
304 return _RepoForm
308 return _RepoForm
305
309
306 def RepoSettingsForm(edit=False, old_data={}):
310 def RepoSettingsForm(edit=False, old_data={}):
307 class _RepoForm(formencode.Schema):
311 class _RepoForm(formencode.Schema):
308 allow_extra_fields = True
312 allow_extra_fields = True
309 filter_extra_fields = False
313 filter_extra_fields = False
310 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
314 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
311 description = UnicodeString(strip=True, min=3, not_empty=True)
315 description = UnicodeString(strip=True, min=3, not_empty=True)
312 private = StringBoolean(if_missing=False)
316 private = StringBoolean(if_missing=False)
313
317
314 chained_validators = [ValidPerms, ValidSettings]
318 chained_validators = [ValidPerms, ValidSettings]
315 return _RepoForm
319 return _RepoForm
316
320
317
321
318 def ApplicationSettingsForm():
322 def ApplicationSettingsForm():
319 class _ApplicationSettingsForm(formencode.Schema):
323 class _ApplicationSettingsForm(formencode.Schema):
320 allow_extra_fields = True
324 allow_extra_fields = True
321 filter_extra_fields = False
325 filter_extra_fields = False
322 hg_app_title = UnicodeString(strip=True, min=3, not_empty=True)
326 hg_app_title = UnicodeString(strip=True, min=3, not_empty=True)
323 hg_app_realm = UnicodeString(strip=True, min=3, not_empty=True)
327 hg_app_realm = UnicodeString(strip=True, min=3, not_empty=True)
324
328
325 return _ApplicationSettingsForm
329 return _ApplicationSettingsForm
326
330
327 def ApplicationUiSettingsForm():
331 def ApplicationUiSettingsForm():
328 class _ApplicationUiSettingsForm(formencode.Schema):
332 class _ApplicationUiSettingsForm(formencode.Schema):
329 allow_extra_fields = True
333 allow_extra_fields = True
330 filter_extra_fields = False
334 filter_extra_fields = False
331 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
335 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
332 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=3, not_empty=True))
336 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=3, not_empty=True))
333 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
337 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
334 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
338 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
335
339
336 return _ApplicationUiSettingsForm
340 return _ApplicationUiSettingsForm
337
341
338 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
342 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
339 class _DefaultPermissionsForm(formencode.Schema):
343 class _DefaultPermissionsForm(formencode.Schema):
340 allow_extra_fields = True
344 allow_extra_fields = True
341 filter_extra_fields = True
345 filter_extra_fields = True
342 overwrite_default = OneOf(['true', 'false'], if_missing='false')
346 overwrite_default = OneOf(['true', 'false'], if_missing='false')
343 default_perm = OneOf(perms_choices)
347 default_perm = OneOf(perms_choices)
344 default_register = OneOf(register_choices)
348 default_register = OneOf(register_choices)
345 default_create = OneOf(create_choices)
349 default_create = OneOf(create_choices)
346
350
347 return _DefaultPermissionsForm
351 return _DefaultPermissionsForm
@@ -1,508 +1,508 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('Mercurial Repository Overview')}
4 ${_('Mercurial Repository Overview')}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(u'Home',h.url('/'))}
8 ${h.link_to(u'Home',h.url('/'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
10 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
11 &raquo;
11 &raquo;
12 ${_('summary')}
12 ${_('summary')}
13 </%def>
13 </%def>
14
14
15 <%def name="page_nav()">
15 <%def name="page_nav()">
16 ${self.menu('summary')}
16 ${self.menu('summary')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <script type="text/javascript">
20 <script type="text/javascript">
21 var E = YAHOO.util.Event;
21 var E = YAHOO.util.Event;
22 var D = YAHOO.util.Dom;
22 var D = YAHOO.util.Dom;
23
23
24 E.onDOMReady(function(e){
24 E.onDOMReady(function(e){
25 id = 'clone_url';
25 id = 'clone_url';
26 E.addListener(id,'click',function(e){
26 E.addListener(id,'click',function(e){
27 D.get('clone_url').select();
27 D.get('clone_url').select();
28 })
28 })
29 })
29 })
30 </script>
30 </script>
31 <div class="box box-left">
31 <div class="box box-left">
32 <!-- box / title -->
32 <!-- box / title -->
33 <div class="title">
33 <div class="title">
34 ${self.breadcrumbs()}
34 ${self.breadcrumbs()}
35 </div>
35 </div>
36 <!-- end box / title -->
36 <!-- end box / title -->
37 <div class="form">
37 <div class="form">
38 <div class="fields">
38 <div class="fields">
39
39
40 <div class="field">
40 <div class="field">
41 <div class="label">
41 <div class="label">
42 <label>${_('Name')}:</label>
42 <label>${_('Name')}:</label>
43 </div>
43 </div>
44 <div class="input-short">
44 <div class="input-short">
45 <span style="font-size: 1.6em;font-weight: bold">${c.repo_info.name}</span>
45 <span style="font-size: 1.6em;font-weight: bold">${c.repo_info.name}</span>
46 </div>
46 </div>
47 </div>
47 </div>
48
48
49
49
50 <div class="field">
50 <div class="field">
51 <div class="label">
51 <div class="label">
52 <label>${_('Description')}:</label>
52 <label>${_('Description')}:</label>
53 </div>
53 </div>
54 <div class="input-short">
54 <div class="input-short">
55 ${c.repo_info.description}
55 ${c.repo_info.description}
56 </div>
56 </div>
57 </div>
57 </div>
58
58
59
59
60 <div class="field">
60 <div class="field">
61 <div class="label">
61 <div class="label">
62 <label>${_('Contact')}:</label>
62 <label>${_('Contact')}:</label>
63 </div>
63 </div>
64 <div class="input-short">
64 <div class="input-short">
65 <div class="gravatar">
65 <div class="gravatar">
66 <img alt="gravatar" src="${h.gravatar_url(c.repo_info.dbrepo.user.email)}"/>
66 <img alt="gravatar" src="${h.gravatar_url(c.repo_info.dbrepo.user.email)}"/>
67 </div>
67 </div>
68 ${_('Username')}: ${c.repo_info.dbrepo.user.username}<br/>
68 ${_('Username')}: ${c.repo_info.dbrepo.user.username}<br/>
69 ${_('Name')}: ${c.repo_info.dbrepo.user.name} ${c.repo_info.dbrepo.user.lastname}<br/>
69 ${_('Name')}: ${c.repo_info.dbrepo.user.name} ${c.repo_info.dbrepo.user.lastname}<br/>
70 ${_('Email')}: <a href="mailto:${c.repo_info.dbrepo.user.email}">${c.repo_info.dbrepo.user.email}</a>
70 ${_('Email')}: <a href="mailto:${c.repo_info.dbrepo.user.email}">${c.repo_info.dbrepo.user.email}</a>
71 </div>
71 </div>
72 </div>
72 </div>
73
73
74 <div class="field">
74 <div class="field">
75 <div class="label">
75 <div class="label">
76 <label>${_('Last change')}:</label>
76 <label>${_('Last change')}:</label>
77 </div>
77 </div>
78 <div class="input-short">
78 <div class="input-short">
79 ${h.age(c.repo_info.last_change)} - ${h.rfc822date(c.repo_info.last_change)}
79 ${h.age(c.repo_info.last_change)} - ${h.rfc822date(c.repo_info.last_change)}
80 ${_('by')} ${c.repo_info.get_changeset('tip').author}
80 ${_('by')} ${h.get_changeset_safe(c.repo_info,'tip').author}
81
81
82 </div>
82 </div>
83 </div>
83 </div>
84
84
85 <div class="field">
85 <div class="field">
86 <div class="label">
86 <div class="label">
87 <label>${_('Clone url')}:</label>
87 <label>${_('Clone url')}:</label>
88 </div>
88 </div>
89 <div class="input-short">
89 <div class="input-short">
90 <input type="text" id="clone_url" readonly="readonly" value="hg clone ${c.clone_repo_url}" size="70"/>
90 <input type="text" id="clone_url" readonly="readonly" value="hg clone ${c.clone_repo_url}" size="70"/>
91 </div>
91 </div>
92 </div>
92 </div>
93
93
94 <div class="field">
94 <div class="field">
95 <div class="label">
95 <div class="label">
96 <label>${_('Download')}:</label>
96 <label>${_('Download')}:</label>
97 </div>
97 </div>
98 <div class="input-short">
98 <div class="input-short">
99 %for cnt,archive in enumerate(c.repo_info._get_archives()):
99 %for cnt,archive in enumerate(c.repo_info._get_archives()):
100 %if cnt >=1:
100 %if cnt >=1:
101 |
101 |
102 %endif
102 %endif
103 ${h.link_to(c.repo_info.name+'.'+archive['type'],
103 ${h.link_to(c.repo_info.name+'.'+archive['type'],
104 h.url('files_archive_home',repo_name=c.repo_info.name,
104 h.url('files_archive_home',repo_name=c.repo_info.name,
105 revision='tip',fileformat=archive['extension']),class_="archive_icon")}
105 revision='tip',fileformat=archive['extension']),class_="archive_icon")}
106 %endfor
106 %endfor
107 </div>
107 </div>
108 </div>
108 </div>
109
109
110 <div class="field">
110 <div class="field">
111 <div class="label">
111 <div class="label">
112 <label>${_('Feeds')}:</label>
112 <label>${_('Feeds')}:</label>
113 </div>
113 </div>
114 <div class="input-short">
114 <div class="input-short">
115 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.repo_info.name),class_='rss_icon')}
115 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.repo_info.name),class_='rss_icon')}
116 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.repo_info.name),class_='atom_icon')}
116 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.repo_info.name),class_='atom_icon')}
117 </div>
117 </div>
118 </div>
118 </div>
119 </div>
119 </div>
120 </div>
120 </div>
121 </div>
121 </div>
122
122
123 <div class="box box-right" style="min-height:455px">
123 <div class="box box-right" style="min-height:455px">
124 <!-- box / title -->
124 <!-- box / title -->
125 <div class="title">
125 <div class="title">
126 <h5>${_('Commit activity')}</h5>
126 <h5>${_('Commit activity')}</h5>
127 </div>
127 </div>
128
128
129 <div class="table">
129 <div class="table">
130 <div id="commit_history" style="width:560px;height:300px;float:left"></div>
130 <div id="commit_history" style="width:560px;height:300px;float:left"></div>
131 <div style="clear: both;height: 10px"></div>
131 <div style="clear: both;height: 10px"></div>
132 <div id="overview" style="width:560px;height:100px;float:left"></div>
132 <div id="overview" style="width:560px;height:100px;float:left"></div>
133
133
134 <div id="legend_data" style="clear:both;margin-top:10px;">
134 <div id="legend_data" style="clear:both;margin-top:10px;">
135 <div id="legend_container"></div>
135 <div id="legend_container"></div>
136 <div id="legend_choices">
136 <div id="legend_choices">
137 <table id="legend_choices_tables" style="font-size:smaller;color:#545454"></table>
137 <table id="legend_choices_tables" style="font-size:smaller;color:#545454"></table>
138 </div>
138 </div>
139 </div>
139 </div>
140 <script type="text/javascript">
140 <script type="text/javascript">
141 /**
141 /**
142 * Plots summary graph
142 * Plots summary graph
143 *
143 *
144 * @class SummaryPlot
144 * @class SummaryPlot
145 * @param {from} initial from for detailed graph
145 * @param {from} initial from for detailed graph
146 * @param {to} initial to for detailed graph
146 * @param {to} initial to for detailed graph
147 * @param {dataset}
147 * @param {dataset}
148 * @param {overview_dataset}
148 * @param {overview_dataset}
149 */
149 */
150 function SummaryPlot(from,to,dataset,overview_dataset) {
150 function SummaryPlot(from,to,dataset,overview_dataset) {
151 var initial_ranges = {
151 var initial_ranges = {
152 "xaxis":{
152 "xaxis":{
153 "from":from,
153 "from":from,
154 "to":to,
154 "to":to,
155 },
155 },
156 };
156 };
157 var dataset = dataset;
157 var dataset = dataset;
158 var overview_dataset = [overview_dataset];
158 var overview_dataset = [overview_dataset];
159 var choiceContainer = YAHOO.util.Dom.get("legend_choices");
159 var choiceContainer = YAHOO.util.Dom.get("legend_choices");
160 var choiceContainerTable = YAHOO.util.Dom.get("legend_choices_tables");
160 var choiceContainerTable = YAHOO.util.Dom.get("legend_choices_tables");
161 var plotContainer = YAHOO.util.Dom.get('commit_history');
161 var plotContainer = YAHOO.util.Dom.get('commit_history');
162 var overviewContainer = YAHOO.util.Dom.get('overview');
162 var overviewContainer = YAHOO.util.Dom.get('overview');
163
163
164 var plot_options = {
164 var plot_options = {
165 bars: {show:true,align:'center',lineWidth:4},
165 bars: {show:true,align:'center',lineWidth:4},
166 legend: {show:true, container:"legend_container"},
166 legend: {show:true, container:"legend_container"},
167 points: {show:true,radius:0,fill:false},
167 points: {show:true,radius:0,fill:false},
168 yaxis: {tickDecimals:0,},
168 yaxis: {tickDecimals:0,},
169 xaxis: {
169 xaxis: {
170 mode: "time",
170 mode: "time",
171 timeformat: "%d/%m",
171 timeformat: "%d/%m",
172 min:from,
172 min:from,
173 max:to,
173 max:to,
174 },
174 },
175 grid: {
175 grid: {
176 hoverable: true,
176 hoverable: true,
177 clickable: true,
177 clickable: true,
178 autoHighlight:true,
178 autoHighlight:true,
179 color: "#999"
179 color: "#999"
180 },
180 },
181 //selection: {mode: "x"}
181 //selection: {mode: "x"}
182 };
182 };
183 var overview_options = {
183 var overview_options = {
184 legend:{show:false},
184 legend:{show:false},
185 bars: {show:true,barWidth: 2,},
185 bars: {show:true,barWidth: 2,},
186 shadowSize: 0,
186 shadowSize: 0,
187 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
187 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
188 yaxis: {ticks: 3, min: 0,},
188 yaxis: {ticks: 3, min: 0,},
189 grid: {color: "#999",},
189 grid: {color: "#999",},
190 selection: {mode: "x"}
190 selection: {mode: "x"}
191 };
191 };
192
192
193 /**
193 /**
194 *get dummy data needed in few places
194 *get dummy data needed in few places
195 */
195 */
196 function getDummyData(label){
196 function getDummyData(label){
197 return {"label":label,
197 return {"label":label,
198 "data":[{"time":0,
198 "data":[{"time":0,
199 "commits":0,
199 "commits":0,
200 "added":0,
200 "added":0,
201 "changed":0,
201 "changed":0,
202 "removed":0,
202 "removed":0,
203 }],
203 }],
204 "schema":["commits"],
204 "schema":["commits"],
205 "color":'#ffffff',
205 "color":'#ffffff',
206 }
206 }
207 }
207 }
208
208
209 /**
209 /**
210 * generate checkboxes accordindly to data
210 * generate checkboxes accordindly to data
211 * @param keys
211 * @param keys
212 * @returns
212 * @returns
213 */
213 */
214 function generateCheckboxes(data) {
214 function generateCheckboxes(data) {
215 //append checkboxes
215 //append checkboxes
216 var i = 0;
216 var i = 0;
217 choiceContainerTable.innerHTML = '';
217 choiceContainerTable.innerHTML = '';
218 for(var pos in data) {
218 for(var pos in data) {
219
219
220 data[pos].color = i;
220 data[pos].color = i;
221 i++;
221 i++;
222 if(data[pos].label != ''){
222 if(data[pos].label != ''){
223 choiceContainerTable.innerHTML += '<tr><td>'+
223 choiceContainerTable.innerHTML += '<tr><td>'+
224 '<input type="checkbox" name="' + data[pos].label +'" checked="checked" />'
224 '<input type="checkbox" name="' + data[pos].label +'" checked="checked" />'
225 +data[pos].label+
225 +data[pos].label+
226 '</td></tr>';
226 '</td></tr>';
227 }
227 }
228 }
228 }
229 }
229 }
230
230
231 /**
231 /**
232 * ToolTip show
232 * ToolTip show
233 */
233 */
234 function showTooltip(x, y, contents) {
234 function showTooltip(x, y, contents) {
235 var div=document.getElementById('tooltip');
235 var div=document.getElementById('tooltip');
236 if(!div) {
236 if(!div) {
237 div = document.createElement('div');
237 div = document.createElement('div');
238 div.id="tooltip";
238 div.id="tooltip";
239 div.style.position="absolute";
239 div.style.position="absolute";
240 div.style.border='1px solid #fdd';
240 div.style.border='1px solid #fdd';
241 div.style.padding='2px';
241 div.style.padding='2px';
242 div.style.backgroundColor='#fee';
242 div.style.backgroundColor='#fee';
243 document.body.appendChild(div);
243 document.body.appendChild(div);
244 }
244 }
245 YAHOO.util.Dom.setStyle(div, 'opacity', 0);
245 YAHOO.util.Dom.setStyle(div, 'opacity', 0);
246 div.innerHTML = contents;
246 div.innerHTML = contents;
247 div.style.top=(y + 5) + "px";
247 div.style.top=(y + 5) + "px";
248 div.style.left=(x + 5) + "px";
248 div.style.left=(x + 5) + "px";
249
249
250 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
250 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
251 anim.animate();
251 anim.animate();
252 }
252 }
253
253
254 /**
254 /**
255 * This function will detect if selected period has some changesets for this user
255 * This function will detect if selected period has some changesets for this user
256 if it does this data is then pushed for displaying
256 if it does this data is then pushed for displaying
257 Additionally it will only display users that are selected by the checkbox
257 Additionally it will only display users that are selected by the checkbox
258 */
258 */
259 function getDataAccordingToRanges(ranges) {
259 function getDataAccordingToRanges(ranges) {
260
260
261 var data = [];
261 var data = [];
262 var keys = [];
262 var keys = [];
263 for(var key in dataset){
263 for(var key in dataset){
264 var push = false;
264 var push = false;
265 //method1 slow !!
265 //method1 slow !!
266 ///*
266 ///*
267 for(var ds in dataset[key].data){
267 for(var ds in dataset[key].data){
268 commit_data = dataset[key].data[ds];
268 commit_data = dataset[key].data[ds];
269 //console.log(key);
269 //console.log(key);
270 //console.log(new Date(commit_data.time*1000));
270 //console.log(new Date(commit_data.time*1000));
271 //console.log(new Date(ranges.xaxis.from*1000));
271 //console.log(new Date(ranges.xaxis.from*1000));
272 //console.log(new Date(ranges.xaxis.to*1000));
272 //console.log(new Date(ranges.xaxis.to*1000));
273 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
273 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
274 push = true;
274 push = true;
275 break;
275 break;
276 }
276 }
277 }
277 }
278 //*/
278 //*/
279 /*//method2 sorted commit data !!!
279 /*//method2 sorted commit data !!!
280 var first_commit = dataset[key].data[0].time;
280 var first_commit = dataset[key].data[0].time;
281 var last_commit = dataset[key].data[dataset[key].data.length-1].time;
281 var last_commit = dataset[key].data[dataset[key].data.length-1].time;
282
282
283 console.log(first_commit);
283 console.log(first_commit);
284 console.log(last_commit);
284 console.log(last_commit);
285
285
286 if (first_commit >= ranges.xaxis.from && last_commit <= ranges.xaxis.to){
286 if (first_commit >= ranges.xaxis.from && last_commit <= ranges.xaxis.to){
287 push = true;
287 push = true;
288 }
288 }
289 */
289 */
290 if(push){
290 if(push){
291 data.push(dataset[key]);
291 data.push(dataset[key]);
292 }
292 }
293 }
293 }
294 if(data.length >= 1){
294 if(data.length >= 1){
295 return data;
295 return data;
296 }
296 }
297 else{
297 else{
298 //just return dummy data for graph to plot itself
298 //just return dummy data for graph to plot itself
299 return [getDummyData('')];
299 return [getDummyData('')];
300 }
300 }
301
301
302 }
302 }
303
303
304 /**
304 /**
305 * redraw using new checkbox data
305 * redraw using new checkbox data
306 */
306 */
307 function plotchoiced(e,args){
307 function plotchoiced(e,args){
308 var cur_data = args[0];
308 var cur_data = args[0];
309 var cur_ranges = args[1];
309 var cur_ranges = args[1];
310
310
311 var new_data = [];
311 var new_data = [];
312 var inputs = choiceContainer.getElementsByTagName("input");
312 var inputs = choiceContainer.getElementsByTagName("input");
313
313
314 //show only checked labels
314 //show only checked labels
315 for(var i=0; i<inputs.length; i++) {
315 for(var i=0; i<inputs.length; i++) {
316 var checkbox_key = inputs[i].name;
316 var checkbox_key = inputs[i].name;
317
317
318 if(inputs[i].checked){
318 if(inputs[i].checked){
319 for(var d in cur_data){
319 for(var d in cur_data){
320 if(cur_data[d].label == checkbox_key){
320 if(cur_data[d].label == checkbox_key){
321 new_data.push(cur_data[d]);
321 new_data.push(cur_data[d]);
322 }
322 }
323 }
323 }
324 }
324 }
325 else{
325 else{
326 //push dummy data to not hide the label
326 //push dummy data to not hide the label
327 new_data.push(getDummyData(checkbox_key));
327 new_data.push(getDummyData(checkbox_key));
328 }
328 }
329 }
329 }
330
330
331 var new_options = YAHOO.lang.merge(plot_options, {
331 var new_options = YAHOO.lang.merge(plot_options, {
332 xaxis: {
332 xaxis: {
333 min: cur_ranges.xaxis.from,
333 min: cur_ranges.xaxis.from,
334 max: cur_ranges.xaxis.to,
334 max: cur_ranges.xaxis.to,
335 mode:"time",
335 mode:"time",
336 timeformat: "%d/%m",
336 timeformat: "%d/%m",
337 }
337 }
338 });
338 });
339 if (!new_data){
339 if (!new_data){
340 new_data = [[0,1]];
340 new_data = [[0,1]];
341 }
341 }
342 // do the zooming
342 // do the zooming
343 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
343 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
344
344
345 plot.subscribe("plotselected", plotselected);
345 plot.subscribe("plotselected", plotselected);
346
346
347 //resubscribe plothover
347 //resubscribe plothover
348 plot.subscribe("plothover", plothover);
348 plot.subscribe("plothover", plothover);
349
349
350 // don't fire event on the overview to prevent eternal loop
350 // don't fire event on the overview to prevent eternal loop
351 overview.setSelection(cur_ranges, true);
351 overview.setSelection(cur_ranges, true);
352
352
353 }
353 }
354
354
355 /**
355 /**
356 * plot only selected items from overview
356 * plot only selected items from overview
357 * @param ranges
357 * @param ranges
358 * @returns
358 * @returns
359 */
359 */
360 function plotselected(ranges,cur_data) {
360 function plotselected(ranges,cur_data) {
361 //updates the data for new plot
361 //updates the data for new plot
362 data = getDataAccordingToRanges(ranges);
362 data = getDataAccordingToRanges(ranges);
363 generateCheckboxes(data);
363 generateCheckboxes(data);
364
364
365 var new_options = YAHOO.lang.merge(plot_options, {
365 var new_options = YAHOO.lang.merge(plot_options, {
366 xaxis: {
366 xaxis: {
367 min: ranges.xaxis.from,
367 min: ranges.xaxis.from,
368 max: ranges.xaxis.to,
368 max: ranges.xaxis.to,
369 mode:"time",
369 mode:"time",
370 timeformat: "%d/%m",
370 timeformat: "%d/%m",
371 }
371 }
372 });
372 });
373 // do the zooming
373 // do the zooming
374 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
374 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
375
375
376 plot.subscribe("plotselected", plotselected);
376 plot.subscribe("plotselected", plotselected);
377
377
378 //resubscribe plothover
378 //resubscribe plothover
379 plot.subscribe("plothover", plothover);
379 plot.subscribe("plothover", plothover);
380
380
381 // don't fire event on the overview to prevent eternal loop
381 // don't fire event on the overview to prevent eternal loop
382 overview.setSelection(ranges, true);
382 overview.setSelection(ranges, true);
383
383
384 //resubscribe choiced
384 //resubscribe choiced
385 YAHOO.util.Event.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
385 YAHOO.util.Event.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
386 }
386 }
387
387
388 var previousPoint = null;
388 var previousPoint = null;
389
389
390 function plothover(o) {
390 function plothover(o) {
391 var pos = o.pos;
391 var pos = o.pos;
392 var item = o.item;
392 var item = o.item;
393
393
394 //YAHOO.util.Dom.get("x").innerHTML = pos.x.toFixed(2);
394 //YAHOO.util.Dom.get("x").innerHTML = pos.x.toFixed(2);
395 //YAHOO.util.Dom.get("y").innerHTML = pos.y.toFixed(2);
395 //YAHOO.util.Dom.get("y").innerHTML = pos.y.toFixed(2);
396 if (item) {
396 if (item) {
397 if (previousPoint != item.datapoint) {
397 if (previousPoint != item.datapoint) {
398 previousPoint = item.datapoint;
398 previousPoint = item.datapoint;
399
399
400 var tooltip = YAHOO.util.Dom.get("tooltip");
400 var tooltip = YAHOO.util.Dom.get("tooltip");
401 if(tooltip) {
401 if(tooltip) {
402 tooltip.parentNode.removeChild(tooltip);
402 tooltip.parentNode.removeChild(tooltip);
403 }
403 }
404 var x = item.datapoint.x.toFixed(2);
404 var x = item.datapoint.x.toFixed(2);
405 var y = item.datapoint.y.toFixed(2);
405 var y = item.datapoint.y.toFixed(2);
406
406
407 if (!item.series.label){
407 if (!item.series.label){
408 item.series.label = 'commits';
408 item.series.label = 'commits';
409 }
409 }
410 var d = new Date(x*1000);
410 var d = new Date(x*1000);
411 var fd = d.toDateString()
411 var fd = d.toDateString()
412 var nr_commits = parseInt(y);
412 var nr_commits = parseInt(y);
413
413
414 var cur_data = dataset[item.series.label].data[item.dataIndex];
414 var cur_data = dataset[item.series.label].data[item.dataIndex];
415 var added = cur_data.added;
415 var added = cur_data.added;
416 var changed = cur_data.changed;
416 var changed = cur_data.changed;
417 var removed = cur_data.removed;
417 var removed = cur_data.removed;
418
418
419 var nr_commits_suffix = " ${_('commits')} ";
419 var nr_commits_suffix = " ${_('commits')} ";
420 var added_suffix = " ${_('files added')} ";
420 var added_suffix = " ${_('files added')} ";
421 var changed_suffix = " ${_('files changed')} ";
421 var changed_suffix = " ${_('files changed')} ";
422 var removed_suffix = " ${_('files removed')} ";
422 var removed_suffix = " ${_('files removed')} ";
423
423
424
424
425 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
425 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
426 if(added==1){added_suffix=" ${_('file added')} ";}
426 if(added==1){added_suffix=" ${_('file added')} ";}
427 if(changed==1){changed_suffix=" ${_('file changed')} ";}
427 if(changed==1){changed_suffix=" ${_('file changed')} ";}
428 if(removed==1){removed_suffix=" ${_('file removed')} ";}
428 if(removed==1){removed_suffix=" ${_('file removed')} ";}
429
429
430 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
430 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
431 +'<br/>'+
431 +'<br/>'+
432 nr_commits + nr_commits_suffix+'<br/>'+
432 nr_commits + nr_commits_suffix+'<br/>'+
433 added + added_suffix +'<br/>'+
433 added + added_suffix +'<br/>'+
434 changed + changed_suffix + '<br/>'+
434 changed + changed_suffix + '<br/>'+
435 removed + removed_suffix + '<br/>');
435 removed + removed_suffix + '<br/>');
436 }
436 }
437 }
437 }
438 else {
438 else {
439 var tooltip = YAHOO.util.Dom.get("tooltip");
439 var tooltip = YAHOO.util.Dom.get("tooltip");
440
440
441 if(tooltip) {
441 if(tooltip) {
442 tooltip.parentNode.removeChild(tooltip);
442 tooltip.parentNode.removeChild(tooltip);
443 }
443 }
444 previousPoint = null;
444 previousPoint = null;
445 }
445 }
446 }
446 }
447
447
448 /**
448 /**
449 * MAIN EXECUTION
449 * MAIN EXECUTION
450 */
450 */
451
451
452 var data = getDataAccordingToRanges(initial_ranges);
452 var data = getDataAccordingToRanges(initial_ranges);
453 generateCheckboxes(data);
453 generateCheckboxes(data);
454
454
455 //main plot
455 //main plot
456 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
456 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
457
457
458 //overview
458 //overview
459 var overview = YAHOO.widget.Flot(overviewContainer, overview_dataset, overview_options);
459 var overview = YAHOO.widget.Flot(overviewContainer, overview_dataset, overview_options);
460
460
461 //show initial selection on overview
461 //show initial selection on overview
462 overview.setSelection(initial_ranges);
462 overview.setSelection(initial_ranges);
463
463
464 plot.subscribe("plotselected", plotselected);
464 plot.subscribe("plotselected", plotselected);
465
465
466 overview.subscribe("plotselected", function (ranges) {
466 overview.subscribe("plotselected", function (ranges) {
467 plot.setSelection(ranges);
467 plot.setSelection(ranges);
468 });
468 });
469
469
470 plot.subscribe("plothover", plothover);
470 plot.subscribe("plothover", plothover);
471
471
472 YAHOO.util.Event.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
472 YAHOO.util.Event.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
473 }
473 }
474 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
474 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
475 </script>
475 </script>
476
476
477 </div>
477 </div>
478 </div>
478 </div>
479
479
480 <div class="box">
480 <div class="box">
481 <div class="title">
481 <div class="title">
482 <div class="breadcrumbs">${h.link_to(_('Last ten changes'),h.url('changelog_home',repo_name=c.repo_name))}</div>
482 <div class="breadcrumbs">${h.link_to(_('Last ten changes'),h.url('changelog_home',repo_name=c.repo_name))}</div>
483 </div>
483 </div>
484 <div class="table">
484 <div class="table">
485 <%include file='../shortlog/shortlog_data.html'/>
485 <%include file='../shortlog/shortlog_data.html'/>
486 ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))}
486 ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))}
487 </div>
487 </div>
488 </div>
488 </div>
489 <div class="box">
489 <div class="box">
490 <div class="title">
490 <div class="title">
491 <div class="breadcrumbs">${h.link_to(_('Last ten tags'),h.url('tags_home',repo_name=c.repo_name))}</div>
491 <div class="breadcrumbs">${h.link_to(_('Last ten tags'),h.url('tags_home',repo_name=c.repo_name))}</div>
492 </div>
492 </div>
493 <div class="table">
493 <div class="table">
494 <%include file='../tags/tags_data.html'/>
494 <%include file='../tags/tags_data.html'/>
495 ${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))}
495 ${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))}
496 </div>
496 </div>
497 </div>
497 </div>
498 <div class="box">
498 <div class="box">
499 <div class="title">
499 <div class="title">
500 <div class="breadcrumbs">${h.link_to(_('Last ten branches'),h.url('branches_home',repo_name=c.repo_name))}</div>
500 <div class="breadcrumbs">${h.link_to(_('Last ten branches'),h.url('branches_home',repo_name=c.repo_name))}</div>
501 </div>
501 </div>
502 <div class="table">
502 <div class="table">
503 <%include file='../branches/branches_data.html'/>
503 <%include file='../branches/branches_data.html'/>
504 ${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))}
504 ${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))}
505 </div>
505 </div>
506 </div>
506 </div>
507
507
508 </%def> No newline at end of file
508 </%def>
General Comments 0
You need to be logged in to leave comments. Login now