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