##// END OF EJS Templates
Code refactor number 2
marcink -
r1022:4f834b0a beta
parent child Browse files
Show More
@@ -1,341 +1,341
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.repos
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Admin controller for RhodeCode
7 7
8 8 :created_on: Apr 7, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27
28 28 import logging
29 29 import traceback
30 30 import formencode
31 31 from operator import itemgetter
32 32 from formencode import htmlfill
33 33
34 34 from paste.httpexceptions import HTTPInternalServerError
35 35 from pylons import request, response, session, tmpl_context as c, url
36 36 from pylons.controllers.util import abort, redirect
37 37 from pylons.i18n.translation import _
38 38
39 39 from rhodecode.lib import helpers as h
40 40 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
41 41 HasPermissionAnyDecorator
42 42 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.utils import invalidate_cache, action_logger
43 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
44 44 from rhodecode.model.db import User
45 45 from rhodecode.model.forms import RepoForm
46 46 from rhodecode.model.scm import ScmModel
47 47 from rhodecode.model.repo import RepoModel
48 48
49 49
50 50 log = logging.getLogger(__name__)
51 51
52 52 class ReposController(BaseController):
53 53 """REST Controller styled on the Atom Publishing Protocol"""
54 54 # To properly map this controller, ensure your config/routing.py
55 55 # file has a resource setup:
56 56 # map.resource('repo', 'repos')
57 57
58 58 @LoginRequired()
59 59 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
60 60 def __before__(self):
61 61 c.admin_user = session.get('admin_user')
62 62 c.admin_username = session.get('admin_username')
63 63 super(ReposController, self).__before__()
64 64
65 65 @HasPermissionAllDecorator('hg.admin')
66 66 def index(self, format='html'):
67 67 """GET /repos: All items in the collection"""
68 68 # url('repos')
69 69 cached_repo_list = ScmModel().get_repos()
70 70 c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort'))
71 71 return render('admin/repos/repos.html')
72 72
73 73 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
74 74 def create(self):
75 75 """POST /repos: Create a new item"""
76 76 # url('repos')
77 77 repo_model = RepoModel()
78 78 _form = RepoForm()()
79 79 form_result = {}
80 80 try:
81 81 form_result = _form.to_python(dict(request.POST))
82 82 repo_model.create(form_result, c.rhodecode_user)
83 83 h.flash(_('created repository %s') % form_result['repo_name'],
84 84 category='success')
85 85
86 86 if request.POST.get('user_created'):
87 87 action_logger(self.rhodecode_user, 'user_created_repo',
88 88 form_result['repo_name'], '', self.sa)
89 89 else:
90 90 action_logger(self.rhodecode_user, 'admin_created_repo',
91 91 form_result['repo_name'], '', self.sa)
92 92
93 93 except formencode.Invalid, errors:
94 94 c.new_repo = errors.value['repo_name']
95 95
96 96 if request.POST.get('user_created'):
97 97 r = render('admin/repos/repo_add_create_repository.html')
98 98 else:
99 99 r = render('admin/repos/repo_add.html')
100 100
101 101 return htmlfill.render(
102 102 r,
103 103 defaults=errors.value,
104 104 errors=errors.error_dict or {},
105 105 prefix_error=False,
106 106 encoding="UTF-8")
107 107
108 108 except Exception:
109 109 log.error(traceback.format_exc())
110 110 msg = _('error occurred during creation of repository %s') \
111 111 % form_result.get('repo_name')
112 112 h.flash(msg, category='error')
113 113 if request.POST.get('user_created'):
114 114 return redirect(url('home'))
115 115 return redirect(url('repos'))
116 116
117 117 @HasPermissionAllDecorator('hg.admin')
118 118 def new(self, format='html'):
119 119 """GET /repos/new: Form to create a new item"""
120 120 new_repo = request.GET.get('repo', '')
121 c.new_repo = h.repo_name_slug(new_repo)
121 c.new_repo = repo_name_slug(new_repo)
122 122
123 123 return render('admin/repos/repo_add.html')
124 124
125 125 @HasPermissionAllDecorator('hg.admin')
126 126 def update(self, repo_name):
127 127 """PUT /repos/repo_name: Update an existing item"""
128 128 # Forms posted to this method should contain a hidden field:
129 129 # <input type="hidden" name="_method" value="PUT" />
130 130 # Or using helpers:
131 131 # h.form(url('repo', repo_name=ID),
132 132 # method='put')
133 133 # url('repo', repo_name=ID)
134 134 repo_model = RepoModel()
135 135 changed_name = repo_name
136 136 _form = RepoForm(edit=True, old_data={'repo_name':repo_name})()
137 137
138 138 try:
139 139 form_result = _form.to_python(dict(request.POST))
140 140 repo_model.update(repo_name, form_result)
141 141 invalidate_cache('get_repo_cached_%s' % repo_name)
142 142 h.flash(_('Repository %s updated successfully' % repo_name),
143 143 category='success')
144 144 changed_name = form_result['repo_name']
145 145 action_logger(self.rhodecode_user, 'admin_updated_repo',
146 146 changed_name, '', self.sa)
147 147
148 148 except formencode.Invalid, errors:
149 149 c.repo_info = repo_model.get_by_repo_name(repo_name)
150 150 if c.repo_info.stats:
151 151 last_rev = c.repo_info.stats.stat_on_revision
152 152 else:
153 153 last_rev = 0
154 154 c.stats_revision = last_rev
155 155 r = ScmModel().get(repo_name)
156 156 c.repo_last_rev = r.revisions[-1] if r.revisions else 0
157 157
158 158 if last_rev == 0:
159 159 c.stats_percentage = 0
160 160 else:
161 161 c.stats_percentage = '%.2f' % ((float((last_rev)) /
162 162 c.repo_last_rev) * 100)
163 163
164 164 c.users_array = repo_model.get_users_js()
165 165 c.users_groups_array = repo_model.get_users_groups_js()
166 166
167 167 errors.value.update({'user':c.repo_info.user.username})
168 168 return htmlfill.render(
169 169 render('admin/repos/repo_edit.html'),
170 170 defaults=errors.value,
171 171 errors=errors.error_dict or {},
172 172 prefix_error=False,
173 173 encoding="UTF-8")
174 174
175 175 except Exception:
176 176 log.error(traceback.format_exc())
177 177 h.flash(_('error occurred during update of repository %s') \
178 178 % repo_name, category='error')
179 179
180 180 return redirect(url('edit_repo', repo_name=changed_name))
181 181
182 182 @HasPermissionAllDecorator('hg.admin')
183 183 def delete(self, repo_name):
184 184 """DELETE /repos/repo_name: Delete an existing item"""
185 185 # Forms posted to this method should contain a hidden field:
186 186 # <input type="hidden" name="_method" value="DELETE" />
187 187 # Or using helpers:
188 188 # h.form(url('repo', repo_name=ID),
189 189 # method='delete')
190 190 # url('repo', repo_name=ID)
191 191
192 192 repo_model = RepoModel()
193 193 repo = repo_model.get_by_repo_name(repo_name)
194 194 if not repo:
195 195 h.flash(_('%s repository is not mapped to db perhaps'
196 196 ' it was moved or renamed from the filesystem'
197 197 ' please run the application again'
198 198 ' in order to rescan repositories') % repo_name,
199 199 category='error')
200 200
201 201 return redirect(url('repos'))
202 202 try:
203 203 action_logger(self.rhodecode_user, 'admin_deleted_repo',
204 204 repo_name, '', self.sa)
205 205 repo_model.delete(repo)
206 206 invalidate_cache('get_repo_cached_%s' % repo_name)
207 207 h.flash(_('deleted repository %s') % repo_name, category='success')
208 208
209 209 except Exception, e:
210 210 log.error(traceback.format_exc())
211 211 h.flash(_('An error occurred during deletion of %s') % repo_name,
212 212 category='error')
213 213
214 214 return redirect(url('repos'))
215 215
216 216 @HasPermissionAllDecorator('hg.admin')
217 217 def delete_perm_user(self, repo_name):
218 218 """DELETE an existing repository permission user
219 219
220 220 :param repo_name:
221 221 """
222 222
223 223 try:
224 224 repo_model = RepoModel()
225 225 repo_model.delete_perm_user(request.POST, repo_name)
226 226 except Exception, e:
227 227 h.flash(_('An error occurred during deletion of repository user'),
228 228 category='error')
229 229 raise HTTPInternalServerError()
230 230
231 231 @HasPermissionAllDecorator('hg.admin')
232 232 def delete_perm_users_group(self, repo_name):
233 233 """DELETE an existing repository permission users group
234 234
235 235 :param repo_name:
236 236 """
237 237 try:
238 238 repo_model = RepoModel()
239 239 repo_model.delete_perm_users_group(request.POST, repo_name)
240 240 except Exception, e:
241 241 h.flash(_('An error occurred during deletion of repository'
242 242 ' users groups'),
243 243 category='error')
244 244 raise HTTPInternalServerError()
245 245
246 246 @HasPermissionAllDecorator('hg.admin')
247 247 def repo_stats(self, repo_name):
248 248 """DELETE an existing repository statistics
249 249
250 250 :param repo_name:
251 251 """
252 252
253 253 try:
254 254 repo_model = RepoModel()
255 255 repo_model.delete_stats(repo_name)
256 256 except Exception, e:
257 257 h.flash(_('An error occurred during deletion of repository stats'),
258 258 category='error')
259 259 return redirect(url('edit_repo', repo_name=repo_name))
260 260
261 261 @HasPermissionAllDecorator('hg.admin')
262 262 def repo_cache(self, repo_name):
263 263 """INVALIDATE existing repository cache
264 264
265 265 :param repo_name:
266 266 """
267 267
268 268 try:
269 269 ScmModel().mark_for_invalidation(repo_name)
270 270 except Exception, e:
271 271 h.flash(_('An error occurred during cache invalidation'),
272 272 category='error')
273 273 return redirect(url('edit_repo', repo_name=repo_name))
274 274
275 275 @HasPermissionAllDecorator('hg.admin')
276 276 def show(self, repo_name, format='html'):
277 277 """GET /repos/repo_name: Show a specific item"""
278 278 # url('repo', repo_name=ID)
279 279
280 280 @HasPermissionAllDecorator('hg.admin')
281 281 def edit(self, repo_name, format='html'):
282 282 """GET /repos/repo_name/edit: Form to edit an existing item"""
283 283 # url('edit_repo', repo_name=ID)
284 284 repo_model = RepoModel()
285 285 c.repo_info = repo_model.get_by_repo_name(repo_name)
286 286
287 287 r = ScmModel().get(repo_name)
288 288
289 289 if c.repo_info is None:
290 290 h.flash(_('%s repository is not mapped to db perhaps'
291 291 ' it was created or renamed from the filesystem'
292 292 ' please run the application again'
293 293 ' in order to rescan repositories') % repo_name,
294 294 category='error')
295 295
296 296 return redirect(url('repos'))
297 297
298 298 if c.repo_info.stats:
299 299 last_rev = c.repo_info.stats.stat_on_revision
300 300 else:
301 301 last_rev = 0
302 302 c.stats_revision = last_rev
303 303
304 304 c.repo_last_rev = r.revisions[-1] if r.revisions else 0
305 305
306 306 if last_rev == 0 or c.repo_last_rev == 0:
307 307 c.stats_percentage = 0
308 308 else:
309 309 c.stats_percentage = '%.2f' % ((float((last_rev)) /
310 310 c.repo_last_rev) * 100)
311 311
312 312 c.users_array = repo_model.get_users_js()
313 313 c.users_groups_array = repo_model.get_users_groups_js()
314 314
315 315 defaults = c.repo_info.get_dict()
316 316
317 317 #fill owner
318 318 if c.repo_info.user:
319 319 defaults.update({'user':c.repo_info.user.username})
320 320 else:
321 321 replacement_user = self.sa.query(User)\
322 322 .filter(User.admin == True).first().username
323 323 defaults.update({'user':replacement_user})
324 324
325 325
326 326 #fill repository users
327 327 for p in c.repo_info.repo_to_perm:
328 328 defaults.update({'u_perm_%s' % p.user.username:
329 329 p.permission.permission_name})
330 330
331 331 #fill repository groups
332 332 for p in c.repo_info.users_group_to_perm:
333 333 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
334 334 p.permission.permission_name})
335 335
336 336 return htmlfill.render(
337 337 render('admin/repos/repo_edit.html'),
338 338 defaults=defaults,
339 339 encoding="UTF-8",
340 340 force_defaults=False
341 341 )
@@ -1,351 +1,349
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 settings controller for rhodecode admin
7 7
8 8 :created_on: Jul 14, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27
28 28 import logging
29 29 import traceback
30 30 import formencode
31 31
32 from operator import itemgetter
32 from sqlalchemy import func
33 33 from formencode import htmlfill
34 34 from pylons import request, session, tmpl_context as c, url, app_globals as g, \
35 35 config
36 36 from pylons.controllers.util import abort, redirect
37 37 from pylons.i18n.translation import _
38 38
39 39 from rhodecode.lib import helpers as h
40 40 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
41 41 HasPermissionAnyDecorator, NotAnonymous
42 42 from rhodecode.lib.base import BaseController, render
43 43 from rhodecode.lib.celerylib import tasks, run_task
44 44 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
45 45 set_rhodecode_config
46 46 from rhodecode.model.db import RhodeCodeUi, Repository
47 47 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
48 48 ApplicationUiSettingsForm
49 49 from rhodecode.model.scm import ScmModel
50 50 from rhodecode.model.settings import SettingsModel
51 51 from rhodecode.model.user import UserModel
52 52
53 from sqlalchemy import func
54
55 53
56 54 log = logging.getLogger(__name__)
57 55
58 56
59 57 class SettingsController(BaseController):
60 58 """REST Controller styled on the Atom Publishing Protocol"""
61 59 # To properly map this controller, ensure your config/routing.py
62 60 # file has a resource setup:
63 61 # map.resource('setting', 'settings', controller='admin/settings',
64 62 # path_prefix='/admin', name_prefix='admin_')
65 63
66 64
67 65 @LoginRequired()
68 66 def __before__(self):
69 67 c.admin_user = session.get('admin_user')
70 68 c.admin_username = session.get('admin_username')
71 69 super(SettingsController, self).__before__()
72 70
73 71
74 72 @HasPermissionAllDecorator('hg.admin')
75 73 def index(self, format='html'):
76 74 """GET /admin/settings: All items in the collection"""
77 75 # url('admin_settings')
78 76
79 77 defaults = SettingsModel().get_app_settings()
80 78 defaults.update(self.get_hg_ui_settings())
81 79 return htmlfill.render(
82 80 render('admin/settings/settings.html'),
83 81 defaults=defaults,
84 82 encoding="UTF-8",
85 83 force_defaults=False
86 84 )
87 85
88 86 @HasPermissionAllDecorator('hg.admin')
89 87 def create(self):
90 88 """POST /admin/settings: Create a new item"""
91 89 # url('admin_settings')
92 90
93 91 @HasPermissionAllDecorator('hg.admin')
94 92 def new(self, format='html'):
95 93 """GET /admin/settings/new: Form to create a new item"""
96 94 # url('admin_new_setting')
97 95
98 96 @HasPermissionAllDecorator('hg.admin')
99 97 def update(self, setting_id):
100 98 """PUT /admin/settings/setting_id: Update an existing item"""
101 99 # Forms posted to this method should contain a hidden field:
102 100 # <input type="hidden" name="_method" value="PUT" />
103 101 # Or using helpers:
104 102 # h.form(url('admin_setting', setting_id=ID),
105 103 # method='put')
106 104 # url('admin_setting', setting_id=ID)
107 105 if setting_id == 'mapping':
108 106 rm_obsolete = request.POST.get('destroy', False)
109 107 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
110 108
111 109 initial = ScmModel().repo_scan(g.paths[0][1], g.baseui)
112 110 for repo_name in initial.keys():
113 111 invalidate_cache('get_repo_cached_%s' % repo_name)
114 112
115 113 repo2db_mapper(initial, rm_obsolete)
116 114
117 115 h.flash(_('Repositories successfully rescanned'), category='success')
118 116
119 117 if setting_id == 'whoosh':
120 118 repo_location = self.get_hg_ui_settings()['paths_root_path']
121 119 full_index = request.POST.get('full_index', False)
122 120 task = run_task(tasks.whoosh_index, repo_location, full_index)
123 121
124 122 h.flash(_('Whoosh reindex task scheduled'), category='success')
125 123 if setting_id == 'global':
126 124
127 125 application_form = ApplicationSettingsForm()()
128 126 try:
129 127 form_result = application_form.to_python(dict(request.POST))
130 128 settings_model = SettingsModel()
131 129
132 130 try:
133 131 hgsettings1 = settings_model.get('title')
134 132 hgsettings1.app_settings_value = form_result['rhodecode_title']
135 133
136 134 hgsettings2 = settings_model.get('realm')
137 135 hgsettings2.app_settings_value = form_result['rhodecode_realm']
138 136
139 137 hgsettings3 = settings_model.get('ga_code')
140 138 hgsettings3.app_settings_value = form_result['rhodecode_ga_code']
141 139
142 140
143 141
144 142 self.sa.add(hgsettings1)
145 143 self.sa.add(hgsettings2)
146 144 self.sa.add(hgsettings3)
147 145 self.sa.commit()
148 146 set_rhodecode_config(config)
149 147 h.flash(_('Updated application settings'),
150 148 category='success')
151 149
152 150 except:
153 151 log.error(traceback.format_exc())
154 152 h.flash(_('error occurred during updating application settings'),
155 153 category='error')
156 154
157 155 self.sa.rollback()
158 156
159 157
160 158 except formencode.Invalid, errors:
161 159 return htmlfill.render(
162 160 render('admin/settings/settings.html'),
163 161 defaults=errors.value,
164 162 errors=errors.error_dict or {},
165 163 prefix_error=False,
166 164 encoding="UTF-8")
167 165
168 166 if setting_id == 'mercurial':
169 167 application_form = ApplicationUiSettingsForm()()
170 168 try:
171 169 form_result = application_form.to_python(dict(request.POST))
172 170
173 171 try:
174 172
175 173 hgsettings1 = self.sa.query(RhodeCodeUi)\
176 174 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
177 175 hgsettings1.ui_value = form_result['web_push_ssl']
178 176
179 177 hgsettings2 = self.sa.query(RhodeCodeUi)\
180 178 .filter(RhodeCodeUi.ui_key == '/').one()
181 179 hgsettings2.ui_value = form_result['paths_root_path']
182 180
183 181
184 182 #HOOKS
185 183 hgsettings3 = self.sa.query(RhodeCodeUi)\
186 184 .filter(RhodeCodeUi.ui_key == 'changegroup.update').one()
187 185 hgsettings3.ui_active = bool(form_result['hooks_changegroup_update'])
188 186
189 187 hgsettings4 = self.sa.query(RhodeCodeUi)\
190 188 .filter(RhodeCodeUi.ui_key == 'changegroup.repo_size').one()
191 189 hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size'])
192 190
193 191 hgsettings5 = self.sa.query(RhodeCodeUi)\
194 192 .filter(RhodeCodeUi.ui_key == 'pretxnchangegroup.push_logger').one()
195 193 hgsettings5.ui_active = bool(form_result['hooks_pretxnchangegroup_push_logger'])
196 194
197 195 hgsettings6 = self.sa.query(RhodeCodeUi)\
198 196 .filter(RhodeCodeUi.ui_key == 'preoutgoing.pull_logger').one()
199 197 hgsettings6.ui_active = bool(form_result['hooks_preoutgoing_pull_logger'])
200 198
201 199
202 200 self.sa.add(hgsettings1)
203 201 self.sa.add(hgsettings2)
204 202 self.sa.add(hgsettings3)
205 203 self.sa.add(hgsettings4)
206 204 self.sa.add(hgsettings5)
207 205 self.sa.add(hgsettings6)
208 206 self.sa.commit()
209 207
210 208 h.flash(_('Updated mercurial settings'),
211 209 category='success')
212 210
213 211 except:
214 212 log.error(traceback.format_exc())
215 213 h.flash(_('error occurred during updating application settings'),
216 214 category='error')
217 215
218 216 self.sa.rollback()
219 217
220 218
221 219 except formencode.Invalid, errors:
222 220 return htmlfill.render(
223 221 render('admin/settings/settings.html'),
224 222 defaults=errors.value,
225 223 errors=errors.error_dict or {},
226 224 prefix_error=False,
227 225 encoding="UTF-8")
228 226
229 227
230 228
231 229 return redirect(url('admin_settings'))
232 230
233 231 @HasPermissionAllDecorator('hg.admin')
234 232 def delete(self, setting_id):
235 233 """DELETE /admin/settings/setting_id: Delete an existing item"""
236 234 # Forms posted to this method should contain a hidden field:
237 235 # <input type="hidden" name="_method" value="DELETE" />
238 236 # Or using helpers:
239 237 # h.form(url('admin_setting', setting_id=ID),
240 238 # method='delete')
241 239 # url('admin_setting', setting_id=ID)
242 240
243 241 @HasPermissionAllDecorator('hg.admin')
244 242 def show(self, setting_id, format='html'):
245 243 """GET /admin/settings/setting_id: Show a specific item"""
246 244 # url('admin_setting', setting_id=ID)
247 245
248 246 @HasPermissionAllDecorator('hg.admin')
249 247 def edit(self, setting_id, format='html'):
250 248 """GET /admin/settings/setting_id/edit: Form to edit an existing item"""
251 249 # url('admin_edit_setting', setting_id=ID)
252 250
253 251 @NotAnonymous()
254 252 def my_account(self):
255 253 """
256 254 GET /_admin/my_account Displays info about my account
257 255 """
258 256 # url('admin_settings_my_account')
259 257
260 258 c.user = UserModel().get(c.rhodecode_user.user_id, cache=False)
261 259 all_repos = self.sa.query(Repository)\
262 260 .filter(Repository.user_id == c.user.user_id)\
263 261 .order_by(func.lower(Repository.repo_name))\
264 262 .all()
265 263
266 264 c.user_repos = ScmModel().get_repos(all_repos)
267 265
268 266 if c.user.username == 'default':
269 267 h.flash(_("You can't edit this user since it's"
270 268 " crucial for entire application"), category='warning')
271 269 return redirect(url('users'))
272 270
273 271 defaults = c.user.get_dict()
274 272 return htmlfill.render(
275 273 render('admin/users/user_edit_my_account.html'),
276 274 defaults=defaults,
277 275 encoding="UTF-8",
278 276 force_defaults=False
279 277 )
280 278
281 279 def my_account_update(self):
282 280 """PUT /_admin/my_account_update: Update an existing item"""
283 281 # Forms posted to this method should contain a hidden field:
284 282 # <input type="hidden" name="_method" value="PUT" />
285 283 # Or using helpers:
286 284 # h.form(url('admin_settings_my_account_update'),
287 285 # method='put')
288 286 # url('admin_settings_my_account_update', id=ID)
289 287 user_model = UserModel()
290 288 uid = c.rhodecode_user.user_id
291 289 _form = UserForm(edit=True, old_data={'user_id':uid,
292 290 'email':c.rhodecode_user.email})()
293 291 form_result = {}
294 292 try:
295 293 form_result = _form.to_python(dict(request.POST))
296 294 user_model.update_my_account(uid, form_result)
297 295 h.flash(_('Your account was updated successfully'),
298 296 category='success')
299 297
300 298 except formencode.Invalid, errors:
301 299 c.user = user_model.get(c.rhodecode_user.user_id, cache=False)
302 300 c.user = UserModel().get(c.rhodecode_user.user_id, cache=False)
303 301 all_repos = self.sa.query(Repository)\
304 302 .filter(Repository.user_id == c.user.user_id)\
305 303 .order_by(func.lower(Repository.repo_name))\
306 304 .all()
307 305 c.user_repos = ScmModel().get_repos(all_repos)
308 306
309 307 return htmlfill.render(
310 308 render('admin/users/user_edit_my_account.html'),
311 309 defaults=errors.value,
312 310 errors=errors.error_dict or {},
313 311 prefix_error=False,
314 312 encoding="UTF-8")
315 313 except Exception:
316 314 log.error(traceback.format_exc())
317 315 h.flash(_('error occurred during update of user %s') \
318 316 % form_result.get('username'), category='error')
319 317
320 318 return redirect(url('my_account'))
321 319
322 320 @NotAnonymous()
323 321 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
324 322 def create_repository(self):
325 323 """GET /_admin/create_repository: Form to create a new item"""
326 324 new_repo = request.GET.get('repo', '')
327 c.new_repo = h.repo_name_slug(new_repo)
325 c.new_repo = repo_name_slug(new_repo)
328 326
329 327 return render('admin/repos/repo_add_create_repository.html')
330 328
331 329 def get_hg_ui_settings(self):
332 330 ret = self.sa.query(RhodeCodeUi).all()
333 331
334 332 if not ret:
335 333 raise Exception('Could not get application ui settings !')
336 334 settings = {}
337 335 for each in ret:
338 336 k = each.ui_key
339 337 v = each.ui_value
340 338 if k == '/':
341 339 k = 'root_path'
342 340
343 341 if k.find('.') != -1:
344 342 k = k.replace('.', '_')
345 343
346 344 if each.ui_section == 'hooks':
347 345 v = each.ui_active
348 346
349 347 settings[each.ui_section + '_' + k] = v
350 348
351 349 return settings
@@ -1,620 +1,590
1 1 """Helper functions
2 2
3 3 Consists of functions to typically be used within templates, but also
4 4 available to Controllers. This module is available to both as 'h'.
5 5 """
6 6 import random
7 7 import hashlib
8 8 import StringIO
9 9 from pygments.formatters import HtmlFormatter
10 10 from pygments import highlight as code_highlight
11 from pylons import url, app_globals as g
11 from pylons import url
12 12 from pylons.i18n.translation import _, ungettext
13 13 from vcs.utils.annotate import annotate_highlight
14 from rhodecode.lib.utils import repo_name_slug
15
14 16 from webhelpers.html import literal, HTML, escape
15 17 from webhelpers.html.tools import *
16 18 from webhelpers.html.builder import make_tag
17 19 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
18 20 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
19 21 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
20 22 password, textarea, title, ul, xml_declaration, radio
21 23 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
22 24 mail_to, strip_links, strip_tags, tag_re
23 25 from webhelpers.number import format_byte_size, format_bit_size
24 26 from webhelpers.pylonslib import Flash as _Flash
25 27 from webhelpers.pylonslib.secure_form import secure_form
26 28 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
27 29 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
28 30 replace_whitespace, urlify, truncate, wrap_paragraphs
29 31 from webhelpers.date import time_ago_in_words
30 32
31 33 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
32 34 convert_boolean_attrs, NotGiven
33 35
34 36 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
35 37 """Reset button
36 38 """
37 39 _set_input_attrs(attrs, type, name, value)
38 40 _set_id_attr(attrs, id, name)
39 41 convert_boolean_attrs(attrs, ["disabled"])
40 42 return HTML.input(**attrs)
41 43
42 44 reset = _reset
43 45
44 46
45 47 def get_token():
46 48 """Return the current authentication token, creating one if one doesn't
47 49 already exist.
48 50 """
49 51 token_key = "_authentication_token"
50 52 from pylons import session
51 53 if not token_key in session:
52 54 try:
53 55 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
54 56 except AttributeError: # Python < 2.4
55 57 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
56 58 session[token_key] = token
57 59 if hasattr(session, 'save'):
58 60 session.save()
59 61 return session[token_key]
60 62
61 63 class _GetError(object):
62 64 """Get error from form_errors, and represent it as span wrapped error
63 65 message
64 66
65 67 :param field_name: field to fetch errors for
66 68 :param form_errors: form errors dict
67 69 """
68 70
69 71 def __call__(self, field_name, form_errors):
70 72 tmpl = """<span class="error_msg">%s</span>"""
71 73 if form_errors and form_errors.has_key(field_name):
72 74 return literal(tmpl % form_errors.get(field_name))
73 75
74 76 get_error = _GetError()
75 77
76 def recursive_replace(str, replace=' '):
77 """Recursive replace of given sign to just one instance
78
79 :param str: given string
80 :param replace: char to find and replace multiple instances
81
82 Examples::
83 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
84 'Mighty-Mighty-Bo-sstones'
85 """
86
87 if str.find(replace * 2) == -1:
88 return str
89 else:
90 str = str.replace(replace * 2, replace)
91 return recursive_replace(str, replace)
92
93 78 class _ToolTip(object):
94 79
95 80 def __call__(self, tooltip_title, trim_at=50):
96 81 """Special function just to wrap our text into nice formatted
97 82 autowrapped text
98 83
99 84 :param tooltip_title:
100 85 """
101 86
102 87 return wrap_paragraphs(escape(tooltip_title), trim_at)\
103 88 .replace('\n', '<br/>')
104 89
105 90 def activate(self):
106 91 """Adds tooltip mechanism to the given Html all tooltips have to have
107 92 set class `tooltip` and set attribute `tooltip_title`.
108 93 Then a tooltip will be generated based on that. All with yui js tooltip
109 94 """
110 95
111 96 js = '''
112 97 YAHOO.util.Event.onDOMReady(function(){
113 98 function toolTipsId(){
114 99 var ids = [];
115 100 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
116 101
117 102 for (var i = 0; i < tts.length; i++) {
118 103 //if element doesn't not have and id autogenerate one for tooltip
119 104
120 105 if (!tts[i].id){
121 106 tts[i].id='tt'+i*100;
122 107 }
123 108 ids.push(tts[i].id);
124 109 }
125 110 return ids
126 111 };
127 112 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
128 113 context: toolTipsId(),
129 114 monitorresize:false,
130 115 xyoffset :[0,0],
131 116 autodismissdelay:300000,
132 117 hidedelay:5,
133 118 showdelay:20,
134 119 });
135 120
136 121 // Set the text for the tooltip just before we display it. Lazy method
137 122 myToolTips.contextTriggerEvent.subscribe(
138 123 function(type, args) {
139 124
140 125 var context = args[0];
141 126
142 127 //positioning of tooltip
143 128 var tt_w = this.element.clientWidth;//tooltip width
144 129 var tt_h = this.element.clientHeight;//tooltip height
145 130
146 131 var context_w = context.offsetWidth;
147 132 var context_h = context.offsetHeight;
148 133
149 134 var pos_x = YAHOO.util.Dom.getX(context);
150 135 var pos_y = YAHOO.util.Dom.getY(context);
151 136
152 137 var display_strategy = 'right';
153 138 var xy_pos = [0,0];
154 139 switch (display_strategy){
155 140
156 141 case 'top':
157 142 var cur_x = (pos_x+context_w/2)-(tt_w/2);
158 143 var cur_y = (pos_y-tt_h-4);
159 144 xy_pos = [cur_x,cur_y];
160 145 break;
161 146 case 'bottom':
162 147 var cur_x = (pos_x+context_w/2)-(tt_w/2);
163 148 var cur_y = pos_y+context_h+4;
164 149 xy_pos = [cur_x,cur_y];
165 150 break;
166 151 case 'left':
167 152 var cur_x = (pos_x-tt_w-4);
168 153 var cur_y = pos_y-((tt_h/2)-context_h/2);
169 154 xy_pos = [cur_x,cur_y];
170 155 break;
171 156 case 'right':
172 157 var cur_x = (pos_x+context_w+4);
173 158 var cur_y = pos_y-((tt_h/2)-context_h/2);
174 159 xy_pos = [cur_x,cur_y];
175 160 break;
176 161 default:
177 162 var cur_x = (pos_x+context_w/2)-(tt_w/2);
178 163 var cur_y = pos_y-tt_h-4;
179 164 xy_pos = [cur_x,cur_y];
180 165 break;
181 166
182 167 }
183 168
184 169 this.cfg.setProperty("xy",xy_pos);
185 170
186 171 });
187 172
188 173 //Mouse out
189 174 myToolTips.contextMouseOutEvent.subscribe(
190 175 function(type, args) {
191 176 var context = args[0];
192 177
193 178 });
194 179 });
195 180 '''
196 181 return literal(js)
197 182
198 183 tooltip = _ToolTip()
199 184
200 185 class _FilesBreadCrumbs(object):
201 186
202 187 def __call__(self, repo_name, rev, paths):
203 188 if isinstance(paths, str):
204 189 paths = paths.decode('utf-8')
205 190 url_l = [link_to(repo_name, url('files_home',
206 191 repo_name=repo_name,
207 192 revision=rev, f_path=''))]
208 193 paths_l = paths.split('/')
209 194 for cnt, p in enumerate(paths_l):
210 195 if p != '':
211 196 url_l.append(link_to(p, url('files_home',
212 197 repo_name=repo_name,
213 198 revision=rev,
214 199 f_path='/'.join(paths_l[:cnt + 1]))))
215 200
216 201 return literal('/'.join(url_l))
217 202
218 203 files_breadcrumbs = _FilesBreadCrumbs()
219 204
220 205 class CodeHtmlFormatter(HtmlFormatter):
221 206 """My code Html Formatter for source codes
222 207 """
223 208
224 209 def wrap(self, source, outfile):
225 210 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
226 211
227 212 def _wrap_code(self, source):
228 213 for cnt, it in enumerate(source):
229 214 i, t = it
230 215 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
231 216 yield i, t
232 217
233 218 def _wrap_tablelinenos(self, inner):
234 219 dummyoutfile = StringIO.StringIO()
235 220 lncount = 0
236 221 for t, line in inner:
237 222 if t:
238 223 lncount += 1
239 224 dummyoutfile.write(line)
240 225
241 226 fl = self.linenostart
242 227 mw = len(str(lncount + fl - 1))
243 228 sp = self.linenospecial
244 229 st = self.linenostep
245 230 la = self.lineanchors
246 231 aln = self.anchorlinenos
247 232 nocls = self.noclasses
248 233 if sp:
249 234 lines = []
250 235
251 236 for i in range(fl, fl + lncount):
252 237 if i % st == 0:
253 238 if i % sp == 0:
254 239 if aln:
255 240 lines.append('<a href="#%s%d" class="special">%*d</a>' %
256 241 (la, i, mw, i))
257 242 else:
258 243 lines.append('<span class="special">%*d</span>' % (mw, i))
259 244 else:
260 245 if aln:
261 246 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
262 247 else:
263 248 lines.append('%*d' % (mw, i))
264 249 else:
265 250 lines.append('')
266 251 ls = '\n'.join(lines)
267 252 else:
268 253 lines = []
269 254 for i in range(fl, fl + lncount):
270 255 if i % st == 0:
271 256 if aln:
272 257 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
273 258 else:
274 259 lines.append('%*d' % (mw, i))
275 260 else:
276 261 lines.append('')
277 262 ls = '\n'.join(lines)
278 263
279 264 # in case you wonder about the seemingly redundant <div> here: since the
280 265 # content in the other cell also is wrapped in a div, some browsers in
281 266 # some configurations seem to mess up the formatting...
282 267 if nocls:
283 268 yield 0, ('<table class="%stable">' % self.cssclass +
284 269 '<tr><td><div class="linenodiv" '
285 270 'style="background-color: #f0f0f0; padding-right: 10px">'
286 271 '<pre style="line-height: 125%">' +
287 272 ls + '</pre></div></td><td class="code">')
288 273 else:
289 274 yield 0, ('<table class="%stable">' % self.cssclass +
290 275 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
291 276 ls + '</pre></div></td><td class="code">')
292 277 yield 0, dummyoutfile.getvalue()
293 278 yield 0, '</td></tr></table>'
294 279
295 280
296 281 def pygmentize(filenode, **kwargs):
297 282 """pygmentize function using pygments
298 283
299 284 :param filenode:
300 285 """
301 286
302 287 return literal(code_highlight(filenode.content,
303 288 filenode.lexer, CodeHtmlFormatter(**kwargs)))
304 289
305 290 def pygmentize_annotation(filenode, **kwargs):
306 291 """pygmentize function for annotation
307 292
308 293 :param filenode:
309 294 """
310 295
311 296 color_dict = {}
312 297 def gen_color(n=10000):
313 298 """generator for getting n of evenly distributed colors using
314 299 hsv color and golden ratio. It always return same order of colors
315 300
316 301 :returns: RGB tuple
317 302 """
318 303 import colorsys
319 304 golden_ratio = 0.618033988749895
320 305 h = 0.22717784590367374
321 306
322 307 for c in xrange(n):
323 308 h += golden_ratio
324 309 h %= 1
325 310 HSV_tuple = [h, 0.95, 0.95]
326 311 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
327 312 yield map(lambda x:str(int(x * 256)), RGB_tuple)
328 313
329 314 cgenerator = gen_color()
330 315
331 316 def get_color_string(cs):
332 317 if color_dict.has_key(cs):
333 318 col = color_dict[cs]
334 319 else:
335 320 col = color_dict[cs] = cgenerator.next()
336 321 return "color: rgb(%s)! important;" % (', '.join(col))
337 322
338 323 def url_func(changeset):
339 324 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
340 325 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
341 326
342 327 tooltip_html = tooltip_html % (changeset.author,
343 328 changeset.date,
344 329 tooltip(changeset.message))
345 330 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
346 331 short_id(changeset.raw_id))
347 332 uri = link_to(
348 333 lnk_format,
349 334 url('changeset_home', repo_name=changeset.repository.name,
350 335 revision=changeset.raw_id),
351 336 style=get_color_string(changeset.raw_id),
352 337 class_='tooltip',
353 338 title=tooltip_html
354 339 )
355 340
356 341 uri += '\n'
357 342 return uri
358 343 return literal(annotate_highlight(filenode, url_func, **kwargs))
359 344
360 def repo_name_slug(value):
361 """Return slug of name of repository
362 This function is called on each creation/modification
363 of repository to prevent bad names in repo
364 """
365
366 slug = remove_formatting(value)
367 slug = strip_tags(slug)
368
369 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
370 slug = slug.replace(c, '-')
371 slug = recursive_replace(slug, '-')
372 slug = collapse(slug, '-')
373 return slug
374
375 345 def get_changeset_safe(repo, rev):
376 346 from vcs.backends.base import BaseRepository
377 347 from vcs.exceptions import RepositoryError
378 348 if not isinstance(repo, BaseRepository):
379 349 raise Exception('You must pass an Repository '
380 350 'object as first argument got %s', type(repo))
381 351
382 352 try:
383 353 cs = repo.get_changeset(rev)
384 354 except RepositoryError:
385 355 from rhodecode.lib.utils import EmptyChangeset
386 356 cs = EmptyChangeset()
387 357 return cs
388 358
389 359
390 360 def is_following_repo(repo_name, user_id):
391 361 from rhodecode.model.scm import ScmModel
392 362 return ScmModel().is_following_repo(repo_name, user_id)
393 363
394 364 flash = _Flash()
395 365
396 366
397 367 #==============================================================================
398 368 # MERCURIAL FILTERS available via h.
399 369 #==============================================================================
400 370 from mercurial import util
401 371 from mercurial.templatefilters import person as _person
402 372
403 373 def _age(curdate):
404 374 """turns a datetime into an age string."""
405 375
406 376 if not curdate:
407 377 return ''
408 378
409 379 from datetime import timedelta, datetime
410 380
411 381 agescales = [("year", 3600 * 24 * 365),
412 382 ("month", 3600 * 24 * 30),
413 383 ("day", 3600 * 24),
414 384 ("hour", 3600),
415 385 ("minute", 60),
416 386 ("second", 1), ]
417 387
418 388 age = datetime.now() - curdate
419 389 age_seconds = (age.days * agescales[2][1]) + age.seconds
420 390 pos = 1
421 391 for scale in agescales:
422 392 if scale[1] <= age_seconds:
423 393 if pos == 6:pos = 5
424 394 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
425 395 pos += 1
426 396
427 397 return _('just now')
428 398
429 399 age = lambda x:_age(x)
430 400 capitalize = lambda x: x.capitalize()
431 401 email = util.email
432 402 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
433 403 person = lambda x: _person(x)
434 404 short_id = lambda x: x[:12]
435 405
436 406
437 407 def bool2icon(value):
438 408 """Returns True/False values represented as small html image of true/false
439 409 icons
440 410
441 411 :param value: bool value
442 412 """
443 413
444 414 if value is True:
445 415 return HTML.tag('img', src="/images/icons/accept.png", alt=_('True'))
446 416
447 417 if value is False:
448 418 return HTML.tag('img', src="/images/icons/cancel.png", alt=_('False'))
449 419
450 420 return value
451 421
452 422
453 423 def action_parser(user_log):
454 424 """This helper will map the specified string action into translated
455 425 fancy names with icons and links
456 426
457 427 :param user_log: user log instance
458 428 """
459 429
460 430 action = user_log.action
461 431 action_params = ' '
462 432
463 433 x = action.split(':')
464 434
465 435 if len(x) > 1:
466 436 action, action_params = x
467 437
468 438 def get_cs_links():
469 439 revs_limit = 5 #display this amount always
470 440 revs_top_limit = 50 #show upto this amount of changesets hidden
471 441 revs = action_params.split(',')
472 442 repo_name = user_log.repository.repo_name
473 443 from rhodecode.model.scm import ScmModel
474 444
475 445 message = lambda rev: get_changeset_safe(ScmModel().get(repo_name),
476 446 rev).message
477 447
478 448 cs_links = " " + ', '.join ([link_to(rev,
479 449 url('changeset_home',
480 450 repo_name=repo_name,
481 451 revision=rev), title=tooltip(message(rev)),
482 452 class_='tooltip') for rev in revs[:revs_limit] ])
483 453
484 454 compare_view = (' <div class="compare_view tooltip" title="%s">'
485 455 '<a href="%s">%s</a> '
486 456 '</div>' % (_('Show all combined changesets %s->%s' \
487 457 % (revs[0], revs[-1])),
488 458 url('changeset_home', repo_name=repo_name,
489 459 revision='%s...%s' % (revs[0], revs[-1])
490 460 ),
491 461 _('compare view'))
492 462 )
493 463
494 464 if len(revs) > revs_limit:
495 465 uniq_id = revs[0]
496 466 html_tmpl = ('<span> %s '
497 467 '<a class="show_more" id="_%s" href="#more">%s</a> '
498 468 '%s</span>')
499 469 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
500 470 % (len(revs) - revs_limit),
501 471 _('revisions'))
502 472
503 473 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
504 474 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
505 475 url('changeset_home',
506 476 repo_name=repo_name, revision=rev),
507 477 title=message(rev), class_='tooltip')
508 478 for rev in revs[revs_limit:revs_top_limit]]))
509 479 cs_links += compare_view
510 480 return cs_links
511 481
512 482 def get_fork_name():
513 483 from rhodecode.model.scm import ScmModel
514 484 repo_name = action_params
515 485 repo = ScmModel().get(repo_name)
516 486 if repo is None:
517 487 return repo_name
518 488 return link_to(action_params, url('summary_home',
519 489 repo_name=repo.name,),
520 490 title=repo.dbrepo.description)
521 491
522 492 map = {'user_deleted_repo':(_('User [deleted] repository'), None),
523 493 'user_created_repo':(_('User [created] repository'), None),
524 494 'user_forked_repo':(_('User [forked] repository as:'), get_fork_name),
525 495 'user_updated_repo':(_('User [updated] repository'), None),
526 496 'admin_deleted_repo':(_('Admin [delete] repository'), None),
527 497 'admin_created_repo':(_('Admin [created] repository'), None),
528 498 'admin_forked_repo':(_('Admin [forked] repository'), None),
529 499 'admin_updated_repo':(_('Admin [updated] repository'), None),
530 500 'push':(_('[Pushed]'), get_cs_links),
531 501 'pull':(_('[Pulled]'), None),
532 502 'started_following_repo':(_('User [started following] repository'), None),
533 503 'stopped_following_repo':(_('User [stopped following] repository'), None),
534 504 }
535 505
536 506 action_str = map.get(action, action)
537 507 action = action_str[0].replace('[', '<span class="journal_highlight">')\
538 508 .replace(']', '</span>')
539 509 if action_str[1] is not None:
540 510 action = action + " " + action_str[1]()
541 511
542 512 return literal(action)
543 513
544 514 def action_parser_icon(user_log):
545 515 action = user_log.action
546 516 action_params = None
547 517 x = action.split(':')
548 518
549 519 if len(x) > 1:
550 520 action, action_params = x
551 521
552 522 tmpl = """<img src="/images/icons/%s" alt="%s"/>"""
553 523 map = {'user_deleted_repo':'database_delete.png',
554 524 'user_created_repo':'database_add.png',
555 525 'user_forked_repo':'arrow_divide.png',
556 526 'user_updated_repo':'database_edit.png',
557 527 'admin_deleted_repo':'database_delete.png',
558 528 'admin_created_repo':'database_add.png',
559 529 'admin_forked_repo':'arrow_divide.png',
560 530 'admin_updated_repo':'database_edit.png',
561 531 'push':'script_add.png',
562 532 'pull':'down_16.png',
563 533 'started_following_repo':'heart_add.png',
564 534 'stopped_following_repo':'heart_delete.png',
565 535 }
566 536 return literal(tmpl % (map.get(action, action), action))
567 537
568 538
569 539 #==============================================================================
570 540 # PERMS
571 541 #==============================================================================
572 542 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
573 543 HasRepoPermissionAny, HasRepoPermissionAll
574 544
575 545 #==============================================================================
576 546 # GRAVATAR URL
577 547 #==============================================================================
578 548 import hashlib
579 549 import urllib
580 550 from pylons import request
581 551
582 552 def gravatar_url(email_address, size=30):
583 553 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
584 554 default = 'identicon'
585 555 baseurl_nossl = "http://www.gravatar.com/avatar/"
586 556 baseurl_ssl = "https://secure.gravatar.com/avatar/"
587 557 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
588 558
589 559
590 560 # construct the url
591 561 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
592 562 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
593 563
594 564 return gravatar_url
595 565
596 566 def safe_unicode(str):
597 567 """safe unicode function. In case of UnicodeDecode error we try to return
598 568 unicode with errors replace, if this failes we return unicode with
599 569 string_escape decoding """
600 570
601 571 try:
602 572 u_str = unicode(str)
603 573 except UnicodeDecodeError:
604 574 try:
605 575 u_str = unicode(str, 'utf-8', 'replace')
606 576 except UnicodeDecodeError:
607 577 #incase we have a decode error just represent as byte string
608 578 u_str = unicode(str(str).encode('string_escape'))
609 579
610 580 return u_str
611 581
612 582 def changed_tooltip(nodes):
613 583 if nodes:
614 584 pref = ': <br/> '
615 585 suf = ''
616 586 if len(nodes) > 30:
617 587 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
618 588 return literal(pref + '<br/> '.join([x.path for x in nodes[:30]]) + suf)
619 589 else:
620 590 return ': ' + _('No Files')
@@ -1,648 +1,682
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Utilities library for RhodeCode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27
28 28 import os
29 29 import logging
30 30 import datetime
31 31 import traceback
32 import paste
33 import beaker
34
35 from paste.script.command import Command, BadCommand
32 36
33 37 from UserDict import DictMixin
34 38
35 39 from mercurial import ui, config, hg
36 40 from mercurial.error import RepoError
37 41
38 import paste
39 import beaker
40 from paste.script.command import Command, BadCommand
42 from webhelpers.text import collapse, remove_formatting, strip_tags
41 43
42 44 from vcs.backends.base import BaseChangeset
43 45 from vcs.utils.lazy import LazyProperty
44 46
45 47 from rhodecode.model import meta
46 48 from rhodecode.model.caching_query import FromCache
47 49 from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, Group
48 50 from rhodecode.model.repo import RepoModel
49 51 from rhodecode.model.user import UserModel
50 52
51 53 log = logging.getLogger(__name__)
52 54
53 55
56 def recursive_replace(str, replace=' '):
57 """Recursive replace of given sign to just one instance
58
59 :param str: given string
60 :param replace: char to find and replace multiple instances
61
62 Examples::
63 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
64 'Mighty-Mighty-Bo-sstones'
65 """
66
67 if str.find(replace * 2) == -1:
68 return str
69 else:
70 str = str.replace(replace * 2, replace)
71 return recursive_replace(str, replace)
72
73 def repo_name_slug(value):
74 """Return slug of name of repository
75 This function is called on each creation/modification
76 of repository to prevent bad names in repo
77 """
78
79 slug = remove_formatting(value)
80 slug = strip_tags(slug)
81
82 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
83 slug = slug.replace(c, '-')
84 slug = recursive_replace(slug, '-')
85 slug = collapse(slug, '-')
86 return slug
87
54 88 def get_repo_slug(request):
55 89 return request.environ['pylons.routes_dict'].get('repo_name')
56 90
57 91 def action_logger(user, action, repo, ipaddr='', sa=None):
58 92 """
59 93 Action logger for various actions made by users
60 94
61 95 :param user: user that made this action, can be a unique username string or
62 96 object containing user_id attribute
63 97 :param action: action to log, should be on of predefined unique actions for
64 98 easy translations
65 99 :param repo: string name of repository or object containing repo_id,
66 100 that action was made on
67 101 :param ipaddr: optional ip address from what the action was made
68 102 :param sa: optional sqlalchemy session
69 103
70 104 """
71 105
72 106 if not sa:
73 107 sa = meta.Session()
74 108
75 109 try:
76 110 um = UserModel()
77 111 if hasattr(user, 'user_id'):
78 112 user_obj = user
79 113 elif isinstance(user, basestring):
80 114 user_obj = um.get_by_username(user, cache=False)
81 115 else:
82 116 raise Exception('You have to provide user object or username')
83 117
84 118
85 119 rm = RepoModel()
86 120 if hasattr(repo, 'repo_id'):
87 121 repo_obj = rm.get(repo.repo_id, cache=False)
88 122 repo_name = repo_obj.repo_name
89 123 elif isinstance(repo, basestring):
90 124 repo_name = repo.lstrip('/')
91 125 repo_obj = rm.get_by_repo_name(repo_name, cache=False)
92 126 else:
93 127 raise Exception('You have to provide repository to action logger')
94 128
95 129
96 130 user_log = UserLog()
97 131 user_log.user_id = user_obj.user_id
98 132 user_log.action = action
99 133
100 134 user_log.repository_id = repo_obj.repo_id
101 135 user_log.repository_name = repo_name
102 136
103 137 user_log.action_date = datetime.datetime.now()
104 138 user_log.user_ip = ipaddr
105 139 sa.add(user_log)
106 140 sa.commit()
107 141
108 142 log.info('Adding user %s, action %s on %s', user_obj, action, repo)
109 143 except:
110 144 log.error(traceback.format_exc())
111 145 sa.rollback()
112 146
113 147 def get_repos(path, recursive=False):
114 148 """
115 149 Scans given path for repos and return (name,(type,path)) tuple
116 150
117 151 :param path: path to scann for repositories
118 152 :param recursive: recursive search and return names with subdirs in front
119 153 """
120 154 from vcs.utils.helpers import get_scm
121 155 from vcs.exceptions import VCSError
122 156
123 157 if path.endswith('/'):
124 158 #add ending slash for better results
125 159 path = path[:-1]
126 160
127 161 def _get_repos(p):
128 162 for dirpath in os.listdir(p):
129 163 if os.path.isfile(os.path.join(p, dirpath)):
130 164 continue
131 165 cur_path = os.path.join(p, dirpath)
132 166 try:
133 167 scm_info = get_scm(cur_path)
134 168 yield scm_info[1].split(path)[-1].lstrip('/'), scm_info
135 169 except VCSError:
136 170 if not recursive:
137 171 continue
138 172 #check if this dir containts other repos for recursive scan
139 173 rec_path = os.path.join(p, dirpath)
140 174 if os.path.isdir(rec_path):
141 175 for inner_scm in _get_repos(rec_path):
142 176 yield inner_scm
143 177
144 178 return _get_repos(path)
145 179
146 180 def check_repo_fast(repo_name, base_path):
147 181 """
148 182 Check given path for existence of directory
149 183 :param repo_name:
150 184 :param base_path:
151 185
152 186 :return False: if this directory is present
153 187 """
154 188 if os.path.isdir(os.path.join(base_path, repo_name)):return False
155 189 return True
156 190
157 191 def check_repo(repo_name, base_path, verify=True):
158 192
159 193 repo_path = os.path.join(base_path, repo_name)
160 194
161 195 try:
162 196 if not check_repo_fast(repo_name, base_path):
163 197 return False
164 198 r = hg.repository(ui.ui(), repo_path)
165 199 if verify:
166 200 hg.verify(r)
167 201 #here we hnow that repo exists it was verified
168 202 log.info('%s repo is already created', repo_name)
169 203 return False
170 204 except RepoError:
171 205 #it means that there is no valid repo there...
172 206 log.info('%s repo is free for creation', repo_name)
173 207 return True
174 208
175 209 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
176 210 while True:
177 211 ok = raw_input(prompt)
178 212 if ok in ('y', 'ye', 'yes'): return True
179 213 if ok in ('n', 'no', 'nop', 'nope'): return False
180 214 retries = retries - 1
181 215 if retries < 0: raise IOError
182 216 print complaint
183 217
184 218 #propagated from mercurial documentation
185 219 ui_sections = ['alias', 'auth',
186 220 'decode/encode', 'defaults',
187 221 'diff', 'email',
188 222 'extensions', 'format',
189 223 'merge-patterns', 'merge-tools',
190 224 'hooks', 'http_proxy',
191 225 'smtp', 'patch',
192 226 'paths', 'profiling',
193 227 'server', 'trusted',
194 228 'ui', 'web', ]
195 229
196 230 def make_ui(read_from='file', path=None, checkpaths=True):
197 231 """A function that will read python rc files or database
198 232 and make an mercurial ui object from read options
199 233
200 234 :param path: path to mercurial config file
201 235 :param checkpaths: check the path
202 236 :param read_from: read from 'file' or 'db'
203 237 """
204 238
205 239 baseui = ui.ui()
206 240
207 241 #clean the baseui object
208 242 baseui._ocfg = config.config()
209 243 baseui._ucfg = config.config()
210 244 baseui._tcfg = config.config()
211 245
212 246 if read_from == 'file':
213 247 if not os.path.isfile(path):
214 248 log.warning('Unable to read config file %s' % path)
215 249 return False
216 250 log.debug('reading hgrc from %s', path)
217 251 cfg = config.config()
218 252 cfg.read(path)
219 253 for section in ui_sections:
220 254 for k, v in cfg.items(section):
221 255 log.debug('settings ui from file[%s]%s:%s', section, k, v)
222 256 baseui.setconfig(section, k, v)
223 257
224 258
225 259 elif read_from == 'db':
226 260 sa = meta.Session()
227 261 ret = sa.query(RhodeCodeUi)\
228 262 .options(FromCache("sql_cache_short",
229 263 "get_hg_ui_settings")).all()
230 264
231 265 hg_ui = ret
232 266 for ui_ in hg_ui:
233 267 if ui_.ui_active:
234 268 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
235 269 ui_.ui_key, ui_.ui_value)
236 270 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
237 271
238 272 meta.Session.remove()
239 273 return baseui
240 274
241 275
242 276 def set_rhodecode_config(config):
243 277 """Updates pylons config with new settings from database
244 278
245 279 :param config:
246 280 """
247 281 from rhodecode.model.settings import SettingsModel
248 282 hgsettings = SettingsModel().get_app_settings()
249 283
250 284 for k, v in hgsettings.items():
251 285 config[k] = v
252 286
253 287 def invalidate_cache(cache_key, *args):
254 288 """Puts cache invalidation task into db for
255 289 further global cache invalidation
256 290 """
257 291
258 292 from rhodecode.model.scm import ScmModel
259 293
260 294 if cache_key.startswith('get_repo_cached_'):
261 295 name = cache_key.split('get_repo_cached_')[-1]
262 296 ScmModel().mark_for_invalidation(name)
263 297
264 298 class EmptyChangeset(BaseChangeset):
265 299 """
266 300 An dummy empty changeset. It's possible to pass hash when creating
267 301 an EmptyChangeset
268 302 """
269 303
270 304 def __init__(self, cs='0' * 40):
271 305 self._empty_cs = cs
272 306 self.revision = -1
273 307 self.message = ''
274 308 self.author = ''
275 309 self.date = ''
276 310
277 311 @LazyProperty
278 312 def raw_id(self):
279 313 """Returns raw string identifying this changeset, useful for web
280 314 representation.
281 315 """
282 316
283 317 return self._empty_cs
284 318
285 319 @LazyProperty
286 320 def short_id(self):
287 321 return self.raw_id[:12]
288 322
289 323 def get_file_changeset(self, path):
290 324 return self
291 325
292 326 def get_file_content(self, path):
293 327 return u''
294 328
295 329 def get_file_size(self, path):
296 330 return 0
297 331
298 332 def map_groups(groups):
299 333 """Checks for groups existence, and creates groups structures.
300 334 It returns last group in structure
301 335
302 336 :param groups: list of groups structure
303 337 """
304 338 sa = meta.Session()
305 339
306 340 parent = None
307 341 group = None
308 342 for lvl, group_name in enumerate(groups[:-1]):
309 343 group = sa.query(Group).filter(Group.group_name == group_name).scalar()
310 344
311 345 if group is None:
312 346 group = Group(group_name, parent)
313 347 sa.add(group)
314 348 sa.commit()
315 349
316 350 parent = group
317 351
318 352 return group
319 353
320 354 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
321 355 """maps all repos given in initial_repo_list, non existing repositories
322 356 are created, if remove_obsolete is True it also check for db entries
323 357 that are not in initial_repo_list and removes them.
324 358
325 359 :param initial_repo_list: list of repositories found by scanning methods
326 360 :param remove_obsolete: check for obsolete entries in database
327 361 """
328 362
329 363 sa = meta.Session()
330 364 rm = RepoModel()
331 365 user = sa.query(User).filter(User.admin == True).first()
332 366
333 367 for name, repo in initial_repo_list.items():
334 368 group = map_groups(name.split('/'))
335 369 if not rm.get_by_repo_name(name, cache=False):
336 370 log.info('repository %s not found creating default', name)
337 371
338 372 form_data = {
339 373 'repo_name':name,
340 374 'repo_type':repo.alias,
341 375 'description':repo.description \
342 376 if repo.description != 'unknown' else \
343 377 '%s repository' % name,
344 378 'private':False,
345 379 'group_id':getattr(group, 'group_id', None)
346 380 }
347 381 rm.create(form_data, user, just_db=True)
348 382
349 383 if remove_obsolete:
350 384 #remove from database those repositories that are not in the filesystem
351 385 for repo in sa.query(Repository).all():
352 386 if repo.repo_name not in initial_repo_list.keys():
353 387 sa.delete(repo)
354 388 sa.commit()
355 389
356 390 class OrderedDict(dict, DictMixin):
357 391
358 392 def __init__(self, *args, **kwds):
359 393 if len(args) > 1:
360 394 raise TypeError('expected at most 1 arguments, got %d' % len(args))
361 395 try:
362 396 self.__end
363 397 except AttributeError:
364 398 self.clear()
365 399 self.update(*args, **kwds)
366 400
367 401 def clear(self):
368 402 self.__end = end = []
369 403 end += [None, end, end] # sentinel node for doubly linked list
370 404 self.__map = {} # key --> [key, prev, next]
371 405 dict.clear(self)
372 406
373 407 def __setitem__(self, key, value):
374 408 if key not in self:
375 409 end = self.__end
376 410 curr = end[1]
377 411 curr[2] = end[1] = self.__map[key] = [key, curr, end]
378 412 dict.__setitem__(self, key, value)
379 413
380 414 def __delitem__(self, key):
381 415 dict.__delitem__(self, key)
382 416 key, prev, next = self.__map.pop(key)
383 417 prev[2] = next
384 418 next[1] = prev
385 419
386 420 def __iter__(self):
387 421 end = self.__end
388 422 curr = end[2]
389 423 while curr is not end:
390 424 yield curr[0]
391 425 curr = curr[2]
392 426
393 427 def __reversed__(self):
394 428 end = self.__end
395 429 curr = end[1]
396 430 while curr is not end:
397 431 yield curr[0]
398 432 curr = curr[1]
399 433
400 434 def popitem(self, last=True):
401 435 if not self:
402 436 raise KeyError('dictionary is empty')
403 437 if last:
404 438 key = reversed(self).next()
405 439 else:
406 440 key = iter(self).next()
407 441 value = self.pop(key)
408 442 return key, value
409 443
410 444 def __reduce__(self):
411 445 items = [[k, self[k]] for k in self]
412 446 tmp = self.__map, self.__end
413 447 del self.__map, self.__end
414 448 inst_dict = vars(self).copy()
415 449 self.__map, self.__end = tmp
416 450 if inst_dict:
417 451 return (self.__class__, (items,), inst_dict)
418 452 return self.__class__, (items,)
419 453
420 454 def keys(self):
421 455 return list(self)
422 456
423 457 setdefault = DictMixin.setdefault
424 458 update = DictMixin.update
425 459 pop = DictMixin.pop
426 460 values = DictMixin.values
427 461 items = DictMixin.items
428 462 iterkeys = DictMixin.iterkeys
429 463 itervalues = DictMixin.itervalues
430 464 iteritems = DictMixin.iteritems
431 465
432 466 def __repr__(self):
433 467 if not self:
434 468 return '%s()' % (self.__class__.__name__,)
435 469 return '%s(%r)' % (self.__class__.__name__, self.items())
436 470
437 471 def copy(self):
438 472 return self.__class__(self)
439 473
440 474 @classmethod
441 475 def fromkeys(cls, iterable, value=None):
442 476 d = cls()
443 477 for key in iterable:
444 478 d[key] = value
445 479 return d
446 480
447 481 def __eq__(self, other):
448 482 if isinstance(other, OrderedDict):
449 483 return len(self) == len(other) and self.items() == other.items()
450 484 return dict.__eq__(self, other)
451 485
452 486 def __ne__(self, other):
453 487 return not self == other
454 488
455 489
456 490 #set cache regions for beaker so celery can utilise it
457 491 def add_cache(settings):
458 492 cache_settings = {'regions':None}
459 493 for key in settings.keys():
460 494 for prefix in ['beaker.cache.', 'cache.']:
461 495 if key.startswith(prefix):
462 496 name = key.split(prefix)[1].strip()
463 497 cache_settings[name] = settings[key].strip()
464 498 if cache_settings['regions']:
465 499 for region in cache_settings['regions'].split(','):
466 500 region = region.strip()
467 501 region_settings = {}
468 502 for key, value in cache_settings.items():
469 503 if key.startswith(region):
470 504 region_settings[key.split('.')[1]] = value
471 505 region_settings['expire'] = int(region_settings.get('expire',
472 506 60))
473 507 region_settings.setdefault('lock_dir',
474 508 cache_settings.get('lock_dir'))
475 509 if 'type' not in region_settings:
476 510 region_settings['type'] = cache_settings.get('type',
477 511 'memory')
478 512 beaker.cache.cache_regions[region] = region_settings
479 513
480 514 def get_current_revision():
481 515 """Returns tuple of (number, id) from repository containing this package
482 516 or None if repository could not be found.
483 517 """
484 518
485 519 try:
486 520 from vcs import get_repo
487 521 from vcs.utils.helpers import get_scm
488 522 from vcs.exceptions import RepositoryError, VCSError
489 523 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
490 524 scm = get_scm(repopath)[0]
491 525 repo = get_repo(path=repopath, alias=scm)
492 526 tip = repo.get_changeset()
493 527 return (tip.revision, tip.short_id)
494 528 except (ImportError, RepositoryError, VCSError), err:
495 529 logging.debug("Cannot retrieve rhodecode's revision. Original error "
496 530 "was: %s" % err)
497 531 return None
498 532
499 533 #===============================================================================
500 534 # TEST FUNCTIONS AND CREATORS
501 535 #===============================================================================
502 536 def create_test_index(repo_location, full_index):
503 537 """Makes default test index
504 538 :param repo_location:
505 539 :param full_index:
506 540 """
507 541 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
508 542 from rhodecode.lib.pidlock import DaemonLock, LockHeld
509 543 import shutil
510 544
511 545 index_location = os.path.join(repo_location, 'index')
512 546 if os.path.exists(index_location):
513 547 shutil.rmtree(index_location)
514 548
515 549 try:
516 550 l = DaemonLock()
517 551 WhooshIndexingDaemon(index_location=index_location,
518 552 repo_location=repo_location)\
519 553 .run(full_index=full_index)
520 554 l.release()
521 555 except LockHeld:
522 556 pass
523 557
524 558 def create_test_env(repos_test_path, config):
525 559 """Makes a fresh database and
526 560 install test repository into tmp dir
527 561 """
528 562 from rhodecode.lib.db_manage import DbManage
529 563 from rhodecode.tests import HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, \
530 564 HG_FORK, GIT_FORK, TESTS_TMP_PATH
531 565 import tarfile
532 566 import shutil
533 567 from os.path import dirname as dn, join as jn, abspath
534 568
535 569 log = logging.getLogger('TestEnvCreator')
536 570 # create logger
537 571 log.setLevel(logging.DEBUG)
538 572 log.propagate = True
539 573 # create console handler and set level to debug
540 574 ch = logging.StreamHandler()
541 575 ch.setLevel(logging.DEBUG)
542 576
543 577 # create formatter
544 578 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
545 579
546 580 # add formatter to ch
547 581 ch.setFormatter(formatter)
548 582
549 583 # add ch to logger
550 584 log.addHandler(ch)
551 585
552 586 #PART ONE create db
553 587 dbconf = config['sqlalchemy.db1.url']
554 588 log.debug('making test db %s', dbconf)
555 589
556 590 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
557 591 tests=True)
558 592 dbmanage.create_tables(override=True)
559 593 dbmanage.config_prompt(repos_test_path)
560 594 dbmanage.create_default_user()
561 595 dbmanage.admin_prompt()
562 596 dbmanage.create_permissions()
563 597 dbmanage.populate_default_permissions()
564 598
565 599 #PART TWO make test repo
566 600 log.debug('making test vcs repositories')
567 601
568 602 #remove old one from previos tests
569 603 for r in [HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, HG_FORK, GIT_FORK]:
570 604
571 605 if os.path.isdir(jn(TESTS_TMP_PATH, r)):
572 606 log.debug('removing %s', r)
573 607 shutil.rmtree(jn(TESTS_TMP_PATH, r))
574 608
575 609 #CREATE DEFAULT HG REPOSITORY
576 610 cur_dir = dn(dn(abspath(__file__)))
577 611 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
578 612 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
579 613 tar.close()
580 614
581 615
582 616 #==============================================================================
583 617 # PASTER COMMANDS
584 618 #==============================================================================
585 619
586 620 class BasePasterCommand(Command):
587 621 """
588 622 Abstract Base Class for paster commands.
589 623
590 624 The celery commands are somewhat aggressive about loading
591 625 celery.conf, and since our module sets the `CELERY_LOADER`
592 626 environment variable to our loader, we have to bootstrap a bit and
593 627 make sure we've had a chance to load the pylons config off of the
594 628 command line, otherwise everything fails.
595 629 """
596 630 min_args = 1
597 631 min_args_error = "Please provide a paster config file as an argument."
598 632 takes_config_file = 1
599 633 requires_config_file = True
600 634
601 635 def notify_msg(self, msg, log=False):
602 636 """Make a notification to user, additionally if logger is passed
603 637 it logs this action using given logger
604 638
605 639 :param msg: message that will be printed to user
606 640 :param log: logging instance, to use to additionally log this message
607 641
608 642 """
609 643 if log and isinstance(log, logging):
610 644 log(msg)
611 645
612 646
613 647 def run(self, args):
614 648 """
615 649 Overrides Command.run
616 650
617 651 Checks for a config file argument and loads it.
618 652 """
619 653 if len(args) < self.min_args:
620 654 raise BadCommand(
621 655 self.min_args_error % {'min_args': self.min_args,
622 656 'actual_args': len(args)})
623 657
624 658 # Decrement because we're going to lob off the first argument.
625 659 # @@ This is hacky
626 660 self.min_args -= 1
627 661 self.bootstrap_config(args[0])
628 662 self.update_parser()
629 663 return super(BasePasterCommand, self).run(args[1:])
630 664
631 665 def update_parser(self):
632 666 """
633 667 Abstract method. Allows for the class's parser to be updated
634 668 before the superclass's `run` method is called. Necessary to
635 669 allow options/arguments to be passed through to the underlying
636 670 celery command.
637 671 """
638 672 raise NotImplementedError("Abstract Method.")
639 673
640 674 def bootstrap_config(self, conf):
641 675 """
642 676 Loads the pylons configuration.
643 677 """
644 678 from pylons import config as pylonsconfig
645 679
646 680 path_to_ini_file = os.path.realpath(conf)
647 681 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
648 682 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
@@ -1,552 +1,551
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14 14
15 15
16 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 import os
23 23 import re
24 24 import logging
25 25
26 26 import formencode
27 27 from formencode import All
28 28 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
29 29 Email, Bool, StringBoolean, Set
30 30
31 31 from pylons.i18n.translation import _
32 from webhelpers.pylonslib.secure_form import authentication_token
32 33
33 import rhodecode.lib.helpers as h
34 from rhodecode.lib.utils import repo_name_slug
34 35 from rhodecode.lib.auth import authenticate, get_crypt_password
35 36 from rhodecode.lib.exceptions import LdapImportError
36 37 from rhodecode.model import meta
37 38 from rhodecode.model.user import UserModel
38 39 from rhodecode.model.repo import RepoModel
39 40 from rhodecode.model.users_group import UsersGroupModel
40 41 from rhodecode.model.db import User, UsersGroup
41 42 from rhodecode import BACKENDS
42 43
43 from webhelpers.pylonslib.secure_form import authentication_token
44
45 44 log = logging.getLogger(__name__)
46 45
47 46 #this is needed to translate the messages using _() in validators
48 47 class State_obj(object):
49 48 _ = staticmethod(_)
50 49
51 50 #===============================================================================
52 51 # VALIDATORS
53 52 #===============================================================================
54 53 class ValidAuthToken(formencode.validators.FancyValidator):
55 54 messages = {'invalid_token':_('Token mismatch')}
56 55
57 56 def validate_python(self, value, state):
58 57
59 58 if value != authentication_token():
60 59 raise formencode.Invalid(self.message('invalid_token', state,
61 60 search_number=value), value, state)
62 61
63 62 def ValidUsername(edit, old_data):
64 63 class _ValidUsername(formencode.validators.FancyValidator):
65 64
66 65 def validate_python(self, value, state):
67 66 if value in ['default', 'new_user']:
68 67 raise formencode.Invalid(_('Invalid username'), value, state)
69 68 #check if user is unique
70 69 old_un = None
71 70 if edit:
72 71 old_un = UserModel().get(old_data.get('user_id')).username
73 72
74 73 if old_un != value or not edit:
75 74 if UserModel().get_by_username(value, cache=False,
76 75 case_insensitive=True):
77 76 raise formencode.Invalid(_('This username already exists') ,
78 77 value, state)
79 78
80 79
81 80 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
82 81 raise formencode.Invalid(_('Username may only contain '
83 82 'alphanumeric characters underscores, '
84 83 'periods or dashes and must begin with '
85 84 'alphanumeric character'),
86 85 value, state)
87 86
88 87
89 88
90 89 return _ValidUsername
91 90
92 91
93 92
94 93 def ValidUsersGroup(edit, old_data):
95 94
96 95 class _ValidUsersGroup(formencode.validators.FancyValidator):
97 96
98 97 def validate_python(self, value, state):
99 98 if value in ['default']:
100 99 raise formencode.Invalid(_('Invalid group name'), value, state)
101 100 #check if group is unique
102 101 old_ugname = None
103 102 if edit:
104 103 old_ugname = UsersGroupModel()\
105 104 .get(old_data.get('users_group_id')).users_group_name
106 105
107 106 if old_ugname != value or not edit:
108 107 if UsersGroupModel().get_by_groupname(value, cache=False,
109 108 case_insensitive=True):
110 109 raise formencode.Invalid(_('This users group already exists') ,
111 110 value, state)
112 111
113 112
114 113 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
115 114 raise formencode.Invalid(_('Group name may only contain '
116 115 'alphanumeric characters underscores, '
117 116 'periods or dashes and must begin with '
118 117 'alphanumeric character'),
119 118 value, state)
120 119
121 120 return _ValidUsersGroup
122 121
123 122
124 123
125 124 class ValidPassword(formencode.validators.FancyValidator):
126 125
127 126 def to_python(self, value, state):
128 127
129 128 if value:
130 129
131 130 if value.get('password'):
132 131 try:
133 132 value['password'] = get_crypt_password(value['password'])
134 133 except UnicodeEncodeError:
135 134 e_dict = {'password':_('Invalid characters in password')}
136 135 raise formencode.Invalid('', value, state, error_dict=e_dict)
137 136
138 137 if value.get('password_confirmation'):
139 138 try:
140 139 value['password_confirmation'] = \
141 140 get_crypt_password(value['password_confirmation'])
142 141 except UnicodeEncodeError:
143 142 e_dict = {'password_confirmation':_('Invalid characters in password')}
144 143 raise formencode.Invalid('', value, state, error_dict=e_dict)
145 144
146 145 if value.get('new_password'):
147 146 try:
148 147 value['new_password'] = \
149 148 get_crypt_password(value['new_password'])
150 149 except UnicodeEncodeError:
151 150 e_dict = {'new_password':_('Invalid characters in password')}
152 151 raise formencode.Invalid('', value, state, error_dict=e_dict)
153 152
154 153 return value
155 154
156 155 class ValidPasswordsMatch(formencode.validators.FancyValidator):
157 156
158 157 def validate_python(self, value, state):
159 158
160 159 if value['password'] != value['password_confirmation']:
161 160 e_dict = {'password_confirmation':
162 161 _('Password do not match')}
163 162 raise formencode.Invalid('', value, state, error_dict=e_dict)
164 163
165 164 class ValidAuth(formencode.validators.FancyValidator):
166 165 messages = {
167 166 'invalid_password':_('invalid password'),
168 167 'invalid_login':_('invalid user name'),
169 168 'disabled_account':_('Your account is disabled')
170 169
171 170 }
172 171 #error mapping
173 172 e_dict = {'username':messages['invalid_login'],
174 173 'password':messages['invalid_password']}
175 174 e_dict_disable = {'username':messages['disabled_account']}
176 175
177 176 def validate_python(self, value, state):
178 177 password = value['password']
179 178 username = value['username']
180 179 user = UserModel().get_by_username(username)
181 180
182 181 if authenticate(username, password):
183 182 return value
184 183 else:
185 184 if user and user.active is False:
186 185 log.warning('user %s is disabled', username)
187 186 raise formencode.Invalid(self.message('disabled_account',
188 187 state=State_obj),
189 188 value, state,
190 189 error_dict=self.e_dict_disable)
191 190 else:
192 191 log.warning('user %s not authenticated', username)
193 192 raise formencode.Invalid(self.message('invalid_password',
194 193 state=State_obj), value, state,
195 194 error_dict=self.e_dict)
196 195
197 196 class ValidRepoUser(formencode.validators.FancyValidator):
198 197
199 198 def to_python(self, value, state):
200 199 sa = meta.Session()
201 200 try:
202 201 self.user_db = sa.query(User)\
203 202 .filter(User.active == True)\
204 203 .filter(User.username == value).one()
205 204 except Exception:
206 205 raise formencode.Invalid(_('This username is not valid'),
207 206 value, state)
208 207 finally:
209 208 meta.Session.remove()
210 209
211 210 return self.user_db.user_id
212 211
213 212 def ValidRepoName(edit, old_data):
214 213 class _ValidRepoName(formencode.validators.FancyValidator):
215 214
216 215 def to_python(self, value, state):
217 slug = h.repo_name_slug(value)
216 slug = repo_name_slug(value)
218 217 if slug in ['_admin']:
219 218 raise formencode.Invalid(_('This repository name is disallowed'),
220 219 value, state)
221 220 if old_data.get('repo_name') != value or not edit:
222 221 if RepoModel().get_by_repo_name(slug, cache=False):
223 222 raise formencode.Invalid(_('This repository already exists') ,
224 223 value, state)
225 224 return slug
226 225
227 226
228 227 return _ValidRepoName
229 228
230 229 def ValidForkType(old_data):
231 230 class _ValidForkType(formencode.validators.FancyValidator):
232 231
233 232 def to_python(self, value, state):
234 233 if old_data['repo_type'] != value:
235 234 raise formencode.Invalid(_('Fork have to be the same type as original'),
236 235 value, state)
237 236 return value
238 237 return _ValidForkType
239 238
240 239 class ValidPerms(formencode.validators.FancyValidator):
241 240 messages = {'perm_new_member_name':_('This username or users group name'
242 241 ' is not valid')}
243 242
244 243 def to_python(self, value, state):
245 244 perms_update = []
246 245 perms_new = []
247 246 #build a list of permission to update and new permission to create
248 247 for k, v in value.items():
249 248 #means new added member to permissions
250 249 if k.startswith('perm_new_member'):
251 250 new_perm = value.get('perm_new_member', False)
252 251 new_member = value.get('perm_new_member_name', False)
253 252 new_type = value.get('perm_new_member_type')
254 253
255 254 if new_member and new_perm:
256 255 if (new_member, new_perm, new_type) not in perms_new:
257 256 perms_new.append((new_member, new_perm, new_type))
258 257 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
259 258 member = k[7:]
260 259 t = {'u':'user',
261 260 'g':'users_group'}[k[0]]
262 261 if member == 'default':
263 262 if value['private']:
264 263 #set none for default when updating to private repo
265 264 v = 'repository.none'
266 265 perms_update.append((member, v, t))
267 266
268 267 value['perms_updates'] = perms_update
269 268 value['perms_new'] = perms_new
270 269
271 270 #update permissions
272 271 sa = meta.Session
273 272 for k, v, t in perms_new:
274 273 try:
275 274 if t is 'user':
276 275 self.user_db = sa.query(User)\
277 276 .filter(User.active == True)\
278 277 .filter(User.username == k).one()
279 278 if t is 'users_group':
280 279 self.user_db = sa.query(UsersGroup)\
281 280 .filter(UsersGroup.users_group_active == True)\
282 281 .filter(UsersGroup.users_group_name == k).one()
283 282
284 283 except Exception:
285 284 msg = self.message('perm_new_member_name',
286 285 state=State_obj)
287 286 raise formencode.Invalid(msg, value, state,
288 287 error_dict={'perm_new_member_name':msg})
289 288 return value
290 289
291 290 class ValidSettings(formencode.validators.FancyValidator):
292 291
293 292 def to_python(self, value, state):
294 293 #settings form can't edit user
295 294 if value.has_key('user'):
296 295 del['value']['user']
297 296
298 297 return value
299 298
300 299 class ValidPath(formencode.validators.FancyValidator):
301 300 def to_python(self, value, state):
302 301
303 302 if not os.path.isdir(value):
304 303 msg = _('This is not a valid path')
305 304 raise formencode.Invalid(msg, value, state,
306 305 error_dict={'paths_root_path':msg})
307 306 return value
308 307
309 308 def UniqSystemEmail(old_data):
310 309 class _UniqSystemEmail(formencode.validators.FancyValidator):
311 310 def to_python(self, value, state):
312 311 value = value.lower()
313 312 if old_data.get('email') != value:
314 313 sa = meta.Session()
315 314 try:
316 315 user = sa.query(User).filter(User.email == value).scalar()
317 316 if user:
318 317 raise formencode.Invalid(_("This e-mail address is already taken") ,
319 318 value, state)
320 319 finally:
321 320 meta.Session.remove()
322 321
323 322 return value
324 323
325 324 return _UniqSystemEmail
326 325
327 326 class ValidSystemEmail(formencode.validators.FancyValidator):
328 327 def to_python(self, value, state):
329 328 value = value.lower()
330 329 sa = meta.Session
331 330 try:
332 331 user = sa.query(User).filter(User.email == value).scalar()
333 332 if user is None:
334 333 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
335 334 value, state)
336 335 finally:
337 336 meta.Session.remove()
338 337
339 338 return value
340 339
341 340 class LdapLibValidator(formencode.validators.FancyValidator):
342 341
343 342 def to_python(self, value, state):
344 343
345 344 try:
346 345 import ldap
347 346 except ImportError:
348 347 raise LdapImportError
349 348 return value
350 349
351 350 class AttrLoginValidator(formencode.validators.FancyValidator):
352 351
353 352 def to_python(self, value, state):
354 353
355 354 if not value or not isinstance(value, (str, unicode)):
356 355 raise formencode.Invalid(_("The LDAP Login attribute of the CN "
357 356 "must be specified - this is the name "
358 357 "of the attribute that is equivalent "
359 358 "to 'username'"),
360 359 value, state)
361 360
362 361 return value
363 362
364 363 #===============================================================================
365 364 # FORMS
366 365 #===============================================================================
367 366 class LoginForm(formencode.Schema):
368 367 allow_extra_fields = True
369 368 filter_extra_fields = True
370 369 username = UnicodeString(
371 370 strip=True,
372 371 min=1,
373 372 not_empty=True,
374 373 messages={
375 374 'empty':_('Please enter a login'),
376 375 'tooShort':_('Enter a value %(min)i characters long or more')}
377 376 )
378 377
379 378 password = UnicodeString(
380 379 strip=True,
381 380 min=6,
382 381 not_empty=True,
383 382 messages={
384 383 'empty':_('Please enter a password'),
385 384 'tooShort':_('Enter %(min)i characters or more')}
386 385 )
387 386
388 387
389 388 #chained validators have access to all data
390 389 chained_validators = [ValidAuth]
391 390
392 391 def UserForm(edit=False, old_data={}):
393 392 class _UserForm(formencode.Schema):
394 393 allow_extra_fields = True
395 394 filter_extra_fields = True
396 395 username = All(UnicodeString(strip=True, min=1, not_empty=True),
397 396 ValidUsername(edit, old_data))
398 397 if edit:
399 398 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
400 399 admin = StringBoolean(if_missing=False)
401 400 else:
402 401 password = All(UnicodeString(strip=True, min=6, not_empty=True))
403 402 active = StringBoolean(if_missing=False)
404 403 name = UnicodeString(strip=True, min=1, not_empty=True)
405 404 lastname = UnicodeString(strip=True, min=1, not_empty=True)
406 405 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
407 406
408 407 chained_validators = [ValidPassword]
409 408
410 409 return _UserForm
411 410
412 411
413 412 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
414 413 class _UsersGroupForm(formencode.Schema):
415 414 allow_extra_fields = True
416 415 filter_extra_fields = True
417 416
418 417 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
419 418 ValidUsersGroup(edit, old_data))
420 419
421 420 users_group_active = StringBoolean(if_missing=False)
422 421
423 422 if edit:
424 423 users_group_members = OneOf(available_members, hideList=False,
425 424 testValueList=True,
426 425 if_missing=None, not_empty=False)
427 426
428 427 return _UsersGroupForm
429 428
430 429 def RegisterForm(edit=False, old_data={}):
431 430 class _RegisterForm(formencode.Schema):
432 431 allow_extra_fields = True
433 432 filter_extra_fields = True
434 433 username = All(ValidUsername(edit, old_data),
435 434 UnicodeString(strip=True, min=1, not_empty=True))
436 435 password = All(UnicodeString(strip=True, min=6, not_empty=True))
437 436 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
438 437 active = StringBoolean(if_missing=False)
439 438 name = UnicodeString(strip=True, min=1, not_empty=True)
440 439 lastname = UnicodeString(strip=True, min=1, not_empty=True)
441 440 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
442 441
443 442 chained_validators = [ValidPasswordsMatch, ValidPassword]
444 443
445 444 return _RegisterForm
446 445
447 446 def PasswordResetForm():
448 447 class _PasswordResetForm(formencode.Schema):
449 448 allow_extra_fields = True
450 449 filter_extra_fields = True
451 450 email = All(ValidSystemEmail(), Email(not_empty=True))
452 451 return _PasswordResetForm
453 452
454 453 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
455 454 class _RepoForm(formencode.Schema):
456 455 allow_extra_fields = True
457 456 filter_extra_fields = False
458 457 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
459 458 ValidRepoName(edit, old_data))
460 459 description = UnicodeString(strip=True, min=1, not_empty=True)
461 460 private = StringBoolean(if_missing=False)
462 461 enable_statistics = StringBoolean(if_missing=False)
463 462 enable_downloads = StringBoolean(if_missing=False)
464 463 repo_type = OneOf(supported_backends)
465 464 if edit:
466 465 #this is repo owner
467 466 user = All(Int(not_empty=True), ValidRepoUser)
468 467
469 468 chained_validators = [ValidPerms]
470 469 return _RepoForm
471 470
472 471 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
473 472 class _RepoForkForm(formencode.Schema):
474 473 allow_extra_fields = True
475 474 filter_extra_fields = False
476 475 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
477 476 ValidRepoName(edit, old_data))
478 477 description = UnicodeString(strip=True, min=1, not_empty=True)
479 478 private = StringBoolean(if_missing=False)
480 479 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
481 480 return _RepoForkForm
482 481
483 482 def RepoSettingsForm(edit=False, old_data={}):
484 483 class _RepoForm(formencode.Schema):
485 484 allow_extra_fields = True
486 485 filter_extra_fields = False
487 486 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
488 487 ValidRepoName(edit, old_data))
489 488 description = UnicodeString(strip=True, min=1, not_empty=True)
490 489 private = StringBoolean(if_missing=False)
491 490
492 491 chained_validators = [ValidPerms, ValidSettings]
493 492 return _RepoForm
494 493
495 494
496 495 def ApplicationSettingsForm():
497 496 class _ApplicationSettingsForm(formencode.Schema):
498 497 allow_extra_fields = True
499 498 filter_extra_fields = False
500 499 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
501 500 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
502 501 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
503 502
504 503 return _ApplicationSettingsForm
505 504
506 505 def ApplicationUiSettingsForm():
507 506 class _ApplicationUiSettingsForm(formencode.Schema):
508 507 allow_extra_fields = True
509 508 filter_extra_fields = False
510 509 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
511 510 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
512 511 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
513 512 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
514 513 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
515 514 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
516 515
517 516 return _ApplicationUiSettingsForm
518 517
519 518 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
520 519 class _DefaultPermissionsForm(formencode.Schema):
521 520 allow_extra_fields = True
522 521 filter_extra_fields = True
523 522 overwrite_default = StringBoolean(if_missing=False)
524 523 anonymous = OneOf(['True', 'False'], if_missing=False)
525 524 default_perm = OneOf(perms_choices)
526 525 default_register = OneOf(register_choices)
527 526 default_create = OneOf(create_choices)
528 527
529 528 return _DefaultPermissionsForm
530 529
531 530
532 531 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices):
533 532 class _LdapSettingsForm(formencode.Schema):
534 533 allow_extra_fields = True
535 534 filter_extra_fields = True
536 535 pre_validators = [LdapLibValidator]
537 536 ldap_active = StringBoolean(if_missing=False)
538 537 ldap_host = UnicodeString(strip=True,)
539 538 ldap_port = Number(strip=True,)
540 539 ldap_ldaps = StringBoolean(if_missing=False)
541 540 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
542 541 ldap_dn_user = UnicodeString(strip=True,)
543 542 ldap_dn_pass = UnicodeString(strip=True,)
544 543 ldap_base_dn = UnicodeString(strip=True,)
545 544 ldap_filter = UnicodeString(strip=True,)
546 545 ldap_search_scope = OneOf(search_scope_choices)
547 546 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
548 547 ldap_attr_firstname = UnicodeString(strip=True,)
549 548 ldap_attr_lastname = UnicodeString(strip=True,)
550 549 ldap_attr_email = UnicodeString(strip=True,)
551 550
552 551 return _LdapSettingsForm
@@ -1,385 +1,384
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.scm
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Scm model for RhodeCode
7 7
8 8 :created_on: Apr 9, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27 import os
28 28 import time
29 29 import traceback
30 30 import logging
31 31
32 from mercurial import ui
33
34 from sqlalchemy.orm import joinedload
35 from sqlalchemy.orm.session import make_transient
36 from sqlalchemy.exc import DatabaseError
37
38 from beaker.cache import cache_region, region_invalidate
39
32 40 from vcs import get_backend
33 41 from vcs.utils.helpers import get_scm
34 42 from vcs.exceptions import RepositoryError, VCSError
35 43 from vcs.utils.lazy import LazyProperty
36 44
37 from mercurial import ui
38
39 from beaker.cache import cache_region, region_invalidate
40
41 45 from rhodecode import BACKENDS
42 46 from rhodecode.lib import helpers as h
43 47 from rhodecode.lib.auth import HasRepoPermissionAny
44 48 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, action_logger
45 49 from rhodecode.model import BaseModel
46 50 from rhodecode.model.user import UserModel
47
48 51 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
49 52 UserFollowing, UserLog
50 53 from rhodecode.model.caching_query import FromCache
51 54
52 from sqlalchemy.orm import joinedload
53 from sqlalchemy.orm.session import make_transient
54 from sqlalchemy.exc import DatabaseError
55
56 55 log = logging.getLogger(__name__)
57 56
58 57
59 58 class UserTemp(object):
60 59 def __init__(self, user_id):
61 60 self.user_id = user_id
62 61
63 62 def __repr__(self):
64 63 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
65 64
66 65 class RepoTemp(object):
67 66 def __init__(self, repo_id):
68 67 self.repo_id = repo_id
69 68
70 69 def __repr__(self):
71 70 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
72 71
73 72 class ScmModel(BaseModel):
74 73 """Generic Scm Model
75 74 """
76 75
77 76 @LazyProperty
78 77 def repos_path(self):
79 78 """Get's the repositories root path from database
80 79 """
81 80
82 81 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
83 82
84 83 return q.ui_value
85 84
86 85 def repo_scan(self, repos_path, baseui):
87 86 """Listing of repositories in given path. This path should not be a
88 87 repository itself. Return a dictionary of repository objects
89 88
90 89 :param repos_path: path to directory containing repositories
91 90 :param baseui: baseui instance to instantiate MercurialRepostitory with
92 91 """
93 92
94 93 log.info('scanning for repositories in %s', repos_path)
95 94
96 95 if not isinstance(baseui, ui.ui):
97 96 baseui = make_ui('db')
98 97 repos_list = {}
99 98
100 99 for name, path in get_filesystem_repos(repos_path, recursive=True):
101 100 try:
102 101 if repos_list.has_key(name):
103 102 raise RepositoryError('Duplicate repository name %s '
104 103 'found in %s' % (name, path))
105 104 else:
106 105
107 106 klass = get_backend(path[0])
108 107
109 108 if path[0] == 'hg' and path[0] in BACKENDS.keys():
110 109 repos_list[name] = klass(path[1], baseui=baseui)
111 110
112 111 if path[0] == 'git' and path[0] in BACKENDS.keys():
113 112 repos_list[name] = klass(path[1])
114 113 except OSError:
115 114 continue
116 115
117 116 return repos_list
118 117
119 118 def get_repos(self, all_repos=None):
120 119 """Get all repos from db and for each repo create it's backend instance.
121 120 and fill that backed with information from database
122 121
123 122 :param all_repos: give specific repositories list, good for filtering
124 123 """
125 124
126 125 if all_repos is None:
127 126 all_repos = self.sa.query(Repository)\
128 127 .order_by(Repository.repo_name).all()
129 128
130 129 #get the repositories that should be invalidated
131 130 invalidation_list = [str(x.cache_key) for x in \
132 131 self.sa.query(CacheInvalidation.cache_key)\
133 132 .filter(CacheInvalidation.cache_active == False)\
134 133 .all()]
135 134
136 135 for r in all_repos:
137 136
138 137 repo = self.get(r.repo_name, invalidation_list)
139 138
140 139 if repo is not None:
141 140 last_change = repo.last_change
142 141 tip = h.get_changeset_safe(repo, 'tip')
143 142
144 143 tmp_d = {}
145 144 tmp_d['name'] = r.repo_name
146 145 tmp_d['name_sort'] = tmp_d['name'].lower()
147 146 tmp_d['description'] = repo.dbrepo.description
148 147 tmp_d['description_sort'] = tmp_d['description']
149 148 tmp_d['last_change'] = last_change
150 149 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
151 150 tmp_d['tip'] = tip.raw_id
152 151 tmp_d['tip_sort'] = tip.revision
153 152 tmp_d['rev'] = tip.revision
154 153 tmp_d['contact'] = repo.dbrepo.user.full_contact
155 154 tmp_d['contact_sort'] = tmp_d['contact']
156 155 tmp_d['owner_sort'] = tmp_d['contact']
157 156 tmp_d['repo_archives'] = list(repo._get_archives())
158 157 tmp_d['last_msg'] = tip.message
159 158 tmp_d['repo'] = repo
160 159 yield tmp_d
161 160
162 161 def get_repo(self, repo_name):
163 162 return self.get(repo_name)
164 163
165 164 def get(self, repo_name, invalidation_list=None):
166 165 """Get's repository from given name, creates BackendInstance and
167 166 propagates it's data from database with all additional information
168 167
169 168 :param repo_name:
170 169 :param invalidation_list: if a invalidation list is given the get
171 170 method should not manually check if this repository needs
172 171 invalidation and just invalidate the repositories in list
173 172
174 173 """
175 174 if not HasRepoPermissionAny('repository.read', 'repository.write',
176 175 'repository.admin')(repo_name, 'get repo check'):
177 176 return
178 177
179 178 #======================================================================
180 179 # CACHE FUNCTION
181 180 #======================================================================
182 181 @cache_region('long_term')
183 182 def _get_repo(repo_name):
184 183
185 184 repo_path = os.path.join(self.repos_path, repo_name)
186 185
187 186 try:
188 187 alias = get_scm(repo_path)[0]
189 188
190 189 log.debug('Creating instance of %s repository', alias)
191 190 backend = get_backend(alias)
192 191 except VCSError:
193 192 log.error(traceback.format_exc())
194 193 return
195 194
196 195 if alias == 'hg':
197 196 from pylons import app_globals as g
198 197 repo = backend(repo_path, create=False, baseui=g.baseui)
199 198 #skip hidden web repository
200 199 if repo._get_hidden():
201 200 return
202 201 else:
203 202 repo = backend(repo_path, create=False)
204 203
205 204 dbrepo = self.sa.query(Repository)\
206 205 .options(joinedload(Repository.fork))\
207 206 .options(joinedload(Repository.user))\
208 207 .filter(Repository.repo_name == repo_name)\
209 208 .scalar()
210 209
211 210 make_transient(dbrepo)
212 211 if dbrepo.user:
213 212 make_transient(dbrepo.user)
214 213 if dbrepo.fork:
215 214 make_transient(dbrepo.fork)
216 215
217 216 repo.dbrepo = dbrepo
218 217 return repo
219 218
220 219 pre_invalidate = True
221 220 if invalidation_list is not None:
222 221 pre_invalidate = repo_name in invalidation_list
223 222
224 223 if pre_invalidate:
225 224 invalidate = self._should_invalidate(repo_name)
226 225
227 226 if invalidate:
228 227 log.info('invalidating cache for repository %s', repo_name)
229 228 region_invalidate(_get_repo, None, repo_name)
230 229 self._mark_invalidated(invalidate)
231 230
232 231 return _get_repo(repo_name)
233 232
234 233
235 234
236 235 def mark_for_invalidation(self, repo_name):
237 236 """Puts cache invalidation task into db for
238 237 further global cache invalidation
239 238
240 239 :param repo_name: this repo that should invalidation take place
241 240 """
242 241
243 242 log.debug('marking %s for invalidation', repo_name)
244 243 cache = self.sa.query(CacheInvalidation)\
245 244 .filter(CacheInvalidation.cache_key == repo_name).scalar()
246 245
247 246 if cache:
248 247 #mark this cache as inactive
249 248 cache.cache_active = False
250 249 else:
251 250 log.debug('cache key not found in invalidation db -> creating one')
252 251 cache = CacheInvalidation(repo_name)
253 252
254 253 try:
255 254 self.sa.add(cache)
256 255 self.sa.commit()
257 256 except (DatabaseError,):
258 257 log.error(traceback.format_exc())
259 258 self.sa.rollback()
260 259
261 260
262 261 def toggle_following_repo(self, follow_repo_id, user_id):
263 262
264 263 f = self.sa.query(UserFollowing)\
265 264 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
266 265 .filter(UserFollowing.user_id == user_id).scalar()
267 266
268 267 if f is not None:
269 268
270 269 try:
271 270 self.sa.delete(f)
272 271 self.sa.commit()
273 272 action_logger(UserTemp(user_id),
274 273 'stopped_following_repo',
275 274 RepoTemp(follow_repo_id))
276 275 return
277 276 except:
278 277 log.error(traceback.format_exc())
279 278 self.sa.rollback()
280 279 raise
281 280
282 281
283 282 try:
284 283 f = UserFollowing()
285 284 f.user_id = user_id
286 285 f.follows_repo_id = follow_repo_id
287 286 self.sa.add(f)
288 287 self.sa.commit()
289 288 action_logger(UserTemp(user_id),
290 289 'started_following_repo',
291 290 RepoTemp(follow_repo_id))
292 291 except:
293 292 log.error(traceback.format_exc())
294 293 self.sa.rollback()
295 294 raise
296 295
297 296 def toggle_following_user(self, follow_user_id , user_id):
298 297 f = self.sa.query(UserFollowing)\
299 298 .filter(UserFollowing.follows_user_id == follow_user_id)\
300 299 .filter(UserFollowing.user_id == user_id).scalar()
301 300
302 301 if f is not None:
303 302 try:
304 303 self.sa.delete(f)
305 304 self.sa.commit()
306 305 return
307 306 except:
308 307 log.error(traceback.format_exc())
309 308 self.sa.rollback()
310 309 raise
311 310
312 311 try:
313 312 f = UserFollowing()
314 313 f.user_id = user_id
315 314 f.follows_user_id = follow_user_id
316 315 self.sa.add(f)
317 316 self.sa.commit()
318 317 except:
319 318 log.error(traceback.format_exc())
320 319 self.sa.rollback()
321 320 raise
322 321
323 322 def is_following_repo(self, repo_name, user_id, cache=False):
324 323 r = self.sa.query(Repository)\
325 324 .filter(Repository.repo_name == repo_name).scalar()
326 325
327 326 f = self.sa.query(UserFollowing)\
328 327 .filter(UserFollowing.follows_repository == r)\
329 328 .filter(UserFollowing.user_id == user_id).scalar()
330 329
331 330 return f is not None
332 331
333 332 def is_following_user(self, username, user_id, cache=False):
334 333 u = UserModel(self.sa).get_by_username(username)
335 334
336 335 f = self.sa.query(UserFollowing)\
337 336 .filter(UserFollowing.follows_user == u)\
338 337 .filter(UserFollowing.user_id == user_id).scalar()
339 338
340 339 return f is not None
341 340
342 341 def get_followers(self, repo_id):
343 342 return self.sa.query(UserFollowing)\
344 343 .filter(UserFollowing.follows_repo_id == repo_id).count()
345 344
346 345 def get_forks(self, repo_id):
347 346 return self.sa.query(Repository)\
348 347 .filter(Repository.fork_id == repo_id).count()
349 348
350 349
351 350 def get_unread_journal(self):
352 351 return self.sa.query(UserLog).count()
353 352
354 353
355 354 def _should_invalidate(self, repo_name):
356 355 """Looks up database for invalidation signals for this repo_name
357 356
358 357 :param repo_name:
359 358 """
360 359
361 360 ret = self.sa.query(CacheInvalidation)\
362 361 .options(FromCache('sql_cache_short',
363 362 'get_invalidation_%s' % repo_name))\
364 363 .filter(CacheInvalidation.cache_key == repo_name)\
365 364 .filter(CacheInvalidation.cache_active == False)\
366 365 .scalar()
367 366
368 367 return ret
369 368
370 369 def _mark_invalidated(self, cache_key):
371 370 """ Marks all occurences of cache to invaldation as already invalidated
372 371
373 372 :param cache_key:
374 373 """
375 374
376 375 if cache_key:
377 376 log.debug('marking %s as already invalidated', cache_key)
378 377 try:
379 378 cache_key.cache_active = True
380 379 self.sa.add(cache_key)
381 380 self.sa.commit()
382 381 except (DatabaseError,):
383 382 log.error(traceback.format_exc())
384 383 self.sa.rollback()
385 384
General Comments 0
You need to be logged in to leave comments. Login now