##// END OF EJS Templates
added action loggers to following repositories,...
marcink -
r735:dbec976d beta
parent child Browse files
Show More
@@ -1,290 +1,290 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # repos controller for pylons
3 # repos controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on April 7, 2010
21 Created on April 7, 2010
22 admin controller for pylons
22 admin controller for pylons
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from formencode import htmlfill
25 from formencode import htmlfill
26 from operator import itemgetter
26 from operator import itemgetter
27 from paste.httpexceptions import HTTPInternalServerError
27 from paste.httpexceptions import HTTPInternalServerError
28 from pylons import request, response, session, tmpl_context as c, url
28 from pylons import request, response, session, tmpl_context as c, url
29 from pylons.controllers.util import abort, redirect
29 from pylons.controllers.util import abort, redirect
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
32 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
32 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
33 HasPermissionAnyDecorator
33 HasPermissionAnyDecorator
34 from rhodecode.lib.base import BaseController, render
34 from rhodecode.lib.base import BaseController, render
35 from rhodecode.lib.utils import invalidate_cache, action_logger
35 from rhodecode.lib.utils import invalidate_cache, action_logger
36 from rhodecode.model.db import User
36 from rhodecode.model.db import User
37 from rhodecode.model.forms import RepoForm
37 from rhodecode.model.forms import RepoForm
38 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.scm import ScmModel
39 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.repo import RepoModel
40 import formencode
40 import formencode
41 import logging
41 import logging
42 import traceback
42 import traceback
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46 class ReposController(BaseController):
46 class ReposController(BaseController):
47 """REST Controller styled on the Atom Publishing Protocol"""
47 """REST Controller styled on the Atom Publishing Protocol"""
48 # To properly map this controller, ensure your config/routing.py
48 # To properly map this controller, ensure your config/routing.py
49 # file has a resource setup:
49 # file has a resource setup:
50 # map.resource('repo', 'repos')
50 # map.resource('repo', 'repos')
51
51
52 @LoginRequired()
52 @LoginRequired()
53 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
53 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
54 def __before__(self):
54 def __before__(self):
55 c.admin_user = session.get('admin_user')
55 c.admin_user = session.get('admin_user')
56 c.admin_username = session.get('admin_username')
56 c.admin_username = session.get('admin_username')
57 super(ReposController, self).__before__()
57 super(ReposController, self).__before__()
58
58
59 @HasPermissionAllDecorator('hg.admin')
59 @HasPermissionAllDecorator('hg.admin')
60 def index(self, format='html'):
60 def index(self, format='html'):
61 """GET /repos: All items in the collection"""
61 """GET /repos: All items in the collection"""
62 # url('repos')
62 # url('repos')
63 cached_repo_list = ScmModel().get_repos()
63 cached_repo_list = ScmModel().get_repos()
64 c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort'))
64 c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort'))
65 return render('admin/repos/repos.html')
65 return render('admin/repos/repos.html')
66
66
67 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
67 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
68 def create(self):
68 def create(self):
69 """POST /repos: Create a new item"""
69 """POST /repos: Create a new item"""
70 # url('repos')
70 # url('repos')
71 repo_model = RepoModel()
71 repo_model = RepoModel()
72 _form = RepoForm()()
72 _form = RepoForm()()
73 form_result = {}
73 form_result = {}
74 try:
74 try:
75 form_result = _form.to_python(dict(request.POST))
75 form_result = _form.to_python(dict(request.POST))
76 repo_model.create(form_result, c.rhodecode_user)
76 repo_model.create(form_result, c.rhodecode_user)
77 h.flash(_('created repository %s') % form_result['repo_name'],
77 h.flash(_('created repository %s') % form_result['repo_name'],
78 category='success')
78 category='success')
79
79
80 if request.POST.get('user_created'):
80 if request.POST.get('user_created'):
81 action_logger(self.rhodecode_user, 'user_created_repo',
81 action_logger(self.rhodecode_user, 'user_created_repo',
82 form_result['repo_name'], '', self.sa)
82 form_result['repo_name'], '', self.sa)
83 else:
83 else:
84 action_logger(self.rhodecode_user, 'admin_created_repo',
84 action_logger(self.rhodecode_user, 'admin_created_repo',
85 form_result['repo_name'], '', self.sa)
85 form_result['repo_name'], '', self.sa)
86
86
87 except formencode.Invalid, errors:
87 except formencode.Invalid, errors:
88 c.new_repo = errors.value['repo_name']
88 c.new_repo = errors.value['repo_name']
89
89
90 if request.POST.get('user_created'):
90 if request.POST.get('user_created'):
91 r = render('admin/repos/repo_add_create_repository.html')
91 r = render('admin/repos/repo_add_create_repository.html')
92 else:
92 else:
93 r = render('admin/repos/repo_add.html')
93 r = render('admin/repos/repo_add.html')
94
94
95 return htmlfill.render(
95 return htmlfill.render(
96 r,
96 r,
97 defaults=errors.value,
97 defaults=errors.value,
98 errors=errors.error_dict or {},
98 errors=errors.error_dict or {},
99 prefix_error=False,
99 prefix_error=False,
100 encoding="UTF-8")
100 encoding="UTF-8")
101
101
102 except Exception:
102 except Exception:
103 log.error(traceback.format_exc())
103 log.error(traceback.format_exc())
104 msg = _('error occured during creation of repository %s') \
104 msg = _('error occured during creation of repository %s') \
105 % form_result.get('repo_name')
105 % form_result.get('repo_name')
106 h.flash(msg, category='error')
106 h.flash(msg, category='error')
107 if request.POST.get('user_created'):
107 if request.POST.get('user_created'):
108 return redirect(url('home'))
108 return redirect(url('home'))
109 return redirect(url('repos'))
109 return redirect(url('repos'))
110
110
111 @HasPermissionAllDecorator('hg.admin')
111 @HasPermissionAllDecorator('hg.admin')
112 def new(self, format='html'):
112 def new(self, format='html'):
113 """GET /repos/new: Form to create a new item"""
113 """GET /repos/new: Form to create a new item"""
114 new_repo = request.GET.get('repo', '')
114 new_repo = request.GET.get('repo', '')
115 c.new_repo = h.repo_name_slug(new_repo)
115 c.new_repo = h.repo_name_slug(new_repo)
116
116
117 return render('admin/repos/repo_add.html')
117 return render('admin/repos/repo_add.html')
118
118
119 @HasPermissionAllDecorator('hg.admin')
119 @HasPermissionAllDecorator('hg.admin')
120 def update(self, repo_name):
120 def update(self, repo_name):
121 """PUT /repos/repo_name: Update an existing item"""
121 """PUT /repos/repo_name: Update an existing item"""
122 # Forms posted to this method should contain a hidden field:
122 # Forms posted to this method should contain a hidden field:
123 # <input type="hidden" name="_method" value="PUT" />
123 # <input type="hidden" name="_method" value="PUT" />
124 # Or using helpers:
124 # Or using helpers:
125 # h.form(url('repo', repo_name=ID),
125 # h.form(url('repo', repo_name=ID),
126 # method='put')
126 # method='put')
127 # url('repo', repo_name=ID)
127 # url('repo', repo_name=ID)
128 repo_model = RepoModel()
128 repo_model = RepoModel()
129 changed_name = repo_name
129 changed_name = repo_name
130 _form = RepoForm(edit=True, old_data={'repo_name':repo_name})()
130 _form = RepoForm(edit=True, old_data={'repo_name':repo_name})()
131
131
132 try:
132 try:
133 form_result = _form.to_python(dict(request.POST))
133 form_result = _form.to_python(dict(request.POST))
134 repo_model.update(repo_name, form_result)
134 repo_model.update(repo_name, form_result)
135 invalidate_cache('get_repo_cached_%s' % repo_name)
135 invalidate_cache('get_repo_cached_%s' % repo_name)
136 h.flash(_('Repository %s updated successfully' % repo_name),
136 h.flash(_('Repository %s updated successfully' % repo_name),
137 category='success')
137 category='success')
138 changed_name = form_result['repo_name']
138 changed_name = form_result['repo_name']
139 action_logger(self.rhodecode_user, 'admin_updated_repo',
139 action_logger(self.rhodecode_user, 'admin_updated_repo',
140 changed_name, '', self.sa)
140 changed_name, '', self.sa)
141
141
142 except formencode.Invalid, errors:
142 except formencode.Invalid, errors:
143 c.repo_info = repo_model.get(repo_name)
143 c.repo_info = repo_model.get_by_repo_name(repo_name)
144 c.users_array = repo_model.get_users_js()
144 c.users_array = repo_model.get_users_js()
145 errors.value.update({'user':c.repo_info.user.username})
145 errors.value.update({'user':c.repo_info.user.username})
146 return htmlfill.render(
146 return htmlfill.render(
147 render('admin/repos/repo_edit.html'),
147 render('admin/repos/repo_edit.html'),
148 defaults=errors.value,
148 defaults=errors.value,
149 errors=errors.error_dict or {},
149 errors=errors.error_dict or {},
150 prefix_error=False,
150 prefix_error=False,
151 encoding="UTF-8")
151 encoding="UTF-8")
152
152
153 except Exception:
153 except Exception:
154 log.error(traceback.format_exc())
154 log.error(traceback.format_exc())
155 h.flash(_('error occurred during update of repository %s') \
155 h.flash(_('error occurred during update of repository %s') \
156 % repo_name, category='error')
156 % repo_name, category='error')
157
157
158 return redirect(url('edit_repo', repo_name=changed_name))
158 return redirect(url('edit_repo', repo_name=changed_name))
159
159
160 @HasPermissionAllDecorator('hg.admin')
160 @HasPermissionAllDecorator('hg.admin')
161 def delete(self, repo_name):
161 def delete(self, repo_name):
162 """DELETE /repos/repo_name: Delete an existing item"""
162 """DELETE /repos/repo_name: Delete an existing item"""
163 # Forms posted to this method should contain a hidden field:
163 # Forms posted to this method should contain a hidden field:
164 # <input type="hidden" name="_method" value="DELETE" />
164 # <input type="hidden" name="_method" value="DELETE" />
165 # Or using helpers:
165 # Or using helpers:
166 # h.form(url('repo', repo_name=ID),
166 # h.form(url('repo', repo_name=ID),
167 # method='delete')
167 # method='delete')
168 # url('repo', repo_name=ID)
168 # url('repo', repo_name=ID)
169
169
170 repo_model = RepoModel()
170 repo_model = RepoModel()
171 repo = repo_model.get(repo_name)
171 repo = repo_model.get_by_repo_name(repo_name)
172 if not repo:
172 if not repo:
173 h.flash(_('%s repository is not mapped to db perhaps'
173 h.flash(_('%s repository is not mapped to db perhaps'
174 ' it was moved or renamed from the filesystem'
174 ' it was moved or renamed from the filesystem'
175 ' please run the application again'
175 ' please run the application again'
176 ' in order to rescan repositories') % repo_name,
176 ' in order to rescan repositories') % repo_name,
177 category='error')
177 category='error')
178
178
179 return redirect(url('repos'))
179 return redirect(url('repos'))
180 try:
180 try:
181 action_logger(self.rhodecode_user, 'admin_deleted_repo',
181 action_logger(self.rhodecode_user, 'admin_deleted_repo',
182 repo_name, '', self.sa)
182 repo_name, '', self.sa)
183 repo_model.delete(repo)
183 repo_model.delete(repo)
184 invalidate_cache('get_repo_cached_%s' % repo_name)
184 invalidate_cache('get_repo_cached_%s' % repo_name)
185 h.flash(_('deleted repository %s') % repo_name, category='success')
185 h.flash(_('deleted repository %s') % repo_name, category='success')
186
186
187 except Exception, e:
187 except Exception, e:
188 log.error(traceback.format_exc())
188 log.error(traceback.format_exc())
189 h.flash(_('An error occured during deletion of %s') % repo_name,
189 h.flash(_('An error occured during deletion of %s') % repo_name,
190 category='error')
190 category='error')
191
191
192 return redirect(url('repos'))
192 return redirect(url('repos'))
193
193
194 @HasPermissionAllDecorator('hg.admin')
194 @HasPermissionAllDecorator('hg.admin')
195 def delete_perm_user(self, repo_name):
195 def delete_perm_user(self, repo_name):
196 """
196 """
197 DELETE an existing repository permission user
197 DELETE an existing repository permission user
198 :param repo_name:
198 :param repo_name:
199 """
199 """
200
200
201 try:
201 try:
202 repo_model = RepoModel()
202 repo_model = RepoModel()
203 repo_model.delete_perm_user(request.POST, repo_name)
203 repo_model.delete_perm_user(request.POST, repo_name)
204 except Exception, e:
204 except Exception, e:
205 h.flash(_('An error occured during deletion of repository user'),
205 h.flash(_('An error occured during deletion of repository user'),
206 category='error')
206 category='error')
207 raise HTTPInternalServerError()
207 raise HTTPInternalServerError()
208
208
209 @HasPermissionAllDecorator('hg.admin')
209 @HasPermissionAllDecorator('hg.admin')
210 def repo_stats(self, repo_name):
210 def repo_stats(self, repo_name):
211 """
211 """
212 DELETE an existing repository statistics
212 DELETE an existing repository statistics
213 :param repo_name:
213 :param repo_name:
214 """
214 """
215
215
216 try:
216 try:
217 repo_model = RepoModel()
217 repo_model = RepoModel()
218 repo_model.delete_stats(repo_name)
218 repo_model.delete_stats(repo_name)
219 except Exception, e:
219 except Exception, e:
220 h.flash(_('An error occured during deletion of repository stats'),
220 h.flash(_('An error occured during deletion of repository stats'),
221 category='error')
221 category='error')
222 return redirect(url('edit_repo', repo_name=repo_name))
222 return redirect(url('edit_repo', repo_name=repo_name))
223
223
224 @HasPermissionAllDecorator('hg.admin')
224 @HasPermissionAllDecorator('hg.admin')
225 def repo_cache(self, repo_name):
225 def repo_cache(self, repo_name):
226 """
226 """
227 INVALIDATE exisitings repository cache
227 INVALIDATE exisitings repository cache
228 :param repo_name:
228 :param repo_name:
229 """
229 """
230
230
231 try:
231 try:
232 ScmModel().mark_for_invalidation(repo_name)
232 ScmModel().mark_for_invalidation(repo_name)
233 except Exception, e:
233 except Exception, e:
234 h.flash(_('An error occurred during cache invalidation'),
234 h.flash(_('An error occurred during cache invalidation'),
235 category='error')
235 category='error')
236 return redirect(url('edit_repo', repo_name=repo_name))
236 return redirect(url('edit_repo', repo_name=repo_name))
237
237
238 @HasPermissionAllDecorator('hg.admin')
238 @HasPermissionAllDecorator('hg.admin')
239 def show(self, repo_name, format='html'):
239 def show(self, repo_name, format='html'):
240 """GET /repos/repo_name: Show a specific item"""
240 """GET /repos/repo_name: Show a specific item"""
241 # url('repo', repo_name=ID)
241 # url('repo', repo_name=ID)
242
242
243 @HasPermissionAllDecorator('hg.admin')
243 @HasPermissionAllDecorator('hg.admin')
244 def edit(self, repo_name, format='html'):
244 def edit(self, repo_name, format='html'):
245 """GET /repos/repo_name/edit: Form to edit an existing item"""
245 """GET /repos/repo_name/edit: Form to edit an existing item"""
246 # url('edit_repo', repo_name=ID)
246 # url('edit_repo', repo_name=ID)
247 repo_model = RepoModel()
247 repo_model = RepoModel()
248 c.repo_info = repo = repo_model.get(repo_name)
248 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
249 if repo.stats:
249 if repo.stats:
250 last_rev = repo.stats.stat_on_revision
250 last_rev = repo.stats.stat_on_revision
251 else:
251 else:
252 last_rev = 0
252 last_rev = 0
253 c.stats_revision = last_rev
253 c.stats_revision = last_rev
254 r = ScmModel().get(repo_name)
254 r = ScmModel().get(repo_name)
255 c.repo_last_rev = r.revisions[-1] if r.revisions else 0
255 c.repo_last_rev = r.revisions[-1] if r.revisions else 0
256
256
257 if last_rev == 0:
257 if last_rev == 0:
258 c.stats_percentage = 0
258 c.stats_percentage = 0
259 else:
259 else:
260 c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100)
260 c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100)
261
261
262
262
263 if not repo:
263 if not repo:
264 h.flash(_('%s repository is not mapped to db perhaps'
264 h.flash(_('%s repository is not mapped to db perhaps'
265 ' it was created or renamed from the filesystem'
265 ' it was created or renamed from the filesystem'
266 ' please run the application again'
266 ' please run the application again'
267 ' in order to rescan repositories') % repo_name,
267 ' in order to rescan repositories') % repo_name,
268 category='error')
268 category='error')
269
269
270 return redirect(url('repos'))
270 return redirect(url('repos'))
271 defaults = c.repo_info.__dict__
271 defaults = c.repo_info.__dict__
272 if c.repo_info.user:
272 if c.repo_info.user:
273 defaults.update({'user':c.repo_info.user.username})
273 defaults.update({'user':c.repo_info.user.username})
274 else:
274 else:
275 replacement_user = self.sa.query(User)\
275 replacement_user = self.sa.query(User)\
276 .filter(User.admin == True).first().username
276 .filter(User.admin == True).first().username
277 defaults.update({'user':replacement_user})
277 defaults.update({'user':replacement_user})
278
278
279 c.users_array = repo_model.get_users_js()
279 c.users_array = repo_model.get_users_js()
280
280
281 for p in c.repo_info.repo_to_perm:
281 for p in c.repo_info.repo_to_perm:
282 defaults.update({'perm_%s' % p.user.username:
282 defaults.update({'perm_%s' % p.user.username:
283 p.permission.permission_name})
283 p.permission.permission_name})
284
284
285 return htmlfill.render(
285 return htmlfill.render(
286 render('admin/repos/repo_edit.html'),
286 render('admin/repos/repo_edit.html'),
287 defaults=defaults,
287 defaults=defaults,
288 encoding="UTF-8",
288 encoding="UTF-8",
289 force_defaults=False
289 force_defaults=False
290 )
290 )
@@ -1,84 +1,84 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # journal controller for pylons
3 # journal controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on November 21, 2010
21 Created on November 21, 2010
22 journal controller for pylons
22 journal controller for pylons
23 @author: marcink
23 @author: marcink
24 """
24 """
25
25
26 from pylons import request, response, session, tmpl_context as c, url
26 from pylons import request, response, session, tmpl_context as c, url
27 from pylons.controllers.util import abort, redirect
27 from pylons.controllers.util import abort, redirect
28 from rhodecode.lib.auth import LoginRequired
28 from rhodecode.lib.auth import LoginRequired
29 from rhodecode.lib.base import BaseController, render
29 from rhodecode.lib.base import BaseController, render
30 from rhodecode.lib.helpers import get_token
30 from rhodecode.lib.helpers import get_token
31 from rhodecode.lib.utils import action_logger
31 from rhodecode.model.db import UserLog, UserFollowing
32 from rhodecode.model.db import UserLog, UserFollowing
32 from rhodecode.model.scm import ScmModel
33 from rhodecode.model.scm import ScmModel
33 import logging
34 import logging
34 from paste.httpexceptions import HTTPInternalServerError, HTTPNotFound
35 from paste.httpexceptions import HTTPInternalServerError, HTTPNotFound
35
36
36 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
37
38
38 class JournalController(BaseController):
39 class JournalController(BaseController):
39
40
40
41
41 @LoginRequired()
42 @LoginRequired()
42 def __before__(self):
43 def __before__(self):
43 super(JournalController, self).__before__()
44 super(JournalController, self).__before__()
44
45
45 def index(self):
46 def index(self):
46 # Return a rendered template
47 # Return a rendered template
47
48
48 c.following = self.sa.query(UserFollowing)\
49 c.following = self.sa.query(UserFollowing)\
49 .filter(UserFollowing.user_id == c.rhodecode_user.user_id).all()
50 .filter(UserFollowing.user_id == c.rhodecode_user.user_id).all()
50
51
51
52
52 c.journal = self.sa.query(UserLog)\
53 c.journal = self.sa.query(UserLog)\
53 .order_by(UserLog.action_date.desc())\
54 .order_by(UserLog.action_date.desc())\
54 .all()
55 .all()
55 return render('/journal.html')
56 return render('/journal.html')
56
57
57
58
58 def toggle_following(self):
59 def toggle_following(self):
59 print c.rhodecode_user
60
60
61 if request.POST.get('auth_token') == get_token():
61 if request.POST.get('auth_token') == get_token():
62 scm_model = ScmModel()
62 scm_model = ScmModel()
63
63
64 user_id = request.POST.get('follows_user_id')
64 user_id = request.POST.get('follows_user_id')
65 if user_id:
65 if user_id:
66 try:
66 try:
67 scm_model.toggle_following_user(user_id,
67 scm_model.toggle_following_user(user_id,
68 c.rhodecode_user.user_id)
68 c.rhodecode_user.user_id)
69 return 'ok'
69 return 'ok'
70 except:
70 except:
71 raise HTTPInternalServerError()
71 raise HTTPInternalServerError()
72
72
73 repo_id = request.POST.get('follows_repo_id')
73 repo_id = request.POST.get('follows_repo_id')
74 if repo_id:
74 if repo_id:
75 try:
75 try:
76 scm_model.toggle_following_repo(repo_id,
76 scm_model.toggle_following_repo(repo_id,
77 c.rhodecode_user.user_id)
77 c.rhodecode_user.user_id)
78 return 'ok'
78 return 'ok'
79 except:
79 except:
80 raise HTTPInternalServerError()
80 raise HTTPInternalServerError()
81
81
82
82
83
83
84 raise HTTPInternalServerError()
84 raise HTTPInternalServerError()
@@ -1,178 +1,178 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # settings controller for pylons
3 # settings controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on June 30, 2010
21 Created on June 30, 2010
22 settings controller for pylons
22 settings controller for pylons
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from formencode import htmlfill
25 from formencode import htmlfill
26 from pylons import tmpl_context as c, request, url
26 from pylons import tmpl_context as c, request, url
27 from pylons.controllers.util import redirect
27 from pylons.controllers.util import redirect
28 from pylons.i18n.translation import _
28 from pylons.i18n.translation import _
29 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator
29 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator
30 from rhodecode.lib.base import BaseController, render
30 from rhodecode.lib.base import BaseController, render
31 from rhodecode.lib.utils import invalidate_cache, action_logger
31 from rhodecode.lib.utils import invalidate_cache, action_logger
32 from rhodecode.model.forms import RepoSettingsForm, RepoForkForm
32 from rhodecode.model.forms import RepoSettingsForm, RepoForkForm
33 from rhodecode.model.repo import RepoModel
33 from rhodecode.model.repo import RepoModel
34 import formencode
34 import formencode
35 import logging
35 import logging
36 import rhodecode.lib.helpers as h
36 import rhodecode.lib.helpers as h
37 import traceback
37 import traceback
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41 class SettingsController(BaseController):
41 class SettingsController(BaseController):
42
42
43 @LoginRequired()
43 @LoginRequired()
44 @HasRepoPermissionAllDecorator('repository.admin')
44 @HasRepoPermissionAllDecorator('repository.admin')
45 def __before__(self):
45 def __before__(self):
46 super(SettingsController, self).__before__()
46 super(SettingsController, self).__before__()
47
47
48 def index(self, repo_name):
48 def index(self, repo_name):
49 repo_model = RepoModel()
49 repo_model = RepoModel()
50 c.repo_info = repo = repo_model.get(repo_name)
50 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
51 if not repo:
51 if not repo:
52 h.flash(_('%s repository is not mapped to db perhaps'
52 h.flash(_('%s repository is not mapped to db perhaps'
53 ' it was created or renamed from the filesystem'
53 ' it was created or renamed from the filesystem'
54 ' please run the application again'
54 ' please run the application again'
55 ' in order to rescan repositories') % repo_name,
55 ' in order to rescan repositories') % repo_name,
56 category='error')
56 category='error')
57
57
58 return redirect(url('home'))
58 return redirect(url('home'))
59 defaults = c.repo_info.__dict__
59 defaults = c.repo_info.__dict__
60 defaults.update({'user':c.repo_info.user.username})
60 defaults.update({'user':c.repo_info.user.username})
61 c.users_array = repo_model.get_users_js()
61 c.users_array = repo_model.get_users_js()
62
62
63 for p in c.repo_info.repo_to_perm:
63 for p in c.repo_info.repo_to_perm:
64 defaults.update({'perm_%s' % p.user.username:
64 defaults.update({'perm_%s' % p.user.username:
65 p.permission.permission_name})
65 p.permission.permission_name})
66
66
67 return htmlfill.render(
67 return htmlfill.render(
68 render('settings/repo_settings.html'),
68 render('settings/repo_settings.html'),
69 defaults=defaults,
69 defaults=defaults,
70 encoding="UTF-8",
70 encoding="UTF-8",
71 force_defaults=False
71 force_defaults=False
72 )
72 )
73
73
74 def update(self, repo_name):
74 def update(self, repo_name):
75 repo_model = RepoModel()
75 repo_model = RepoModel()
76 changed_name = repo_name
76 changed_name = repo_name
77 _form = RepoSettingsForm(edit=True, old_data={'repo_name':repo_name})()
77 _form = RepoSettingsForm(edit=True, old_data={'repo_name':repo_name})()
78 try:
78 try:
79 form_result = _form.to_python(dict(request.POST))
79 form_result = _form.to_python(dict(request.POST))
80 repo_model.update(repo_name, form_result)
80 repo_model.update(repo_name, form_result)
81 invalidate_cache('get_repo_cached_%s' % repo_name)
81 invalidate_cache('get_repo_cached_%s' % repo_name)
82 h.flash(_('Repository %s updated successfully' % repo_name),
82 h.flash(_('Repository %s updated successfully' % repo_name),
83 category='success')
83 category='success')
84 changed_name = form_result['repo_name']
84 changed_name = form_result['repo_name']
85 action_logger(self.rhodecode_user, 'user_updated_repo',
85 action_logger(self.rhodecode_user, 'user_updated_repo',
86 changed_name, '', self.sa)
86 changed_name, '', self.sa)
87 except formencode.Invalid, errors:
87 except formencode.Invalid, errors:
88 c.repo_info = repo_model.get(repo_name)
88 c.repo_info = repo_model.get_by_repo_name(repo_name)
89 c.users_array = repo_model.get_users_js()
89 c.users_array = repo_model.get_users_js()
90 errors.value.update({'user':c.repo_info.user.username})
90 errors.value.update({'user':c.repo_info.user.username})
91 return htmlfill.render(
91 return htmlfill.render(
92 render('settings/repo_settings.html'),
92 render('settings/repo_settings.html'),
93 defaults=errors.value,
93 defaults=errors.value,
94 errors=errors.error_dict or {},
94 errors=errors.error_dict or {},
95 prefix_error=False,
95 prefix_error=False,
96 encoding="UTF-8")
96 encoding="UTF-8")
97 except Exception:
97 except Exception:
98 log.error(traceback.format_exc())
98 log.error(traceback.format_exc())
99 h.flash(_('error occurred during update of repository %s') \
99 h.flash(_('error occurred during update of repository %s') \
100 % repo_name, category='error')
100 % repo_name, category='error')
101
101
102 return redirect(url('repo_settings_home', repo_name=changed_name))
102 return redirect(url('repo_settings_home', repo_name=changed_name))
103
103
104
104
105
105
106 def delete(self, repo_name):
106 def delete(self, repo_name):
107 """DELETE /repos/repo_name: Delete an existing item"""
107 """DELETE /repos/repo_name: Delete an existing item"""
108 # Forms posted to this method should contain a hidden field:
108 # Forms posted to this method should contain a hidden field:
109 # <input type="hidden" name="_method" value="DELETE" />
109 # <input type="hidden" name="_method" value="DELETE" />
110 # Or using helpers:
110 # Or using helpers:
111 # h.form(url('repo_settings_delete', repo_name=ID),
111 # h.form(url('repo_settings_delete', repo_name=ID),
112 # method='delete')
112 # method='delete')
113 # url('repo_settings_delete', repo_name=ID)
113 # url('repo_settings_delete', repo_name=ID)
114
114
115 repo_model = RepoModel()
115 repo_model = RepoModel()
116 repo = repo_model.get(repo_name)
116 repo = repo_model.get_by_repo_name(repo_name)
117 if not repo:
117 if not repo:
118 h.flash(_('%s repository is not mapped to db perhaps'
118 h.flash(_('%s repository is not mapped to db perhaps'
119 ' it was moved or renamed from the filesystem'
119 ' it was moved or renamed from the filesystem'
120 ' please run the application again'
120 ' please run the application again'
121 ' in order to rescan repositories') % repo_name,
121 ' in order to rescan repositories') % repo_name,
122 category='error')
122 category='error')
123
123
124 return redirect(url('home'))
124 return redirect(url('home'))
125 try:
125 try:
126 action_logger(self.rhodecode_user, 'user_deleted_repo',
126 action_logger(self.rhodecode_user, 'user_deleted_repo',
127 repo_name, '', self.sa)
127 repo_name, '', self.sa)
128 repo_model.delete(repo)
128 repo_model.delete(repo)
129 invalidate_cache('get_repo_cached_%s' % repo_name)
129 invalidate_cache('get_repo_cached_%s' % repo_name)
130 h.flash(_('deleted repository %s') % repo_name, category='success')
130 h.flash(_('deleted repository %s') % repo_name, category='success')
131 except Exception:
131 except Exception:
132 h.flash(_('An error occurred during deletion of %s') % repo_name,
132 h.flash(_('An error occurred during deletion of %s') % repo_name,
133 category='error')
133 category='error')
134
134
135 return redirect(url('home'))
135 return redirect(url('home'))
136
136
137 def fork(self, repo_name):
137 def fork(self, repo_name):
138 repo_model = RepoModel()
138 repo_model = RepoModel()
139 c.repo_info = repo = repo_model.get(repo_name)
139 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
140 if not repo:
140 if not repo:
141 h.flash(_('%s repository is not mapped to db perhaps'
141 h.flash(_('%s repository is not mapped to db perhaps'
142 ' it was created or renamed from the filesystem'
142 ' it was created or renamed from the filesystem'
143 ' please run the application again'
143 ' please run the application again'
144 ' in order to rescan repositories') % repo_name,
144 ' in order to rescan repositories') % repo_name,
145 category='error')
145 category='error')
146
146
147 return redirect(url('home'))
147 return redirect(url('home'))
148
148
149 return render('settings/repo_fork.html')
149 return render('settings/repo_fork.html')
150
150
151
151
152
152
153 def fork_create(self, repo_name):
153 def fork_create(self, repo_name):
154 repo_model = RepoModel()
154 repo_model = RepoModel()
155 c.repo_info = repo_model.get(repo_name)
155 c.repo_info = repo_model.get_by_repo_name(repo_name)
156 _form = RepoForkForm(old_data={'repo_type':c.repo_info.repo_type})()
156 _form = RepoForkForm(old_data={'repo_type':c.repo_info.repo_type})()
157 form_result = {}
157 form_result = {}
158 try:
158 try:
159 form_result = _form.to_python(dict(request.POST))
159 form_result = _form.to_python(dict(request.POST))
160 form_result.update({'repo_name':repo_name})
160 form_result.update({'repo_name':repo_name})
161 repo_model.create_fork(form_result, c.rhodecode_user)
161 repo_model.create_fork(form_result, c.rhodecode_user)
162 h.flash(_('forked %s repository as %s') \
162 h.flash(_('forked %s repository as %s') \
163 % (repo_name, form_result['fork_name']),
163 % (repo_name, form_result['fork_name']),
164 category='success')
164 category='success')
165 action_logger(self.rhodecode_user,
165 action_logger(self.rhodecode_user,
166 'user_forked_repo:%s' % form_result['fork_name'],
166 'user_forked_repo:%s' % form_result['fork_name'],
167 repo_name, '', self.sa)
167 repo_name, '', self.sa)
168 except formencode.Invalid, errors:
168 except formencode.Invalid, errors:
169 c.new_repo = errors.value['fork_name']
169 c.new_repo = errors.value['fork_name']
170 r = render('settings/repo_fork.html')
170 r = render('settings/repo_fork.html')
171
171
172 return htmlfill.render(
172 return htmlfill.render(
173 r,
173 r,
174 defaults=errors.value,
174 defaults=errors.value,
175 errors=errors.error_dict or {},
175 errors=errors.error_dict or {},
176 prefix_error=False,
176 prefix_error=False,
177 encoding="UTF-8")
177 encoding="UTF-8")
178 return redirect(url('home'))
178 return redirect(url('home'))
@@ -1,508 +1,511 b''
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 from pygments.formatters import HtmlFormatter
8 from pygments.formatters import HtmlFormatter
9 from pygments import highlight as code_highlight
9 from pygments import highlight as code_highlight
10 from pylons import url, app_globals as g
10 from pylons import url, app_globals as g
11 from pylons.i18n.translation import _, ungettext
11 from pylons.i18n.translation import _, ungettext
12 from vcs.utils.annotate import annotate_highlight
12 from vcs.utils.annotate import annotate_highlight
13 from webhelpers.html import literal, HTML, escape
13 from webhelpers.html import literal, HTML, escape
14 from webhelpers.html.tools import *
14 from webhelpers.html.tools import *
15 from webhelpers.html.builder import make_tag
15 from webhelpers.html.builder import make_tag
16 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
16 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
17 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
17 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
18 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
18 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
19 password, textarea, title, ul, xml_declaration, radio
19 password, textarea, title, ul, xml_declaration, radio
20 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
20 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
21 mail_to, strip_links, strip_tags, tag_re
21 mail_to, strip_links, strip_tags, tag_re
22 from webhelpers.number import format_byte_size, format_bit_size
22 from webhelpers.number import format_byte_size, format_bit_size
23 from webhelpers.pylonslib import Flash as _Flash
23 from webhelpers.pylonslib import Flash as _Flash
24 from webhelpers.pylonslib.secure_form import secure_form
24 from webhelpers.pylonslib.secure_form import secure_form
25 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
25 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
26 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
26 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
27 replace_whitespace, urlify, truncate, wrap_paragraphs
27 replace_whitespace, urlify, truncate, wrap_paragraphs
28 from webhelpers.date import time_ago_in_words
28 from webhelpers.date import time_ago_in_words
29
29
30 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
30 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
31 convert_boolean_attrs, NotGiven
31 convert_boolean_attrs, NotGiven
32
32
33 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
33 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
34 _set_input_attrs(attrs, type, name, value)
34 _set_input_attrs(attrs, type, name, value)
35 _set_id_attr(attrs, id, name)
35 _set_id_attr(attrs, id, name)
36 convert_boolean_attrs(attrs, ["disabled"])
36 convert_boolean_attrs(attrs, ["disabled"])
37 return HTML.input(**attrs)
37 return HTML.input(**attrs)
38
38
39 reset = _reset
39 reset = _reset
40
40
41
41
42 def get_token():
42 def get_token():
43 """Return the current authentication token, creating one if one doesn't
43 """Return the current authentication token, creating one if one doesn't
44 already exist.
44 already exist.
45 """
45 """
46 token_key = "_authentication_token"
46 token_key = "_authentication_token"
47 from pylons import session
47 from pylons import session
48 if not token_key in session:
48 if not token_key in session:
49 try:
49 try:
50 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
50 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
51 except AttributeError: # Python < 2.4
51 except AttributeError: # Python < 2.4
52 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
52 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
53 session[token_key] = token
53 session[token_key] = token
54 if hasattr(session, 'save'):
54 if hasattr(session, 'save'):
55 session.save()
55 session.save()
56 return session[token_key]
56 return session[token_key]
57
57
58
58
59 #Custom helpers here :)
59 #Custom helpers here :)
60 class _Link(object):
60 class _Link(object):
61 '''
61 '''
62 Make a url based on label and url with help of url_for
62 Make a url based on label and url with help of url_for
63 :param label:name of link if not defined url is used
63 :param label:name of link if not defined url is used
64 :param url: the url for link
64 :param url: the url for link
65 '''
65 '''
66
66
67 def __call__(self, label='', *url_, **urlargs):
67 def __call__(self, label='', *url_, **urlargs):
68 if label is None or '':
68 if label is None or '':
69 label = url
69 label = url
70 link_fn = link_to(label, url(*url_, **urlargs))
70 link_fn = link_to(label, url(*url_, **urlargs))
71 return link_fn
71 return link_fn
72
72
73 link = _Link()
73 link = _Link()
74
74
75 class _GetError(object):
75 class _GetError(object):
76
76
77 def __call__(self, field_name, form_errors):
77 def __call__(self, field_name, form_errors):
78 tmpl = """<span class="error_msg">%s</span>"""
78 tmpl = """<span class="error_msg">%s</span>"""
79 if form_errors and form_errors.has_key(field_name):
79 if form_errors and form_errors.has_key(field_name):
80 return literal(tmpl % form_errors.get(field_name))
80 return literal(tmpl % form_errors.get(field_name))
81
81
82 get_error = _GetError()
82 get_error = _GetError()
83
83
84 def recursive_replace(str, replace=' '):
84 def recursive_replace(str, replace=' '):
85 """
85 """
86 Recursive replace of given sign to just one instance
86 Recursive replace of given sign to just one instance
87 :param str: given string
87 :param str: given string
88 :param replace:char to find and replace multiple instances
88 :param replace:char to find and replace multiple instances
89
89
90 Examples::
90 Examples::
91 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
91 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
92 'Mighty-Mighty-Bo-sstones'
92 'Mighty-Mighty-Bo-sstones'
93 """
93 """
94
94
95 if str.find(replace * 2) == -1:
95 if str.find(replace * 2) == -1:
96 return str
96 return str
97 else:
97 else:
98 str = str.replace(replace * 2, replace)
98 str = str.replace(replace * 2, replace)
99 return recursive_replace(str, replace)
99 return recursive_replace(str, replace)
100
100
101 class _ToolTip(object):
101 class _ToolTip(object):
102
102
103 def __call__(self, tooltip_title, trim_at=50):
103 def __call__(self, tooltip_title, trim_at=50):
104 """
104 """
105 Special function just to wrap our text into nice formatted autowrapped
105 Special function just to wrap our text into nice formatted autowrapped
106 text
106 text
107 :param tooltip_title:
107 :param tooltip_title:
108 """
108 """
109
109
110 return wrap_paragraphs(escape(tooltip_title), trim_at)\
110 return wrap_paragraphs(escape(tooltip_title), trim_at)\
111 .replace('\n', '<br/>')
111 .replace('\n', '<br/>')
112
112
113 def activate(self):
113 def activate(self):
114 """
114 """
115 Adds tooltip mechanism to the given Html all tooltips have to have
115 Adds tooltip mechanism to the given Html all tooltips have to have
116 set class tooltip and set attribute tooltip_title.
116 set class tooltip and set attribute tooltip_title.
117 Then a tooltip will be generated based on that
117 Then a tooltip will be generated based on that
118 All with yui js tooltip
118 All with yui js tooltip
119 """
119 """
120
120
121 js = '''
121 js = '''
122 YAHOO.util.Event.onDOMReady(function(){
122 YAHOO.util.Event.onDOMReady(function(){
123 function toolTipsId(){
123 function toolTipsId(){
124 var ids = [];
124 var ids = [];
125 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
125 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
126
126
127 for (var i = 0; i < tts.length; i++) {
127 for (var i = 0; i < tts.length; i++) {
128 //if element doesn't not have and id autgenerate one for tooltip
128 //if element doesn't not have and id autgenerate one for tooltip
129
129
130 if (!tts[i].id){
130 if (!tts[i].id){
131 tts[i].id='tt'+i*100;
131 tts[i].id='tt'+i*100;
132 }
132 }
133 ids.push(tts[i].id);
133 ids.push(tts[i].id);
134 }
134 }
135 return ids
135 return ids
136 };
136 };
137 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
137 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
138 context: toolTipsId(),
138 context: toolTipsId(),
139 monitorresize:false,
139 monitorresize:false,
140 xyoffset :[0,0],
140 xyoffset :[0,0],
141 autodismissdelay:300000,
141 autodismissdelay:300000,
142 hidedelay:5,
142 hidedelay:5,
143 showdelay:20,
143 showdelay:20,
144 });
144 });
145
145
146 //Mouse Over event disabled for new repositories since they don't
146 //Mouse Over event disabled for new repositories since they don't
147 //have last commit message
147 //have last commit message
148 myToolTips.contextMouseOverEvent.subscribe(
148 myToolTips.contextMouseOverEvent.subscribe(
149 function(type, args) {
149 function(type, args) {
150 var context = args[0];
150 var context = args[0];
151 var txt = context.getAttribute('tooltip_title');
151 var txt = context.getAttribute('tooltip_title');
152 if(txt){
152 if(txt){
153 return true;
153 return true;
154 }
154 }
155 else{
155 else{
156 return false;
156 return false;
157 }
157 }
158 });
158 });
159
159
160
160
161 // Set the text for the tooltip just before we display it. Lazy method
161 // Set the text for the tooltip just before we display it. Lazy method
162 myToolTips.contextTriggerEvent.subscribe(
162 myToolTips.contextTriggerEvent.subscribe(
163 function(type, args) {
163 function(type, args) {
164
164
165
165
166 var context = args[0];
166 var context = args[0];
167
167
168 var txt = context.getAttribute('tooltip_title');
168 var txt = context.getAttribute('tooltip_title');
169 this.cfg.setProperty("text", txt);
169 this.cfg.setProperty("text", txt);
170
170
171
171
172 // positioning of tooltip
172 // positioning of tooltip
173 var tt_w = this.element.clientWidth;
173 var tt_w = this.element.clientWidth;
174 var tt_h = this.element.clientHeight;
174 var tt_h = this.element.clientHeight;
175
175
176 var context_w = context.offsetWidth;
176 var context_w = context.offsetWidth;
177 var context_h = context.offsetHeight;
177 var context_h = context.offsetHeight;
178
178
179 var pos_x = YAHOO.util.Dom.getX(context);
179 var pos_x = YAHOO.util.Dom.getX(context);
180 var pos_y = YAHOO.util.Dom.getY(context);
180 var pos_y = YAHOO.util.Dom.getY(context);
181
181
182 var display_strategy = 'top';
182 var display_strategy = 'top';
183 var xy_pos = [0,0];
183 var xy_pos = [0,0];
184 switch (display_strategy){
184 switch (display_strategy){
185
185
186 case 'top':
186 case 'top':
187 var cur_x = (pos_x+context_w/2)-(tt_w/2);
187 var cur_x = (pos_x+context_w/2)-(tt_w/2);
188 var cur_y = pos_y-tt_h-4;
188 var cur_y = pos_y-tt_h-4;
189 xy_pos = [cur_x,cur_y];
189 xy_pos = [cur_x,cur_y];
190 break;
190 break;
191 case 'bottom':
191 case 'bottom':
192 var cur_x = (pos_x+context_w/2)-(tt_w/2);
192 var cur_x = (pos_x+context_w/2)-(tt_w/2);
193 var cur_y = pos_y+context_h+4;
193 var cur_y = pos_y+context_h+4;
194 xy_pos = [cur_x,cur_y];
194 xy_pos = [cur_x,cur_y];
195 break;
195 break;
196 case 'left':
196 case 'left':
197 var cur_x = (pos_x-tt_w-4);
197 var cur_x = (pos_x-tt_w-4);
198 var cur_y = pos_y-((tt_h/2)-context_h/2);
198 var cur_y = pos_y-((tt_h/2)-context_h/2);
199 xy_pos = [cur_x,cur_y];
199 xy_pos = [cur_x,cur_y];
200 break;
200 break;
201 case 'right':
201 case 'right':
202 var cur_x = (pos_x+context_w+4);
202 var cur_x = (pos_x+context_w+4);
203 var cur_y = pos_y-((tt_h/2)-context_h/2);
203 var cur_y = pos_y-((tt_h/2)-context_h/2);
204 xy_pos = [cur_x,cur_y];
204 xy_pos = [cur_x,cur_y];
205 break;
205 break;
206 default:
206 default:
207 var cur_x = (pos_x+context_w/2)-(tt_w/2);
207 var cur_x = (pos_x+context_w/2)-(tt_w/2);
208 var cur_y = pos_y-tt_h-4;
208 var cur_y = pos_y-tt_h-4;
209 xy_pos = [cur_x,cur_y];
209 xy_pos = [cur_x,cur_y];
210 break;
210 break;
211
211
212 }
212 }
213
213
214 this.cfg.setProperty("xy",xy_pos);
214 this.cfg.setProperty("xy",xy_pos);
215
215
216 });
216 });
217
217
218 //Mouse out
218 //Mouse out
219 myToolTips.contextMouseOutEvent.subscribe(
219 myToolTips.contextMouseOutEvent.subscribe(
220 function(type, args) {
220 function(type, args) {
221 var context = args[0];
221 var context = args[0];
222
222
223 });
223 });
224 });
224 });
225 '''
225 '''
226 return literal(js)
226 return literal(js)
227
227
228 tooltip = _ToolTip()
228 tooltip = _ToolTip()
229
229
230 class _FilesBreadCrumbs(object):
230 class _FilesBreadCrumbs(object):
231
231
232 def __call__(self, repo_name, rev, paths):
232 def __call__(self, repo_name, rev, paths):
233 url_l = [link_to(repo_name, url('files_home',
233 url_l = [link_to(repo_name, url('files_home',
234 repo_name=repo_name,
234 repo_name=repo_name,
235 revision=rev, f_path=''))]
235 revision=rev, f_path=''))]
236 paths_l = paths.split('/')
236 paths_l = paths.split('/')
237
237
238 for cnt, p in enumerate(paths_l, 1):
238 for cnt, p in enumerate(paths_l, 1):
239 if p != '':
239 if p != '':
240 url_l.append(link_to(p, url('files_home',
240 url_l.append(link_to(p, url('files_home',
241 repo_name=repo_name,
241 repo_name=repo_name,
242 revision=rev,
242 revision=rev,
243 f_path='/'.join(paths_l[:cnt]))))
243 f_path='/'.join(paths_l[:cnt]))))
244
244
245 return literal('/'.join(url_l))
245 return literal('/'.join(url_l))
246
246
247 files_breadcrumbs = _FilesBreadCrumbs()
247 files_breadcrumbs = _FilesBreadCrumbs()
248 class CodeHtmlFormatter(HtmlFormatter):
248 class CodeHtmlFormatter(HtmlFormatter):
249
249
250 def wrap(self, source, outfile):
250 def wrap(self, source, outfile):
251 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
251 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
252
252
253 def _wrap_code(self, source):
253 def _wrap_code(self, source):
254 for cnt, it in enumerate(source, 1):
254 for cnt, it in enumerate(source, 1):
255 i, t = it
255 i, t = it
256 t = '<div id="#S-%s">%s</div>' % (cnt, t)
256 t = '<div id="#S-%s">%s</div>' % (cnt, t)
257 yield i, t
257 yield i, t
258 def pygmentize(filenode, **kwargs):
258 def pygmentize(filenode, **kwargs):
259 """
259 """
260 pygmentize function using pygments
260 pygmentize function using pygments
261 :param filenode:
261 :param filenode:
262 """
262 """
263 return literal(code_highlight(filenode.content,
263 return literal(code_highlight(filenode.content,
264 filenode.lexer, CodeHtmlFormatter(**kwargs)))
264 filenode.lexer, CodeHtmlFormatter(**kwargs)))
265
265
266 def pygmentize_annotation(filenode, **kwargs):
266 def pygmentize_annotation(filenode, **kwargs):
267 """
267 """
268 pygmentize function for annotation
268 pygmentize function for annotation
269 :param filenode:
269 :param filenode:
270 """
270 """
271
271
272 color_dict = {}
272 color_dict = {}
273 def gen_color():
273 def gen_color():
274 """generator for getting 10k of evenly distibuted colors using hsv color
274 """generator for getting 10k of evenly distibuted colors using hsv color
275 and golden ratio.
275 and golden ratio.
276 """
276 """
277 import colorsys
277 import colorsys
278 n = 10000
278 n = 10000
279 golden_ratio = 0.618033988749895
279 golden_ratio = 0.618033988749895
280 h = 0.22717784590367374
280 h = 0.22717784590367374
281 #generate 10k nice web friendly colors in the same order
281 #generate 10k nice web friendly colors in the same order
282 for c in xrange(n):
282 for c in xrange(n):
283 h += golden_ratio
283 h += golden_ratio
284 h %= 1
284 h %= 1
285 HSV_tuple = [h, 0.95, 0.95]
285 HSV_tuple = [h, 0.95, 0.95]
286 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
286 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
287 yield map(lambda x:str(int(x * 256)), RGB_tuple)
287 yield map(lambda x:str(int(x * 256)), RGB_tuple)
288
288
289 cgenerator = gen_color()
289 cgenerator = gen_color()
290
290
291 def get_color_string(cs):
291 def get_color_string(cs):
292 if color_dict.has_key(cs):
292 if color_dict.has_key(cs):
293 col = color_dict[cs]
293 col = color_dict[cs]
294 else:
294 else:
295 col = color_dict[cs] = cgenerator.next()
295 col = color_dict[cs] = cgenerator.next()
296 return "color: rgb(%s)! important;" % (', '.join(col))
296 return "color: rgb(%s)! important;" % (', '.join(col))
297
297
298 def url_func(changeset):
298 def url_func(changeset):
299 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
299 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
300 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
300 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
301
301
302 tooltip_html = tooltip_html % (changeset.author,
302 tooltip_html = tooltip_html % (changeset.author,
303 changeset.date,
303 changeset.date,
304 tooltip(changeset.message))
304 tooltip(changeset.message))
305 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
305 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
306 short_id(changeset.raw_id))
306 short_id(changeset.raw_id))
307 uri = link_to(
307 uri = link_to(
308 lnk_format,
308 lnk_format,
309 url('changeset_home', repo_name=changeset.repository.name,
309 url('changeset_home', repo_name=changeset.repository.name,
310 revision=changeset.raw_id),
310 revision=changeset.raw_id),
311 style=get_color_string(changeset.raw_id),
311 style=get_color_string(changeset.raw_id),
312 class_='tooltip',
312 class_='tooltip',
313 tooltip_title=tooltip_html
313 tooltip_title=tooltip_html
314 )
314 )
315
315
316 uri += '\n'
316 uri += '\n'
317 return uri
317 return uri
318 return literal(annotate_highlight(filenode, url_func, **kwargs))
318 return literal(annotate_highlight(filenode, url_func, **kwargs))
319
319
320 def repo_name_slug(value):
320 def repo_name_slug(value):
321 """Return slug of name of repository
321 """Return slug of name of repository
322 This function is called on each creation/modification
322 This function is called on each creation/modification
323 of repository to prevent bad names in repo
323 of repository to prevent bad names in repo
324 """
324 """
325 slug = remove_formatting(value)
325 slug = remove_formatting(value)
326 slug = strip_tags(slug)
326 slug = strip_tags(slug)
327
327
328 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
328 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
329 slug = slug.replace(c, '-')
329 slug = slug.replace(c, '-')
330 slug = recursive_replace(slug, '-')
330 slug = recursive_replace(slug, '-')
331 slug = collapse(slug, '-')
331 slug = collapse(slug, '-')
332 return slug
332 return slug
333
333
334 def get_changeset_safe(repo, rev):
334 def get_changeset_safe(repo, rev):
335 from vcs.backends.base import BaseRepository
335 from vcs.backends.base import BaseRepository
336 from vcs.exceptions import RepositoryError
336 from vcs.exceptions import RepositoryError
337 if not isinstance(repo, BaseRepository):
337 if not isinstance(repo, BaseRepository):
338 raise Exception('You must pass an Repository '
338 raise Exception('You must pass an Repository '
339 'object as first argument got %s', type(repo))
339 'object as first argument got %s', type(repo))
340
340
341 try:
341 try:
342 cs = repo.get_changeset(rev)
342 cs = repo.get_changeset(rev)
343 except RepositoryError:
343 except RepositoryError:
344 from rhodecode.lib.utils import EmptyChangeset
344 from rhodecode.lib.utils import EmptyChangeset
345 cs = EmptyChangeset()
345 cs = EmptyChangeset()
346 return cs
346 return cs
347
347
348
348
349 flash = _Flash()
349 flash = _Flash()
350
350
351
351
352 #==============================================================================
352 #==============================================================================
353 # MERCURIAL FILTERS available via h.
353 # MERCURIAL FILTERS available via h.
354 #==============================================================================
354 #==============================================================================
355 from mercurial import util
355 from mercurial import util
356 from mercurial.templatefilters import person as _person
356 from mercurial.templatefilters import person as _person
357
357
358
358
359
359
360 def _age(curdate):
360 def _age(curdate):
361 """turns a datetime into an age string."""
361 """turns a datetime into an age string."""
362
362
363 if not curdate:
363 if not curdate:
364 return ''
364 return ''
365
365
366 from datetime import timedelta, datetime
366 from datetime import timedelta, datetime
367
367
368 agescales = [("year", 3600 * 24 * 365),
368 agescales = [("year", 3600 * 24 * 365),
369 ("month", 3600 * 24 * 30),
369 ("month", 3600 * 24 * 30),
370 ("day", 3600 * 24),
370 ("day", 3600 * 24),
371 ("hour", 3600),
371 ("hour", 3600),
372 ("minute", 60),
372 ("minute", 60),
373 ("second", 1), ]
373 ("second", 1), ]
374
374
375 age = datetime.now() - curdate
375 age = datetime.now() - curdate
376 age_seconds = (age.days * agescales[2][1]) + age.seconds
376 age_seconds = (age.days * agescales[2][1]) + age.seconds
377 pos = 1
377 pos = 1
378 for scale in agescales:
378 for scale in agescales:
379 if scale[1] <= age_seconds:
379 if scale[1] <= age_seconds:
380 if pos == 6:pos = 5
380 if pos == 6:pos = 5
381 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
381 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
382 pos += 1
382 pos += 1
383
383
384 return _('just now')
384 return _('just now')
385
385
386 age = lambda x:_age(x)
386 age = lambda x:_age(x)
387 capitalize = lambda x: x.capitalize()
387 capitalize = lambda x: x.capitalize()
388 email = util.email
388 email = util.email
389 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
389 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
390 person = lambda x: _person(x)
390 person = lambda x: _person(x)
391 short_id = lambda x: x[:12]
391 short_id = lambda x: x[:12]
392
392
393
393
394 def bool2icon(value):
394 def bool2icon(value):
395 """
395 """
396 Returns True/False values represented as small html image of true/false
396 Returns True/False values represented as small html image of true/false
397 icons
397 icons
398 :param value: bool value
398 :param value: bool value
399 """
399 """
400
400
401 if value is True:
401 if value is True:
402 return HTML.tag('img', src="/images/icons/accept.png", alt=_('True'))
402 return HTML.tag('img', src="/images/icons/accept.png", alt=_('True'))
403
403
404 if value is False:
404 if value is False:
405 return HTML.tag('img', src="/images/icons/cancel.png", alt=_('False'))
405 return HTML.tag('img', src="/images/icons/cancel.png", alt=_('False'))
406
406
407 return value
407 return value
408
408
409
409
410 def action_parser(user_log):
410 def action_parser(user_log):
411 """
411 """
412 This helper will map the specified string action into translated
412 This helper will map the specified string action into translated
413 fancy names with icons and links
413 fancy names with icons and links
414
414
415 @param action:
415 @param action:
416 """
416 """
417 action = user_log.action
417 action = user_log.action
418 action_params = None
418 action_params = None
419
419
420 x = action.split(':')
420 x = action.split(':')
421
421
422 if len(x) > 1:
422 if len(x) > 1:
423 action, action_params = x
423 action, action_params = x
424
424
425 def get_cs_links():
425 def get_cs_links():
426 if action == 'push':
426 if action == 'push':
427 revs_limit = 5
427 revs_limit = 5
428 revs = action_params.split(',')
428 revs = action_params.split(',')
429 cs_links = " " + ', '.join ([link(rev,
429 cs_links = " " + ', '.join ([link(rev,
430 url('changeset_home',
430 url('changeset_home',
431 repo_name=user_log.repository.repo_name,
431 repo_name=user_log.repository.repo_name,
432 revision=rev)) for rev in revs[:revs_limit] ])
432 revision=rev)) for rev in revs[:revs_limit] ])
433 if len(revs) > revs_limit:
433 if len(revs) > revs_limit:
434 html_tmpl = '<span title="%s"> %s </span>'
434 html_tmpl = '<span title="%s"> %s </span>'
435 cs_links += html_tmpl % (', '.join(r for r in revs[revs_limit:]),
435 cs_links += html_tmpl % (', '.join(r for r in revs[revs_limit:]),
436 _('and %s more revisions') \
436 _('and %s more revisions') \
437 % (len(revs) - revs_limit))
437 % (len(revs) - revs_limit))
438
438
439 return literal(cs_links)
439 return literal(cs_links)
440 return ''
440 return ''
441
441
442 def get_fork_name():
442 def get_fork_name():
443 if action == 'user_forked_repo':
443 if action == 'user_forked_repo':
444 from rhodecode.model.scm import ScmModel
444 from rhodecode.model.scm import ScmModel
445 repo_name = action_params
445 repo_name = action_params
446 repo = ScmModel().get(repo_name)
446 repo = ScmModel().get(repo_name)
447 if repo is None:
447 if repo is None:
448 return repo_name
448 return repo_name
449 return link_to(action_params, url('summary_home',
449 return link_to(action_params, url('summary_home',
450 repo_name=repo.name,),
450 repo_name=repo.name,),
451 title=repo.dbrepo.description)
451 title=repo.dbrepo.description)
452 return ''
452 return ''
453 map = {'user_deleted_repo':_('User deleted repository'),
453 map = {'user_deleted_repo':_('User deleted repository'),
454 'user_created_repo':_('User created repository'),
454 'user_created_repo':_('User created repository'),
455 'user_forked_repo':_('User forked repository as: ') + get_fork_name(),
455 'user_forked_repo':_('User forked repository as: ') + get_fork_name(),
456 'user_updated_repo':_('User updated repository'),
456 'user_updated_repo':_('User updated repository'),
457 'admin_deleted_repo':_('Admin delete repository'),
457 'admin_deleted_repo':_('Admin delete repository'),
458 'admin_created_repo':_('Admin created repository'),
458 'admin_created_repo':_('Admin created repository'),
459 'admin_forked_repo':_('Admin forked repository'),
459 'admin_forked_repo':_('Admin forked repository'),
460 'admin_updated_repo':_('Admin updated repository'),
460 'admin_updated_repo':_('Admin updated repository'),
461 'push':_('Pushed') + get_cs_links(),
461 'push':_('Pushed') + get_cs_links(),
462 'pull':_('Pulled'), }
462 'pull':_('Pulled'),
463 'started_following_repo':_('User started following repository'),
464 'stopped_following_repo':_('User stopped following repository'),
465 }
463
466
464 return map.get(action, action)
467 return map.get(action, action)
465
468
466
469
467 #==============================================================================
470 #==============================================================================
468 # PERMS
471 # PERMS
469 #==============================================================================
472 #==============================================================================
470 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
473 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
471 HasRepoPermissionAny, HasRepoPermissionAll
474 HasRepoPermissionAny, HasRepoPermissionAll
472
475
473 #==============================================================================
476 #==============================================================================
474 # GRAVATAR URL
477 # GRAVATAR URL
475 #==============================================================================
478 #==============================================================================
476 import hashlib
479 import hashlib
477 import urllib
480 import urllib
478 from pylons import request
481 from pylons import request
479
482
480 def gravatar_url(email_address, size=30):
483 def gravatar_url(email_address, size=30):
481 ssl_enabled = 'https' == request.environ.get('HTTP_X_URL_SCHEME')
484 ssl_enabled = 'https' == request.environ.get('HTTP_X_URL_SCHEME')
482 default = 'identicon'
485 default = 'identicon'
483 baseurl_nossl = "http://www.gravatar.com/avatar/"
486 baseurl_nossl = "http://www.gravatar.com/avatar/"
484 baseurl_ssl = "https://secure.gravatar.com/avatar/"
487 baseurl_ssl = "https://secure.gravatar.com/avatar/"
485 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
488 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
486
489
487
490
488 # construct the url
491 # construct the url
489 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
492 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
490 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
493 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
491
494
492 return gravatar_url
495 return gravatar_url
493
496
494 def safe_unicode(str):
497 def safe_unicode(str):
495 """safe unicode function. In case of UnicodeDecode error we try to return
498 """safe unicode function. In case of UnicodeDecode error we try to return
496 unicode with errors replace, if this failes we return unicode with
499 unicode with errors replace, if this failes we return unicode with
497 string_escape decoding """
500 string_escape decoding """
498
501
499 try:
502 try:
500 u_str = unicode(str)
503 u_str = unicode(str)
501 except UnicodeDecodeError:
504 except UnicodeDecodeError:
502 try:
505 try:
503 u_str = unicode(str, 'utf-8', 'replace')
506 u_str = unicode(str, 'utf-8', 'replace')
504 except UnicodeDecodeError:
507 except UnicodeDecodeError:
505 #incase we have a decode error just represent as byte string
508 #incase we have a decode error just represent as byte string
506 u_str = unicode(str(str).encode('string_escape'))
509 u_str = unicode(str(str).encode('string_escape'))
507
510
508 return u_str
511 return u_str
@@ -1,580 +1,585 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # Utilities for RhodeCode
3 # Utilities for RhodeCode
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 # This program is free software; you can redistribute it and/or
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; version 2
7 # as published by the Free Software Foundation; version 2
8 # of the License or (at your opinion) any later version of the license.
8 # of the License or (at your opinion) any later version of the license.
9 #
9 #
10 # This program is distributed in the hope that it will be useful,
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
13 # GNU General Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # MA 02110-1301, USA.
18 # MA 02110-1301, USA.
19 """
19 """
20 Created on April 18, 2010
20 Created on April 18, 2010
21 Utilities for RhodeCode
21 Utilities for RhodeCode
22 @author: marcink
22 @author: marcink
23 """
23 """
24
24
25 from UserDict import DictMixin
25 from UserDict import DictMixin
26 from mercurial import ui, config, hg
26 from mercurial import ui, config, hg
27 from mercurial.error import RepoError
27 from mercurial.error import RepoError
28 from rhodecode.model import meta
28 from rhodecode.model import meta
29 from rhodecode.model.caching_query import FromCache
29 from rhodecode.model.caching_query import FromCache
30 from rhodecode.model.db import Repository, User, RhodeCodeUi, RhodeCodeSettings, \
30 from rhodecode.model.db import Repository, User, RhodeCodeUi, RhodeCodeSettings, \
31 UserLog
31 UserLog
32 from rhodecode.model.repo import RepoModel
32 from rhodecode.model.repo import RepoModel
33 from rhodecode.model.user import UserModel
33 from rhodecode.model.user import UserModel
34 from vcs.backends.base import BaseChangeset
34 from vcs.backends.base import BaseChangeset
35 from paste.script import command
35 from paste.script import command
36 import ConfigParser
36 import ConfigParser
37 from vcs.utils.lazy import LazyProperty
37 from vcs.utils.lazy import LazyProperty
38 import traceback
38 import traceback
39 import datetime
39 import datetime
40 import logging
40 import logging
41 import os
41 import os
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 def get_repo_slug(request):
46 def get_repo_slug(request):
47 return request.environ['pylons.routes_dict'].get('repo_name')
47 return request.environ['pylons.routes_dict'].get('repo_name')
48
48
49 def is_mercurial(environ):
49 def is_mercurial(environ):
50 """
50 """
51 Returns True if request's target is mercurial server - header
51 Returns True if request's target is mercurial server - header
52 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
52 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
53 """
53 """
54 http_accept = environ.get('HTTP_ACCEPT')
54 http_accept = environ.get('HTTP_ACCEPT')
55 if http_accept and http_accept.startswith('application/mercurial'):
55 if http_accept and http_accept.startswith('application/mercurial'):
56 return True
56 return True
57 return False
57 return False
58
58
59 def is_git(environ):
59 def is_git(environ):
60 """
60 """
61 Returns True if request's target is git server. ``HTTP_USER_AGENT`` would
61 Returns True if request's target is git server. ``HTTP_USER_AGENT`` would
62 then have git client version given.
62 then have git client version given.
63
63
64 :param environ:
64 :param environ:
65 """
65 """
66 http_user_agent = environ.get('HTTP_USER_AGENT')
66 http_user_agent = environ.get('HTTP_USER_AGENT')
67 if http_user_agent and http_user_agent.startswith('git'):
67 if http_user_agent and http_user_agent.startswith('git'):
68 return True
68 return True
69 return False
69 return False
70
70
71 def action_logger(user, action, repo, ipaddr='', sa=None):
71 def action_logger(user, action, repo, ipaddr='', sa=None):
72 """
72 """
73 Action logger for various action made by users
73 Action logger for various actions made by users
74
74
75 :param user: user that made this action, can be a string unique username or
75 :param user: user that made this action, can be a unique username string or
76 object containing user_id attribute
76 object containing user_id attribute
77 :param action: action to log, should be on of predefined unique actions for
77 :param action: action to log, should be on of predefined unique actions for
78 easy translations
78 easy translations
79 :param repo: repository that action was made on
79 :param repo: string name of repository or object containing repo_id,
80 that action was made on
80 :param ipaddr: optional ip address from what the action was made
81 :param ipaddr: optional ip address from what the action was made
81 :param sa: optional sqlalchemy session
82 :param sa: optional sqlalchemy session
82
83
83 """
84 """
84
85
85 if not sa:
86 if not sa:
86 sa = meta.Session()
87 sa = meta.Session()
87
88
88 try:
89 try:
90 um = UserModel()
89 if hasattr(user, 'user_id'):
91 if hasattr(user, 'user_id'):
90 user_obj = user
92 user_obj = user
91 elif isinstance(user, basestring):
93 elif isinstance(user, basestring):
92 user_obj = UserModel().get_by_username(user, cache=False)
94 user_obj = um.get_by_username(user, cache=False)
93 else:
95 else:
94 raise Exception('You have to provide user object or username')
96 raise Exception('You have to provide user object or username')
95
97
96
98
97 if repo:
99 rm = RepoModel()
100 if hasattr(repo, 'repo_id'):
101 repo_obj = rm.get(repo.repo_id, cache=False)
102 repo_name = repo_obj.repo_name
103 elif isinstance(repo, basestring):
98 repo_name = repo.lstrip('/')
104 repo_name = repo.lstrip('/')
99
105 repo_obj = rm.get_by_repo_name(repo_name, cache=False)
100 repository = RepoModel().get(repo_name, cache=False)
101 if not repository:
102 raise Exception('You have to provide valid repository')
103 else:
106 else:
104 raise Exception('You have to provide repository to action logger')
107 raise Exception('You have to provide repository to action logger')
105
108
106
109
107 user_log = UserLog()
110 user_log = UserLog()
108 user_log.user_id = user_obj.user_id
111 user_log.user_id = user_obj.user_id
109 user_log.action = action
112 user_log.action = action
113
114 user_log.repository_id = repo_obj.repo_id
110 user_log.repository_name = repo_name
115 user_log.repository_name = repo_name
111 user_log.repository = repository
116
112 user_log.action_date = datetime.datetime.now()
117 user_log.action_date = datetime.datetime.now()
113 user_log.user_ip = ipaddr
118 user_log.user_ip = ipaddr
114 sa.add(user_log)
119 sa.add(user_log)
115 sa.commit()
120 sa.commit()
116
121
117 log.info('Adding user %s, action %s on %s',
122 log.info('Adding user %s, action %s on %s',
118 user_obj.username, action, repo)
123 user_obj.username, action, repo)
119 except:
124 except:
120 log.error(traceback.format_exc())
125 log.error(traceback.format_exc())
121 sa.rollback()
126 sa.rollback()
122
127
123 def get_repos(path, recursive=False, initial=False):
128 def get_repos(path, recursive=False, initial=False):
124 """
129 """
125 Scans given path for repos and return (name,(type,path)) tuple
130 Scans given path for repos and return (name,(type,path)) tuple
126 :param prefix:
131 :param prefix:
127 :param path:
132 :param path:
128 :param recursive:
133 :param recursive:
129 :param initial:
134 :param initial:
130 """
135 """
131 from vcs.utils.helpers import get_scm
136 from vcs.utils.helpers import get_scm
132 from vcs.exceptions import VCSError
137 from vcs.exceptions import VCSError
133
138
134 try:
139 try:
135 scm = get_scm(path)
140 scm = get_scm(path)
136 except:
141 except:
137 pass
142 pass
138 else:
143 else:
139 raise Exception('The given path %s should not be a repository got %s',
144 raise Exception('The given path %s should not be a repository got %s',
140 path, scm)
145 path, scm)
141
146
142 for dirpath in os.listdir(path):
147 for dirpath in os.listdir(path):
143 try:
148 try:
144 yield dirpath, get_scm(os.path.join(path, dirpath))
149 yield dirpath, get_scm(os.path.join(path, dirpath))
145 except VCSError:
150 except VCSError:
146 pass
151 pass
147
152
148 if __name__ == '__main__':
153 if __name__ == '__main__':
149 get_repos('', '/home/marcink/workspace-python')
154 get_repos('', '/home/marcink/workspace-python')
150
155
151
156
152 def check_repo_fast(repo_name, base_path):
157 def check_repo_fast(repo_name, base_path):
153 if os.path.isdir(os.path.join(base_path, repo_name)):return False
158 if os.path.isdir(os.path.join(base_path, repo_name)):return False
154 return True
159 return True
155
160
156 def check_repo(repo_name, base_path, verify=True):
161 def check_repo(repo_name, base_path, verify=True):
157
162
158 repo_path = os.path.join(base_path, repo_name)
163 repo_path = os.path.join(base_path, repo_name)
159
164
160 try:
165 try:
161 if not check_repo_fast(repo_name, base_path):
166 if not check_repo_fast(repo_name, base_path):
162 return False
167 return False
163 r = hg.repository(ui.ui(), repo_path)
168 r = hg.repository(ui.ui(), repo_path)
164 if verify:
169 if verify:
165 hg.verify(r)
170 hg.verify(r)
166 #here we hnow that repo exists it was verified
171 #here we hnow that repo exists it was verified
167 log.info('%s repo is already created', repo_name)
172 log.info('%s repo is already created', repo_name)
168 return False
173 return False
169 except RepoError:
174 except RepoError:
170 #it means that there is no valid repo there...
175 #it means that there is no valid repo there...
171 log.info('%s repo is free for creation', repo_name)
176 log.info('%s repo is free for creation', repo_name)
172 return True
177 return True
173
178
174 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
179 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
175 while True:
180 while True:
176 ok = raw_input(prompt)
181 ok = raw_input(prompt)
177 if ok in ('y', 'ye', 'yes'): return True
182 if ok in ('y', 'ye', 'yes'): return True
178 if ok in ('n', 'no', 'nop', 'nope'): return False
183 if ok in ('n', 'no', 'nop', 'nope'): return False
179 retries = retries - 1
184 retries = retries - 1
180 if retries < 0: raise IOError
185 if retries < 0: raise IOError
181 print complaint
186 print complaint
182
187
183 def get_hg_ui_cached():
188 def get_hg_ui_cached():
184 try:
189 try:
185 sa = meta.Session
190 sa = meta.Session
186 ret = sa.query(RhodeCodeUi)\
191 ret = sa.query(RhodeCodeUi)\
187 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
192 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
188 .all()
193 .all()
189 except:
194 except:
190 pass
195 pass
191 finally:
196 finally:
192 meta.Session.remove()
197 meta.Session.remove()
193 return ret
198 return ret
194
199
195
200
196 def get_hg_settings():
201 def get_hg_settings():
197 try:
202 try:
198 sa = meta.Session()
203 sa = meta.Session()
199 ret = sa.query(RhodeCodeSettings)\
204 ret = sa.query(RhodeCodeSettings)\
200 .options(FromCache("sql_cache_short", "get_hg_settings"))\
205 .options(FromCache("sql_cache_short", "get_hg_settings"))\
201 .all()
206 .all()
202 except:
207 except:
203 pass
208 pass
204 finally:
209 finally:
205 meta.Session.remove()
210 meta.Session.remove()
206
211
207 if not ret:
212 if not ret:
208 raise Exception('Could not get application settings !')
213 raise Exception('Could not get application settings !')
209 settings = {}
214 settings = {}
210 for each in ret:
215 for each in ret:
211 settings['rhodecode_' + each.app_settings_name] = each.app_settings_value
216 settings['rhodecode_' + each.app_settings_name] = each.app_settings_value
212
217
213 return settings
218 return settings
214
219
215 def get_hg_ui_settings():
220 def get_hg_ui_settings():
216 try:
221 try:
217 sa = meta.Session()
222 sa = meta.Session()
218 ret = sa.query(RhodeCodeUi).all()
223 ret = sa.query(RhodeCodeUi).all()
219 except:
224 except:
220 pass
225 pass
221 finally:
226 finally:
222 meta.Session.remove()
227 meta.Session.remove()
223
228
224 if not ret:
229 if not ret:
225 raise Exception('Could not get application ui settings !')
230 raise Exception('Could not get application ui settings !')
226 settings = {}
231 settings = {}
227 for each in ret:
232 for each in ret:
228 k = each.ui_key
233 k = each.ui_key
229 v = each.ui_value
234 v = each.ui_value
230 if k == '/':
235 if k == '/':
231 k = 'root_path'
236 k = 'root_path'
232
237
233 if k.find('.') != -1:
238 if k.find('.') != -1:
234 k = k.replace('.', '_')
239 k = k.replace('.', '_')
235
240
236 if each.ui_section == 'hooks':
241 if each.ui_section == 'hooks':
237 v = each.ui_active
242 v = each.ui_active
238
243
239 settings[each.ui_section + '_' + k] = v
244 settings[each.ui_section + '_' + k] = v
240
245
241 return settings
246 return settings
242
247
243 #propagated from mercurial documentation
248 #propagated from mercurial documentation
244 ui_sections = ['alias', 'auth',
249 ui_sections = ['alias', 'auth',
245 'decode/encode', 'defaults',
250 'decode/encode', 'defaults',
246 'diff', 'email',
251 'diff', 'email',
247 'extensions', 'format',
252 'extensions', 'format',
248 'merge-patterns', 'merge-tools',
253 'merge-patterns', 'merge-tools',
249 'hooks', 'http_proxy',
254 'hooks', 'http_proxy',
250 'smtp', 'patch',
255 'smtp', 'patch',
251 'paths', 'profiling',
256 'paths', 'profiling',
252 'server', 'trusted',
257 'server', 'trusted',
253 'ui', 'web', ]
258 'ui', 'web', ]
254
259
255 def make_ui(read_from='file', path=None, checkpaths=True):
260 def make_ui(read_from='file', path=None, checkpaths=True):
256 """
261 """
257 A function that will read python rc files or database
262 A function that will read python rc files or database
258 and make an mercurial ui object from read options
263 and make an mercurial ui object from read options
259
264
260 :param path: path to mercurial config file
265 :param path: path to mercurial config file
261 :param checkpaths: check the path
266 :param checkpaths: check the path
262 :param read_from: read from 'file' or 'db'
267 :param read_from: read from 'file' or 'db'
263 """
268 """
264
269
265 baseui = ui.ui()
270 baseui = ui.ui()
266
271
267 #clean the baseui object
272 #clean the baseui object
268 baseui._ocfg = config.config()
273 baseui._ocfg = config.config()
269 baseui._ucfg = config.config()
274 baseui._ucfg = config.config()
270 baseui._tcfg = config.config()
275 baseui._tcfg = config.config()
271
276
272 if read_from == 'file':
277 if read_from == 'file':
273 if not os.path.isfile(path):
278 if not os.path.isfile(path):
274 log.warning('Unable to read config file %s' % path)
279 log.warning('Unable to read config file %s' % path)
275 return False
280 return False
276 log.debug('reading hgrc from %s', path)
281 log.debug('reading hgrc from %s', path)
277 cfg = config.config()
282 cfg = config.config()
278 cfg.read(path)
283 cfg.read(path)
279 for section in ui_sections:
284 for section in ui_sections:
280 for k, v in cfg.items(section):
285 for k, v in cfg.items(section):
281 log.debug('settings ui from file[%s]%s:%s', section, k, v)
286 log.debug('settings ui from file[%s]%s:%s', section, k, v)
282 baseui.setconfig(section, k, v)
287 baseui.setconfig(section, k, v)
283
288
284
289
285 elif read_from == 'db':
290 elif read_from == 'db':
286 hg_ui = get_hg_ui_cached()
291 hg_ui = get_hg_ui_cached()
287 for ui_ in hg_ui:
292 for ui_ in hg_ui:
288 if ui_.ui_active:
293 if ui_.ui_active:
289 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value)
294 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value)
290 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
295 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
291 return baseui
296 return baseui
292
297
293
298
294 def set_rhodecode_config(config):
299 def set_rhodecode_config(config):
295 hgsettings = get_hg_settings()
300 hgsettings = get_hg_settings()
296
301
297 for k, v in hgsettings.items():
302 for k, v in hgsettings.items():
298 config[k] = v
303 config[k] = v
299
304
300 def invalidate_cache(cache_key, *args):
305 def invalidate_cache(cache_key, *args):
301 """
306 """
302 Puts cache invalidation task into db for
307 Puts cache invalidation task into db for
303 further global cache invalidation
308 further global cache invalidation
304 """
309 """
305 from rhodecode.model.scm import ScmModel
310 from rhodecode.model.scm import ScmModel
306
311
307 if cache_key.startswith('get_repo_cached_'):
312 if cache_key.startswith('get_repo_cached_'):
308 name = cache_key.split('get_repo_cached_')[-1]
313 name = cache_key.split('get_repo_cached_')[-1]
309 ScmModel().mark_for_invalidation(name)
314 ScmModel().mark_for_invalidation(name)
310
315
311 class EmptyChangeset(BaseChangeset):
316 class EmptyChangeset(BaseChangeset):
312 """
317 """
313 An dummy empty changeset. It's possible to pass hash when creating
318 An dummy empty changeset. It's possible to pass hash when creating
314 an EmptyChangeset
319 an EmptyChangeset
315 """
320 """
316
321
317 def __init__(self, cs='0' * 40):
322 def __init__(self, cs='0' * 40):
318 self._empty_cs = cs
323 self._empty_cs = cs
319 self.revision = -1
324 self.revision = -1
320 self.message = ''
325 self.message = ''
321 self.author = ''
326 self.author = ''
322 self.date = ''
327 self.date = ''
323
328
324 @LazyProperty
329 @LazyProperty
325 def raw_id(self):
330 def raw_id(self):
326 """
331 """
327 Returns raw string identifying this changeset, useful for web
332 Returns raw string identifying this changeset, useful for web
328 representation.
333 representation.
329 """
334 """
330 return self._empty_cs
335 return self._empty_cs
331
336
332 @LazyProperty
337 @LazyProperty
333 def short_id(self):
338 def short_id(self):
334 return self.raw_id[:12]
339 return self.raw_id[:12]
335
340
336 def get_file_changeset(self, path):
341 def get_file_changeset(self, path):
337 return self
342 return self
338
343
339 def get_file_content(self, path):
344 def get_file_content(self, path):
340 return u''
345 return u''
341
346
342 def get_file_size(self, path):
347 def get_file_size(self, path):
343 return 0
348 return 0
344
349
345 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
350 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
346 """
351 """
347 maps all found repositories into db
352 maps all found repositories into db
348 """
353 """
349
354
350 sa = meta.Session()
355 sa = meta.Session()
351 rm = RepoModel()
356 rm = RepoModel()
352 user = sa.query(User).filter(User.admin == True).first()
357 user = sa.query(User).filter(User.admin == True).first()
353
358
354 for name, repo in initial_repo_list.items():
359 for name, repo in initial_repo_list.items():
355 if not rm.get(name, cache=False):
360 if not rm.get_by_repo_name(name, cache=False):
356 log.info('repository %s not found creating default', name)
361 log.info('repository %s not found creating default', name)
357
362
358 form_data = {
363 form_data = {
359 'repo_name':name,
364 'repo_name':name,
360 'repo_type':repo.alias,
365 'repo_type':repo.alias,
361 'description':repo.description \
366 'description':repo.description \
362 if repo.description != 'unknown' else \
367 if repo.description != 'unknown' else \
363 '%s repository' % name,
368 '%s repository' % name,
364 'private':False
369 'private':False
365 }
370 }
366 rm.create(form_data, user, just_db=True)
371 rm.create(form_data, user, just_db=True)
367
372
368 if remove_obsolete:
373 if remove_obsolete:
369 #remove from database those repositories that are not in the filesystem
374 #remove from database those repositories that are not in the filesystem
370 for repo in sa.query(Repository).all():
375 for repo in sa.query(Repository).all():
371 if repo.repo_name not in initial_repo_list.keys():
376 if repo.repo_name not in initial_repo_list.keys():
372 sa.delete(repo)
377 sa.delete(repo)
373 sa.commit()
378 sa.commit()
374
379
375 class OrderedDict(dict, DictMixin):
380 class OrderedDict(dict, DictMixin):
376
381
377 def __init__(self, *args, **kwds):
382 def __init__(self, *args, **kwds):
378 if len(args) > 1:
383 if len(args) > 1:
379 raise TypeError('expected at most 1 arguments, got %d' % len(args))
384 raise TypeError('expected at most 1 arguments, got %d' % len(args))
380 try:
385 try:
381 self.__end
386 self.__end
382 except AttributeError:
387 except AttributeError:
383 self.clear()
388 self.clear()
384 self.update(*args, **kwds)
389 self.update(*args, **kwds)
385
390
386 def clear(self):
391 def clear(self):
387 self.__end = end = []
392 self.__end = end = []
388 end += [None, end, end] # sentinel node for doubly linked list
393 end += [None, end, end] # sentinel node for doubly linked list
389 self.__map = {} # key --> [key, prev, next]
394 self.__map = {} # key --> [key, prev, next]
390 dict.clear(self)
395 dict.clear(self)
391
396
392 def __setitem__(self, key, value):
397 def __setitem__(self, key, value):
393 if key not in self:
398 if key not in self:
394 end = self.__end
399 end = self.__end
395 curr = end[1]
400 curr = end[1]
396 curr[2] = end[1] = self.__map[key] = [key, curr, end]
401 curr[2] = end[1] = self.__map[key] = [key, curr, end]
397 dict.__setitem__(self, key, value)
402 dict.__setitem__(self, key, value)
398
403
399 def __delitem__(self, key):
404 def __delitem__(self, key):
400 dict.__delitem__(self, key)
405 dict.__delitem__(self, key)
401 key, prev, next = self.__map.pop(key)
406 key, prev, next = self.__map.pop(key)
402 prev[2] = next
407 prev[2] = next
403 next[1] = prev
408 next[1] = prev
404
409
405 def __iter__(self):
410 def __iter__(self):
406 end = self.__end
411 end = self.__end
407 curr = end[2]
412 curr = end[2]
408 while curr is not end:
413 while curr is not end:
409 yield curr[0]
414 yield curr[0]
410 curr = curr[2]
415 curr = curr[2]
411
416
412 def __reversed__(self):
417 def __reversed__(self):
413 end = self.__end
418 end = self.__end
414 curr = end[1]
419 curr = end[1]
415 while curr is not end:
420 while curr is not end:
416 yield curr[0]
421 yield curr[0]
417 curr = curr[1]
422 curr = curr[1]
418
423
419 def popitem(self, last=True):
424 def popitem(self, last=True):
420 if not self:
425 if not self:
421 raise KeyError('dictionary is empty')
426 raise KeyError('dictionary is empty')
422 if last:
427 if last:
423 key = reversed(self).next()
428 key = reversed(self).next()
424 else:
429 else:
425 key = iter(self).next()
430 key = iter(self).next()
426 value = self.pop(key)
431 value = self.pop(key)
427 return key, value
432 return key, value
428
433
429 def __reduce__(self):
434 def __reduce__(self):
430 items = [[k, self[k]] for k in self]
435 items = [[k, self[k]] for k in self]
431 tmp = self.__map, self.__end
436 tmp = self.__map, self.__end
432 del self.__map, self.__end
437 del self.__map, self.__end
433 inst_dict = vars(self).copy()
438 inst_dict = vars(self).copy()
434 self.__map, self.__end = tmp
439 self.__map, self.__end = tmp
435 if inst_dict:
440 if inst_dict:
436 return (self.__class__, (items,), inst_dict)
441 return (self.__class__, (items,), inst_dict)
437 return self.__class__, (items,)
442 return self.__class__, (items,)
438
443
439 def keys(self):
444 def keys(self):
440 return list(self)
445 return list(self)
441
446
442 setdefault = DictMixin.setdefault
447 setdefault = DictMixin.setdefault
443 update = DictMixin.update
448 update = DictMixin.update
444 pop = DictMixin.pop
449 pop = DictMixin.pop
445 values = DictMixin.values
450 values = DictMixin.values
446 items = DictMixin.items
451 items = DictMixin.items
447 iterkeys = DictMixin.iterkeys
452 iterkeys = DictMixin.iterkeys
448 itervalues = DictMixin.itervalues
453 itervalues = DictMixin.itervalues
449 iteritems = DictMixin.iteritems
454 iteritems = DictMixin.iteritems
450
455
451 def __repr__(self):
456 def __repr__(self):
452 if not self:
457 if not self:
453 return '%s()' % (self.__class__.__name__,)
458 return '%s()' % (self.__class__.__name__,)
454 return '%s(%r)' % (self.__class__.__name__, self.items())
459 return '%s(%r)' % (self.__class__.__name__, self.items())
455
460
456 def copy(self):
461 def copy(self):
457 return self.__class__(self)
462 return self.__class__(self)
458
463
459 @classmethod
464 @classmethod
460 def fromkeys(cls, iterable, value=None):
465 def fromkeys(cls, iterable, value=None):
461 d = cls()
466 d = cls()
462 for key in iterable:
467 for key in iterable:
463 d[key] = value
468 d[key] = value
464 return d
469 return d
465
470
466 def __eq__(self, other):
471 def __eq__(self, other):
467 if isinstance(other, OrderedDict):
472 if isinstance(other, OrderedDict):
468 return len(self) == len(other) and self.items() == other.items()
473 return len(self) == len(other) and self.items() == other.items()
469 return dict.__eq__(self, other)
474 return dict.__eq__(self, other)
470
475
471 def __ne__(self, other):
476 def __ne__(self, other):
472 return not self == other
477 return not self == other
473
478
474
479
475 #===============================================================================
480 #===============================================================================
476 # TEST FUNCTIONS AND CREATORS
481 # TEST FUNCTIONS AND CREATORS
477 #===============================================================================
482 #===============================================================================
478 def create_test_index(repo_location, full_index):
483 def create_test_index(repo_location, full_index):
479 """Makes default test index
484 """Makes default test index
480 :param repo_location:
485 :param repo_location:
481 :param full_index:
486 :param full_index:
482 """
487 """
483 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
488 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
484 from rhodecode.lib.pidlock import DaemonLock, LockHeld
489 from rhodecode.lib.pidlock import DaemonLock, LockHeld
485 import shutil
490 import shutil
486
491
487 index_location = os.path.join(repo_location, 'index')
492 index_location = os.path.join(repo_location, 'index')
488 if os.path.exists(index_location):
493 if os.path.exists(index_location):
489 shutil.rmtree(index_location)
494 shutil.rmtree(index_location)
490
495
491 try:
496 try:
492 l = DaemonLock()
497 l = DaemonLock()
493 WhooshIndexingDaemon(index_location=index_location,
498 WhooshIndexingDaemon(index_location=index_location,
494 repo_location=repo_location)\
499 repo_location=repo_location)\
495 .run(full_index=full_index)
500 .run(full_index=full_index)
496 l.release()
501 l.release()
497 except LockHeld:
502 except LockHeld:
498 pass
503 pass
499
504
500 def create_test_env(repos_test_path, config):
505 def create_test_env(repos_test_path, config):
501 """Makes a fresh database and
506 """Makes a fresh database and
502 install test repository into tmp dir
507 install test repository into tmp dir
503 """
508 """
504 from rhodecode.lib.db_manage import DbManage
509 from rhodecode.lib.db_manage import DbManage
505 from rhodecode.tests import HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, \
510 from rhodecode.tests import HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, \
506 HG_FORK, GIT_FORK, TESTS_TMP_PATH
511 HG_FORK, GIT_FORK, TESTS_TMP_PATH
507 import tarfile
512 import tarfile
508 import shutil
513 import shutil
509 from os.path import dirname as dn, join as jn, abspath
514 from os.path import dirname as dn, join as jn, abspath
510
515
511 log = logging.getLogger('TestEnvCreator')
516 log = logging.getLogger('TestEnvCreator')
512 # create logger
517 # create logger
513 log.setLevel(logging.DEBUG)
518 log.setLevel(logging.DEBUG)
514 log.propagate = True
519 log.propagate = True
515 # create console handler and set level to debug
520 # create console handler and set level to debug
516 ch = logging.StreamHandler()
521 ch = logging.StreamHandler()
517 ch.setLevel(logging.DEBUG)
522 ch.setLevel(logging.DEBUG)
518
523
519 # create formatter
524 # create formatter
520 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
525 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
521
526
522 # add formatter to ch
527 # add formatter to ch
523 ch.setFormatter(formatter)
528 ch.setFormatter(formatter)
524
529
525 # add ch to logger
530 # add ch to logger
526 log.addHandler(ch)
531 log.addHandler(ch)
527
532
528 #PART ONE create db
533 #PART ONE create db
529 dbname = config['sqlalchemy.db1.url'].split('/')[-1]
534 dbname = config['sqlalchemy.db1.url'].split('/')[-1]
530 log.debug('making test db %s', dbname)
535 log.debug('making test db %s', dbname)
531
536
532 dbmanage = DbManage(log_sql=True, dbname=dbname, root=config['here'],
537 dbmanage = DbManage(log_sql=True, dbname=dbname, root=config['here'],
533 tests=True)
538 tests=True)
534 dbmanage.create_tables(override=True)
539 dbmanage.create_tables(override=True)
535 dbmanage.config_prompt(repos_test_path)
540 dbmanage.config_prompt(repos_test_path)
536 dbmanage.create_default_user()
541 dbmanage.create_default_user()
537 dbmanage.admin_prompt()
542 dbmanage.admin_prompt()
538 dbmanage.create_permissions()
543 dbmanage.create_permissions()
539 dbmanage.populate_default_permissions()
544 dbmanage.populate_default_permissions()
540
545
541 #PART TWO make test repo
546 #PART TWO make test repo
542 log.debug('making test vcs repositories')
547 log.debug('making test vcs repositories')
543
548
544 #remove old one from previos tests
549 #remove old one from previos tests
545 for r in [HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, HG_FORK, GIT_FORK]:
550 for r in [HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, HG_FORK, GIT_FORK]:
546
551
547 if os.path.isdir(jn(TESTS_TMP_PATH, r)):
552 if os.path.isdir(jn(TESTS_TMP_PATH, r)):
548 log.debug('removing %s', r)
553 log.debug('removing %s', r)
549 shutil.rmtree(jn(TESTS_TMP_PATH, r))
554 shutil.rmtree(jn(TESTS_TMP_PATH, r))
550
555
551 #CREATE DEFAULT HG REPOSITORY
556 #CREATE DEFAULT HG REPOSITORY
552 cur_dir = dn(dn(abspath(__file__)))
557 cur_dir = dn(dn(abspath(__file__)))
553 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
558 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
554 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
559 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
555 tar.close()
560 tar.close()
556
561
557 class UpgradeDb(command.Command):
562 class UpgradeDb(command.Command):
558 """Command used for paster to upgrade our database to newer version
563 """Command used for paster to upgrade our database to newer version
559 """
564 """
560
565
561 max_args = 1
566 max_args = 1
562 min_args = 1
567 min_args = 1
563
568
564 usage = "CONFIG_FILE"
569 usage = "CONFIG_FILE"
565 summary = "Upgrades current db to newer version given configuration file"
570 summary = "Upgrades current db to newer version given configuration file"
566 group_name = "RhodeCode"
571 group_name = "RhodeCode"
567
572
568 parser = command.Command.standard_parser(verbose=True)
573 parser = command.Command.standard_parser(verbose=True)
569
574
570 parser.add_option('--sql',
575 parser.add_option('--sql',
571 action='store_true',
576 action='store_true',
572 dest='just_sql',
577 dest='just_sql',
573 help="Prints upgrade sql for further investigation",
578 help="Prints upgrade sql for further investigation",
574 default=False)
579 default=False)
575 def command(self):
580 def command(self):
576 config_name = self.args[0]
581 config_name = self.args[0]
577 p = config_name.split('/')
582 p = config_name.split('/')
578 root = '.' if len(p) == 1 else '/'.join(p[:-1])
583 root = '.' if len(p) == 1 else '/'.join(p[:-1])
579 config = ConfigParser.ConfigParser({'here':root})
584 config = ConfigParser.ConfigParser({'here':root})
580 config.read(config_name)
585 config.read(config_name)
@@ -1,438 +1,438 b''
1 """ this is forms validation classes
1 """ this is forms validation classes
2 http://formencode.org/module-formencode.validators.html
2 http://formencode.org/module-formencode.validators.html
3 for list off all availible validators
3 for list off all availible validators
4
4
5 we can create our own validators
5 we can create our own validators
6
6
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 pre_validators [] These validators will be applied before the schema
8 pre_validators [] These validators will be applied before the schema
9 chained_validators [] These validators will be applied after the schema
9 chained_validators [] These validators will be applied after the schema
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14
14
15
15
16 <name> = formencode.validators.<name of validator>
16 <name> = formencode.validators.<name of validator>
17 <name> must equal form name
17 <name> must equal form name
18 list=[1,2,3,4,5]
18 list=[1,2,3,4,5]
19 for SELECT use formencode.All(OneOf(list), Int())
19 for SELECT use formencode.All(OneOf(list), Int())
20
20
21 """
21 """
22 from formencode import All
22 from formencode import All
23 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
23 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
24 Email, Bool, StringBoolean
24 Email, Bool, StringBoolean
25 from pylons import session
25 from pylons import session
26 from pylons.i18n.translation import _
26 from pylons.i18n.translation import _
27 from rhodecode.lib.auth import authfunc, get_crypt_password
27 from rhodecode.lib.auth import authfunc, get_crypt_password
28 from rhodecode.lib.exceptions import LdapImportError
28 from rhodecode.lib.exceptions import LdapImportError
29 from rhodecode.model import meta
29 from rhodecode.model import meta
30 from rhodecode.model.user import UserModel
30 from rhodecode.model.user import UserModel
31 from rhodecode.model.repo import RepoModel
31 from rhodecode.model.repo import RepoModel
32 from rhodecode.model.db import User
32 from rhodecode.model.db import User
33 from webhelpers.pylonslib.secure_form import authentication_token
33 from webhelpers.pylonslib.secure_form import authentication_token
34 from rhodecode import BACKENDS
34 from rhodecode import BACKENDS
35 import formencode
35 import formencode
36 import logging
36 import logging
37 import os
37 import os
38 import rhodecode.lib.helpers as h
38 import rhodecode.lib.helpers as h
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42 #this is needed to translate the messages using _() in validators
42 #this is needed to translate the messages using _() in validators
43 class State_obj(object):
43 class State_obj(object):
44 _ = staticmethod(_)
44 _ = staticmethod(_)
45
45
46 #===============================================================================
46 #===============================================================================
47 # VALIDATORS
47 # VALIDATORS
48 #===============================================================================
48 #===============================================================================
49 class ValidAuthToken(formencode.validators.FancyValidator):
49 class ValidAuthToken(formencode.validators.FancyValidator):
50 messages = {'invalid_token':_('Token mismatch')}
50 messages = {'invalid_token':_('Token mismatch')}
51
51
52 def validate_python(self, value, state):
52 def validate_python(self, value, state):
53
53
54 if value != authentication_token():
54 if value != authentication_token():
55 raise formencode.Invalid(self.message('invalid_token', state,
55 raise formencode.Invalid(self.message('invalid_token', state,
56 search_number=value), value, state)
56 search_number=value), value, state)
57
57
58 def ValidUsername(edit, old_data):
58 def ValidUsername(edit, old_data):
59 class _ValidUsername(formencode.validators.FancyValidator):
59 class _ValidUsername(formencode.validators.FancyValidator):
60
60
61 def validate_python(self, value, state):
61 def validate_python(self, value, state):
62 if value in ['default', 'new_user']:
62 if value in ['default', 'new_user']:
63 raise formencode.Invalid(_('Invalid username'), value, state)
63 raise formencode.Invalid(_('Invalid username'), value, state)
64 #check if user is unique
64 #check if user is unique
65 old_un = None
65 old_un = None
66 if edit:
66 if edit:
67 old_un = UserModel().get(old_data.get('user_id')).username
67 old_un = UserModel().get(old_data.get('user_id')).username
68
68
69 if old_un != value or not edit:
69 if old_un != value or not edit:
70 if UserModel().get_by_username(value, cache=False):
70 if UserModel().get_by_username(value, cache=False):
71 raise formencode.Invalid(_('This username already exists') ,
71 raise formencode.Invalid(_('This username already exists') ,
72 value, state)
72 value, state)
73
73
74 return _ValidUsername
74 return _ValidUsername
75
75
76 class ValidPassword(formencode.validators.FancyValidator):
76 class ValidPassword(formencode.validators.FancyValidator):
77
77
78 def to_python(self, value, state):
78 def to_python(self, value, state):
79
79
80 if value:
80 if value:
81
81
82 if value.get('password'):
82 if value.get('password'):
83 try:
83 try:
84 value['password'] = get_crypt_password(value['password'])
84 value['password'] = get_crypt_password(value['password'])
85 except UnicodeEncodeError:
85 except UnicodeEncodeError:
86 e_dict = {'password':_('Invalid characters in password')}
86 e_dict = {'password':_('Invalid characters in password')}
87 raise formencode.Invalid('', value, state, error_dict=e_dict)
87 raise formencode.Invalid('', value, state, error_dict=e_dict)
88
88
89 if value.get('password_confirmation'):
89 if value.get('password_confirmation'):
90 try:
90 try:
91 value['password_confirmation'] = \
91 value['password_confirmation'] = \
92 get_crypt_password(value['password_confirmation'])
92 get_crypt_password(value['password_confirmation'])
93 except UnicodeEncodeError:
93 except UnicodeEncodeError:
94 e_dict = {'password_confirmation':_('Invalid characters in password')}
94 e_dict = {'password_confirmation':_('Invalid characters in password')}
95 raise formencode.Invalid('', value, state, error_dict=e_dict)
95 raise formencode.Invalid('', value, state, error_dict=e_dict)
96
96
97 if value.get('new_password'):
97 if value.get('new_password'):
98 try:
98 try:
99 value['new_password'] = \
99 value['new_password'] = \
100 get_crypt_password(value['new_password'])
100 get_crypt_password(value['new_password'])
101 except UnicodeEncodeError:
101 except UnicodeEncodeError:
102 e_dict = {'new_password':_('Invalid characters in password')}
102 e_dict = {'new_password':_('Invalid characters in password')}
103 raise formencode.Invalid('', value, state, error_dict=e_dict)
103 raise formencode.Invalid('', value, state, error_dict=e_dict)
104
104
105 return value
105 return value
106
106
107 class ValidPasswordsMatch(formencode.validators.FancyValidator):
107 class ValidPasswordsMatch(formencode.validators.FancyValidator):
108
108
109 def validate_python(self, value, state):
109 def validate_python(self, value, state):
110
110
111 if value['password'] != value['password_confirmation']:
111 if value['password'] != value['password_confirmation']:
112 e_dict = {'password_confirmation':
112 e_dict = {'password_confirmation':
113 _('Password do not match')}
113 _('Password do not match')}
114 raise formencode.Invalid('', value, state, error_dict=e_dict)
114 raise formencode.Invalid('', value, state, error_dict=e_dict)
115
115
116 class ValidAuth(formencode.validators.FancyValidator):
116 class ValidAuth(formencode.validators.FancyValidator):
117 messages = {
117 messages = {
118 'invalid_password':_('invalid password'),
118 'invalid_password':_('invalid password'),
119 'invalid_login':_('invalid user name'),
119 'invalid_login':_('invalid user name'),
120 'disabled_account':_('Your account is disabled')
120 'disabled_account':_('Your account is disabled')
121
121
122 }
122 }
123 #error mapping
123 #error mapping
124 e_dict = {'username':messages['invalid_login'],
124 e_dict = {'username':messages['invalid_login'],
125 'password':messages['invalid_password']}
125 'password':messages['invalid_password']}
126 e_dict_disable = {'username':messages['disabled_account']}
126 e_dict_disable = {'username':messages['disabled_account']}
127
127
128 def validate_python(self, value, state):
128 def validate_python(self, value, state):
129 password = value['password']
129 password = value['password']
130 username = value['username']
130 username = value['username']
131 user = UserModel().get_by_username(username)
131 user = UserModel().get_by_username(username)
132
132
133 if authfunc(None, username, password):
133 if authfunc(None, username, password):
134 return value
134 return value
135 else:
135 else:
136 if user and user.active is False:
136 if user and user.active is False:
137 log.warning('user %s is disabled', username)
137 log.warning('user %s is disabled', username)
138 raise formencode.Invalid(self.message('disabled_account',
138 raise formencode.Invalid(self.message('disabled_account',
139 state=State_obj),
139 state=State_obj),
140 value, state,
140 value, state,
141 error_dict=self.e_dict_disable)
141 error_dict=self.e_dict_disable)
142 else:
142 else:
143 log.warning('user %s not authenticated', username)
143 log.warning('user %s not authenticated', username)
144 raise formencode.Invalid(self.message('invalid_password',
144 raise formencode.Invalid(self.message('invalid_password',
145 state=State_obj), value, state,
145 state=State_obj), value, state,
146 error_dict=self.e_dict)
146 error_dict=self.e_dict)
147
147
148 class ValidRepoUser(formencode.validators.FancyValidator):
148 class ValidRepoUser(formencode.validators.FancyValidator):
149
149
150 def to_python(self, value, state):
150 def to_python(self, value, state):
151 sa = meta.Session()
151 sa = meta.Session()
152 try:
152 try:
153 self.user_db = sa.query(User)\
153 self.user_db = sa.query(User)\
154 .filter(User.active == True)\
154 .filter(User.active == True)\
155 .filter(User.username == value).one()
155 .filter(User.username == value).one()
156 except Exception:
156 except Exception:
157 raise formencode.Invalid(_('This username is not valid'),
157 raise formencode.Invalid(_('This username is not valid'),
158 value, state)
158 value, state)
159 finally:
159 finally:
160 meta.Session.remove()
160 meta.Session.remove()
161
161
162 return self.user_db.user_id
162 return self.user_db.user_id
163
163
164 def ValidRepoName(edit, old_data):
164 def ValidRepoName(edit, old_data):
165 class _ValidRepoName(formencode.validators.FancyValidator):
165 class _ValidRepoName(formencode.validators.FancyValidator):
166
166
167 def to_python(self, value, state):
167 def to_python(self, value, state):
168 slug = h.repo_name_slug(value)
168 slug = h.repo_name_slug(value)
169 if slug in ['_admin']:
169 if slug in ['_admin']:
170 raise formencode.Invalid(_('This repository name is disallowed'),
170 raise formencode.Invalid(_('This repository name is disallowed'),
171 value, state)
171 value, state)
172 if old_data.get('repo_name') != value or not edit:
172 if old_data.get('repo_name') != value or not edit:
173 if RepoModel().get(slug, cache=False):
173 if RepoModel().get_by_repo_name(slug, cache=False):
174 raise formencode.Invalid(_('This repository already exists') ,
174 raise formencode.Invalid(_('This repository already exists') ,
175 value, state)
175 value, state)
176 return slug
176 return slug
177
177
178
178
179 return _ValidRepoName
179 return _ValidRepoName
180
180
181 def ValidForkType(old_data):
181 def ValidForkType(old_data):
182 class _ValidForkType(formencode.validators.FancyValidator):
182 class _ValidForkType(formencode.validators.FancyValidator):
183
183
184 def to_python(self, value, state):
184 def to_python(self, value, state):
185 if old_data['repo_type'] != value:
185 if old_data['repo_type'] != value:
186 raise formencode.Invalid(_('Fork have to be the same type as original'), value, state)
186 raise formencode.Invalid(_('Fork have to be the same type as original'), value, state)
187 return value
187 return value
188 return _ValidForkType
188 return _ValidForkType
189
189
190 class ValidPerms(formencode.validators.FancyValidator):
190 class ValidPerms(formencode.validators.FancyValidator):
191 messages = {'perm_new_user_name':_('This username is not valid')}
191 messages = {'perm_new_user_name':_('This username is not valid')}
192
192
193 def to_python(self, value, state):
193 def to_python(self, value, state):
194 perms_update = []
194 perms_update = []
195 perms_new = []
195 perms_new = []
196 #build a list of permission to update and new permission to create
196 #build a list of permission to update and new permission to create
197 for k, v in value.items():
197 for k, v in value.items():
198 if k.startswith('perm_'):
198 if k.startswith('perm_'):
199 if k.startswith('perm_new_user'):
199 if k.startswith('perm_new_user'):
200 new_perm = value.get('perm_new_user', False)
200 new_perm = value.get('perm_new_user', False)
201 new_user = value.get('perm_new_user_name', False)
201 new_user = value.get('perm_new_user_name', False)
202 if new_user and new_perm:
202 if new_user and new_perm:
203 if (new_user, new_perm) not in perms_new:
203 if (new_user, new_perm) not in perms_new:
204 perms_new.append((new_user, new_perm))
204 perms_new.append((new_user, new_perm))
205 else:
205 else:
206 usr = k[5:]
206 usr = k[5:]
207 if usr == 'default':
207 if usr == 'default':
208 if value['private']:
208 if value['private']:
209 #set none for default when updating to private repo
209 #set none for default when updating to private repo
210 v = 'repository.none'
210 v = 'repository.none'
211 perms_update.append((usr, v))
211 perms_update.append((usr, v))
212 value['perms_updates'] = perms_update
212 value['perms_updates'] = perms_update
213 value['perms_new'] = perms_new
213 value['perms_new'] = perms_new
214 sa = meta.Session
214 sa = meta.Session
215 for k, v in perms_new:
215 for k, v in perms_new:
216 try:
216 try:
217 self.user_db = sa.query(User)\
217 self.user_db = sa.query(User)\
218 .filter(User.active == True)\
218 .filter(User.active == True)\
219 .filter(User.username == k).one()
219 .filter(User.username == k).one()
220 except Exception:
220 except Exception:
221 msg = self.message('perm_new_user_name',
221 msg = self.message('perm_new_user_name',
222 state=State_obj)
222 state=State_obj)
223 raise formencode.Invalid(msg, value, state, error_dict={'perm_new_user_name':msg})
223 raise formencode.Invalid(msg, value, state, error_dict={'perm_new_user_name':msg})
224 return value
224 return value
225
225
226 class ValidSettings(formencode.validators.FancyValidator):
226 class ValidSettings(formencode.validators.FancyValidator):
227
227
228 def to_python(self, value, state):
228 def to_python(self, value, state):
229 #settings form can't edit user
229 #settings form can't edit user
230 if value.has_key('user'):
230 if value.has_key('user'):
231 del['value']['user']
231 del['value']['user']
232
232
233 return value
233 return value
234
234
235 class ValidPath(formencode.validators.FancyValidator):
235 class ValidPath(formencode.validators.FancyValidator):
236 def to_python(self, value, state):
236 def to_python(self, value, state):
237
237
238 if not os.path.isdir(value):
238 if not os.path.isdir(value):
239 msg = _('This is not a valid path')
239 msg = _('This is not a valid path')
240 raise formencode.Invalid(msg, value, state,
240 raise formencode.Invalid(msg, value, state,
241 error_dict={'paths_root_path':msg})
241 error_dict={'paths_root_path':msg})
242 return value
242 return value
243
243
244 def UniqSystemEmail(old_data):
244 def UniqSystemEmail(old_data):
245 class _UniqSystemEmail(formencode.validators.FancyValidator):
245 class _UniqSystemEmail(formencode.validators.FancyValidator):
246 def to_python(self, value, state):
246 def to_python(self, value, state):
247 if old_data.get('email') != value:
247 if old_data.get('email') != value:
248 sa = meta.Session()
248 sa = meta.Session()
249 try:
249 try:
250 user = sa.query(User).filter(User.email == value).scalar()
250 user = sa.query(User).filter(User.email == value).scalar()
251 if user:
251 if user:
252 raise formencode.Invalid(_("That e-mail address is already taken") ,
252 raise formencode.Invalid(_("That e-mail address is already taken") ,
253 value, state)
253 value, state)
254 finally:
254 finally:
255 meta.Session.remove()
255 meta.Session.remove()
256
256
257 return value
257 return value
258
258
259 return _UniqSystemEmail
259 return _UniqSystemEmail
260
260
261 class ValidSystemEmail(formencode.validators.FancyValidator):
261 class ValidSystemEmail(formencode.validators.FancyValidator):
262 def to_python(self, value, state):
262 def to_python(self, value, state):
263 sa = meta.Session
263 sa = meta.Session
264 try:
264 try:
265 user = sa.query(User).filter(User.email == value).scalar()
265 user = sa.query(User).filter(User.email == value).scalar()
266 if user is None:
266 if user is None:
267 raise formencode.Invalid(_("That e-mail address doesn't exist.") ,
267 raise formencode.Invalid(_("That e-mail address doesn't exist.") ,
268 value, state)
268 value, state)
269 finally:
269 finally:
270 meta.Session.remove()
270 meta.Session.remove()
271
271
272 return value
272 return value
273
273
274 class LdapLibValidator(formencode.validators.FancyValidator):
274 class LdapLibValidator(formencode.validators.FancyValidator):
275
275
276 def to_python(self, value, state):
276 def to_python(self, value, state):
277
277
278 try:
278 try:
279 import ldap
279 import ldap
280 except ImportError:
280 except ImportError:
281 raise LdapImportError
281 raise LdapImportError
282 return value
282 return value
283
283
284 #===============================================================================
284 #===============================================================================
285 # FORMS
285 # FORMS
286 #===============================================================================
286 #===============================================================================
287 class LoginForm(formencode.Schema):
287 class LoginForm(formencode.Schema):
288 allow_extra_fields = True
288 allow_extra_fields = True
289 filter_extra_fields = True
289 filter_extra_fields = True
290 username = UnicodeString(
290 username = UnicodeString(
291 strip=True,
291 strip=True,
292 min=1,
292 min=1,
293 not_empty=True,
293 not_empty=True,
294 messages={
294 messages={
295 'empty':_('Please enter a login'),
295 'empty':_('Please enter a login'),
296 'tooShort':_('Enter a value %(min)i characters long or more')}
296 'tooShort':_('Enter a value %(min)i characters long or more')}
297 )
297 )
298
298
299 password = UnicodeString(
299 password = UnicodeString(
300 strip=True,
300 strip=True,
301 min=6,
301 min=6,
302 not_empty=True,
302 not_empty=True,
303 messages={
303 messages={
304 'empty':_('Please enter a password'),
304 'empty':_('Please enter a password'),
305 'tooShort':_('Enter %(min)i characters or more')}
305 'tooShort':_('Enter %(min)i characters or more')}
306 )
306 )
307
307
308
308
309 #chained validators have access to all data
309 #chained validators have access to all data
310 chained_validators = [ValidAuth]
310 chained_validators = [ValidAuth]
311
311
312 def UserForm(edit=False, old_data={}):
312 def UserForm(edit=False, old_data={}):
313 class _UserForm(formencode.Schema):
313 class _UserForm(formencode.Schema):
314 allow_extra_fields = True
314 allow_extra_fields = True
315 filter_extra_fields = True
315 filter_extra_fields = True
316 username = All(UnicodeString(strip=True, min=1, not_empty=True), ValidUsername(edit, old_data))
316 username = All(UnicodeString(strip=True, min=1, not_empty=True), ValidUsername(edit, old_data))
317 if edit:
317 if edit:
318 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
318 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
319 admin = StringBoolean(if_missing=False)
319 admin = StringBoolean(if_missing=False)
320 else:
320 else:
321 password = All(UnicodeString(strip=True, min=6, not_empty=True))
321 password = All(UnicodeString(strip=True, min=6, not_empty=True))
322 active = StringBoolean(if_missing=False)
322 active = StringBoolean(if_missing=False)
323 name = UnicodeString(strip=True, min=1, not_empty=True)
323 name = UnicodeString(strip=True, min=1, not_empty=True)
324 lastname = UnicodeString(strip=True, min=1, not_empty=True)
324 lastname = UnicodeString(strip=True, min=1, not_empty=True)
325 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
325 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
326
326
327 chained_validators = [ValidPassword]
327 chained_validators = [ValidPassword]
328
328
329 return _UserForm
329 return _UserForm
330
330
331 def RegisterForm(edit=False, old_data={}):
331 def RegisterForm(edit=False, old_data={}):
332 class _RegisterForm(formencode.Schema):
332 class _RegisterForm(formencode.Schema):
333 allow_extra_fields = True
333 allow_extra_fields = True
334 filter_extra_fields = True
334 filter_extra_fields = True
335 username = All(ValidUsername(edit, old_data), UnicodeString(strip=True, min=1, not_empty=True))
335 username = All(ValidUsername(edit, old_data), UnicodeString(strip=True, min=1, not_empty=True))
336 password = All(UnicodeString(strip=True, min=6, not_empty=True))
336 password = All(UnicodeString(strip=True, min=6, not_empty=True))
337 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
337 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
338 active = StringBoolean(if_missing=False)
338 active = StringBoolean(if_missing=False)
339 name = UnicodeString(strip=True, min=1, not_empty=True)
339 name = UnicodeString(strip=True, min=1, not_empty=True)
340 lastname = UnicodeString(strip=True, min=1, not_empty=True)
340 lastname = UnicodeString(strip=True, min=1, not_empty=True)
341 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
341 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
342
342
343 chained_validators = [ValidPasswordsMatch, ValidPassword]
343 chained_validators = [ValidPasswordsMatch, ValidPassword]
344
344
345 return _RegisterForm
345 return _RegisterForm
346
346
347 def PasswordResetForm():
347 def PasswordResetForm():
348 class _PasswordResetForm(formencode.Schema):
348 class _PasswordResetForm(formencode.Schema):
349 allow_extra_fields = True
349 allow_extra_fields = True
350 filter_extra_fields = True
350 filter_extra_fields = True
351 email = All(ValidSystemEmail(), Email(not_empty=True))
351 email = All(ValidSystemEmail(), Email(not_empty=True))
352 return _PasswordResetForm
352 return _PasswordResetForm
353
353
354 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
354 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
355 class _RepoForm(formencode.Schema):
355 class _RepoForm(formencode.Schema):
356 allow_extra_fields = True
356 allow_extra_fields = True
357 filter_extra_fields = False
357 filter_extra_fields = False
358 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
358 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
359 description = UnicodeString(strip=True, min=1, not_empty=True)
359 description = UnicodeString(strip=True, min=1, not_empty=True)
360 private = StringBoolean(if_missing=False)
360 private = StringBoolean(if_missing=False)
361 repo_type = OneOf(supported_backends)
361 repo_type = OneOf(supported_backends)
362 if edit:
362 if edit:
363 user = All(Int(not_empty=True), ValidRepoUser)
363 user = All(Int(not_empty=True), ValidRepoUser)
364
364
365 chained_validators = [ValidPerms]
365 chained_validators = [ValidPerms]
366 return _RepoForm
366 return _RepoForm
367
367
368 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
368 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
369 class _RepoForkForm(formencode.Schema):
369 class _RepoForkForm(formencode.Schema):
370 allow_extra_fields = True
370 allow_extra_fields = True
371 filter_extra_fields = False
371 filter_extra_fields = False
372 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
372 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
373 description = UnicodeString(strip=True, min=1, not_empty=True)
373 description = UnicodeString(strip=True, min=1, not_empty=True)
374 private = StringBoolean(if_missing=False)
374 private = StringBoolean(if_missing=False)
375 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
375 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
376 return _RepoForkForm
376 return _RepoForkForm
377
377
378 def RepoSettingsForm(edit=False, old_data={}):
378 def RepoSettingsForm(edit=False, old_data={}):
379 class _RepoForm(formencode.Schema):
379 class _RepoForm(formencode.Schema):
380 allow_extra_fields = True
380 allow_extra_fields = True
381 filter_extra_fields = False
381 filter_extra_fields = False
382 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
382 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
383 description = UnicodeString(strip=True, min=1, not_empty=True)
383 description = UnicodeString(strip=True, min=1, not_empty=True)
384 private = StringBoolean(if_missing=False)
384 private = StringBoolean(if_missing=False)
385
385
386 chained_validators = [ValidPerms, ValidSettings]
386 chained_validators = [ValidPerms, ValidSettings]
387 return _RepoForm
387 return _RepoForm
388
388
389
389
390 def ApplicationSettingsForm():
390 def ApplicationSettingsForm():
391 class _ApplicationSettingsForm(formencode.Schema):
391 class _ApplicationSettingsForm(formencode.Schema):
392 allow_extra_fields = True
392 allow_extra_fields = True
393 filter_extra_fields = False
393 filter_extra_fields = False
394 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
394 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
395 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
395 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
396
396
397 return _ApplicationSettingsForm
397 return _ApplicationSettingsForm
398
398
399 def ApplicationUiSettingsForm():
399 def ApplicationUiSettingsForm():
400 class _ApplicationUiSettingsForm(formencode.Schema):
400 class _ApplicationUiSettingsForm(formencode.Schema):
401 allow_extra_fields = True
401 allow_extra_fields = True
402 filter_extra_fields = False
402 filter_extra_fields = False
403 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
403 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
404 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
404 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
405 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
405 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
406 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
406 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
407 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
407 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
408 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
408 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
409
409
410 return _ApplicationUiSettingsForm
410 return _ApplicationUiSettingsForm
411
411
412 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
412 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
413 class _DefaultPermissionsForm(formencode.Schema):
413 class _DefaultPermissionsForm(formencode.Schema):
414 allow_extra_fields = True
414 allow_extra_fields = True
415 filter_extra_fields = True
415 filter_extra_fields = True
416 overwrite_default = StringBoolean(if_missing=False)
416 overwrite_default = StringBoolean(if_missing=False)
417 anonymous = OneOf(['True', 'False'], if_missing=False)
417 anonymous = OneOf(['True', 'False'], if_missing=False)
418 default_perm = OneOf(perms_choices)
418 default_perm = OneOf(perms_choices)
419 default_register = OneOf(register_choices)
419 default_register = OneOf(register_choices)
420 default_create = OneOf(create_choices)
420 default_create = OneOf(create_choices)
421
421
422 return _DefaultPermissionsForm
422 return _DefaultPermissionsForm
423
423
424
424
425 def LdapSettingsForm():
425 def LdapSettingsForm():
426 class _LdapSettingsForm(formencode.Schema):
426 class _LdapSettingsForm(formencode.Schema):
427 allow_extra_fields = True
427 allow_extra_fields = True
428 filter_extra_fields = True
428 filter_extra_fields = True
429 pre_validators = [LdapLibValidator]
429 pre_validators = [LdapLibValidator]
430 ldap_active = StringBoolean(if_missing=False)
430 ldap_active = StringBoolean(if_missing=False)
431 ldap_host = UnicodeString(strip=True,)
431 ldap_host = UnicodeString(strip=True,)
432 ldap_port = Number(strip=True,)
432 ldap_port = Number(strip=True,)
433 ldap_ldaps = StringBoolean(if_missing=False)
433 ldap_ldaps = StringBoolean(if_missing=False)
434 ldap_dn_user = UnicodeString(strip=True,)
434 ldap_dn_user = UnicodeString(strip=True,)
435 ldap_dn_pass = UnicodeString(strip=True,)
435 ldap_dn_pass = UnicodeString(strip=True,)
436 ldap_base_dn = UnicodeString(strip=True,)
436 ldap_base_dn = UnicodeString(strip=True,)
437
437
438 return _LdapSettingsForm
438 return _LdapSettingsForm
@@ -1,221 +1,235 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # model for handling repositories actions
3 # model for handling repositories actions
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 # This program is free software; you can redistribute it and/or
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; version 2
7 # as published by the Free Software Foundation; version 2
8 # of the License or (at your opinion) any later version of the license.
8 # of the License or (at your opinion) any later version of the license.
9 #
9 #
10 # This program is distributed in the hope that it will be useful,
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
13 # GNU General Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # MA 02110-1301, USA.
18 # MA 02110-1301, USA.
19 """
19 """
20 Created on Jun 5, 2010
20 Created on Jun 5, 2010
21 model for handling repositories actions
21 model for handling repositories actions
22 :author: marcink
22 :author: marcink
23 """
23 """
24 from vcs.backends import get_repo, get_backend
24 from vcs.backends import get_repo, get_backend
25 from datetime import datetime
25 from datetime import datetime
26 from pylons import app_globals as g
26 from pylons import app_globals as g
27 from rhodecode.model.db import Repository, RepoToPerm, User, Permission, \
27 from rhodecode.model.db import Repository, RepoToPerm, User, Permission, \
28 Statistics
28 Statistics
29 from rhodecode.model.meta import Session
29 from rhodecode.model.meta import Session
30 from rhodecode.model.user import UserModel
30 from rhodecode.model.user import UserModel
31 from rhodecode.model.caching_query import FromCache
31 from rhodecode.model.caching_query import FromCache
32 import logging
32 import logging
33 import os
33 import os
34 import shutil
34 import shutil
35 import traceback
35 import traceback
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38 class RepoModel(object):
38 class RepoModel(object):
39
39
40 def __init__(self):
40 def __init__(self):
41 self.sa = Session()
41 self.sa = Session()
42
42
43 def get(self, repo_id, cache=False):
43 def get(self, repo_id, cache=False):
44 repo = self.sa.query(Repository)\
44 repo = self.sa.query(Repository)\
45 .filter(Repository.repo_name == repo_id)
45 .filter(Repository.repo_id == repo_id)
46
46
47 if cache:
47 if cache:
48 repo = repo.options(FromCache("sql_cache_short",
48 repo = repo.options(FromCache("sql_cache_short",
49 "get_repo_%s" % repo))
49 "get_repo_%s" % repo_id))
50 return repo.scalar()
51
52
53 def get_by_repo_name(self, repo_name, cache=False):
54 repo = self.sa.query(Repository)\
55 .filter(Repository.repo_name == repo_name)
56
57 if cache:
58 repo = repo.options(FromCache("sql_cache_short",
59 "get_repo_%s" % repo_name))
50 return repo.scalar()
60 return repo.scalar()
51
61
52 def get_users_js(self):
62 def get_users_js(self):
53
63
54 users = self.sa.query(User).filter(User.active == True).all()
64 users = self.sa.query(User).filter(User.active == True).all()
55 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
65 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
56 users_array = '[%s];' % '\n'.join([u_tmpl % (u.user_id, u.name,
66 users_array = '[%s];' % '\n'.join([u_tmpl % (u.user_id, u.name,
57 u.lastname, u.username)
67 u.lastname, u.username)
58 for u in users])
68 for u in users])
59 return users_array
69 return users_array
60
70
61
71
62 def update(self, repo_name, form_data):
72 def update(self, repo_name, form_data):
63 try:
73 try:
64
74
65 #update permissions
75 #update permissions
66 for username, perm in form_data['perms_updates']:
76 for username, perm in form_data['perms_updates']:
67 r2p = self.sa.query(RepoToPerm)\
77 r2p = self.sa.query(RepoToPerm)\
68 .filter(RepoToPerm.user == UserModel().get_by_username(username, cache=False))\
78 .filter(RepoToPerm.user == UserModel()\
69 .filter(RepoToPerm.repository == self.get(repo_name))\
79 .get_by_username(username, cache=False))\
80 .filter(RepoToPerm.repository == \
81 self.get_by_repo_name(repo_name))\
70 .one()
82 .one()
71
83
72 r2p.permission_id = self.sa.query(Permission).filter(
84 r2p.permission_id = self.sa.query(Permission).filter(
73 Permission.permission_name ==
85 Permission.permission_name ==
74 perm).one().permission_id
86 perm).one().permission_id
75 self.sa.add(r2p)
87 self.sa.add(r2p)
76
88
77 #set new permissions
89 #set new permissions
78 for username, perm in form_data['perms_new']:
90 for username, perm in form_data['perms_new']:
79 r2p = RepoToPerm()
91 r2p = RepoToPerm()
80 r2p.repository = self.get(repo_name)
92 r2p.repository = self.get_by_repo_name(repo_name)
81 r2p.user = UserModel().get_by_username(username, cache=False)
93 r2p.user = UserModel().get_by_username(username, cache=False)
82
94
83 r2p.permission_id = self.sa.query(Permission).filter(
95 r2p.permission_id = self.sa.query(Permission).filter(
84 Permission.permission_name == perm)\
96 Permission.permission_name == perm)\
85 .one().permission_id
97 .one().permission_id
86 self.sa.add(r2p)
98 self.sa.add(r2p)
87
99
88 #update current repo
100 #update current repo
89 cur_repo = self.get(repo_name, cache=False)
101 cur_repo = self.get_by_repo_name(repo_name, cache=False)
90
102
91 for k, v in form_data.items():
103 for k, v in form_data.items():
92 if k == 'user':
104 if k == 'user':
93 cur_repo.user_id = v
105 cur_repo.user_id = v
94 else:
106 else:
95 setattr(cur_repo, k, v)
107 setattr(cur_repo, k, v)
96
108
97 self.sa.add(cur_repo)
109 self.sa.add(cur_repo)
98
110
99 if repo_name != form_data['repo_name']:
111 if repo_name != form_data['repo_name']:
100 #rename our data
112 #rename our data
101 self.__rename_repo(repo_name, form_data['repo_name'])
113 self.__rename_repo(repo_name, form_data['repo_name'])
102
114
103 self.sa.commit()
115 self.sa.commit()
104 except:
116 except:
105 log.error(traceback.format_exc())
117 log.error(traceback.format_exc())
106 self.sa.rollback()
118 self.sa.rollback()
107 raise
119 raise
108
120
109 def create(self, form_data, cur_user, just_db=False, fork=False):
121 def create(self, form_data, cur_user, just_db=False, fork=False):
110 try:
122 try:
111 if fork:
123 if fork:
112 #force str since hg doesn't go with unicode
124 #force str since hg doesn't go with unicode
113 repo_name = str(form_data['fork_name'])
125 repo_name = str(form_data['fork_name'])
114 org_name = str(form_data['repo_name'])
126 org_name = str(form_data['repo_name'])
115
127
116 else:
128 else:
117 org_name = repo_name = str(form_data['repo_name'])
129 org_name = repo_name = str(form_data['repo_name'])
118 new_repo = Repository()
130 new_repo = Repository()
119 for k, v in form_data.items():
131 for k, v in form_data.items():
120 if k == 'repo_name':
132 if k == 'repo_name':
121 v = repo_name
133 v = repo_name
122 setattr(new_repo, k, v)
134 setattr(new_repo, k, v)
123
135
124 if fork:
136 if fork:
125 parent_repo = self.sa.query(Repository)\
137 parent_repo = self.sa.query(Repository)\
126 .filter(Repository.repo_name == org_name).scalar()
138 .filter(Repository.repo_name == org_name).scalar()
127 new_repo.fork = parent_repo
139 new_repo.fork = parent_repo
128
140
129 new_repo.user_id = cur_user.user_id
141 new_repo.user_id = cur_user.user_id
130 self.sa.add(new_repo)
142 self.sa.add(new_repo)
131
143
132 #create default permission
144 #create default permission
133 repo_to_perm = RepoToPerm()
145 repo_to_perm = RepoToPerm()
134 default = 'repository.read'
146 default = 'repository.read'
135 for p in UserModel().get_by_username('default', cache=False).user_perms:
147 for p in UserModel().get_by_username('default', cache=False).user_perms:
136 if p.permission.permission_name.startswith('repository.'):
148 if p.permission.permission_name.startswith('repository.'):
137 default = p.permission.permission_name
149 default = p.permission.permission_name
138 break
150 break
139
151
140 default_perm = 'repository.none' if form_data['private'] else default
152 default_perm = 'repository.none' if form_data['private'] else default
141
153
142 repo_to_perm.permission_id = self.sa.query(Permission)\
154 repo_to_perm.permission_id = self.sa.query(Permission)\
143 .filter(Permission.permission_name == default_perm)\
155 .filter(Permission.permission_name == default_perm)\
144 .one().permission_id
156 .one().permission_id
145
157
146 repo_to_perm.repository_id = new_repo.repo_id
158 repo_to_perm.repository_id = new_repo.repo_id
147 repo_to_perm.user_id = UserModel().get_by_username('default', cache=False).user_id
159 repo_to_perm.user_id = UserModel().get_by_username('default', cache=False).user_id
148
160
149 self.sa.add(repo_to_perm)
161 self.sa.add(repo_to_perm)
150 self.sa.commit()
162 self.sa.commit()
151 if not just_db:
163 if not just_db:
152 self.__create_repo(repo_name, form_data['repo_type'])
164 self.__create_repo(repo_name, form_data['repo_type'])
153 except:
165 except:
154 log.error(traceback.format_exc())
166 log.error(traceback.format_exc())
155 self.sa.rollback()
167 self.sa.rollback()
156 raise
168 raise
157
169
158 def create_fork(self, form_data, cur_user):
170 def create_fork(self, form_data, cur_user):
159 from rhodecode.lib.celerylib import tasks, run_task
171 from rhodecode.lib.celerylib import tasks, run_task
160 run_task(tasks.create_repo_fork, form_data, cur_user)
172 run_task(tasks.create_repo_fork, form_data, cur_user)
161
173
162 def delete(self, repo):
174 def delete(self, repo):
163 try:
175 try:
164 self.sa.delete(repo)
176 self.sa.delete(repo)
165 self.__delete_repo(repo)
177 self.__delete_repo(repo)
166 self.sa.commit()
178 self.sa.commit()
167 except:
179 except:
168 log.error(traceback.format_exc())
180 log.error(traceback.format_exc())
169 self.sa.rollback()
181 self.sa.rollback()
170 raise
182 raise
171
183
172 def delete_perm_user(self, form_data, repo_name):
184 def delete_perm_user(self, form_data, repo_name):
173 try:
185 try:
174 self.sa.query(RepoToPerm)\
186 self.sa.query(RepoToPerm)\
175 .filter(RepoToPerm.repository == self.get(repo_name))\
187 .filter(RepoToPerm.repository \
188 == self.get_by_repo_name(repo_name))\
176 .filter(RepoToPerm.user_id == form_data['user_id']).delete()
189 .filter(RepoToPerm.user_id == form_data['user_id']).delete()
177 self.sa.commit()
190 self.sa.commit()
178 except:
191 except:
179 log.error(traceback.format_exc())
192 log.error(traceback.format_exc())
180 self.sa.rollback()
193 self.sa.rollback()
181 raise
194 raise
182
195
183 def delete_stats(self, repo_name):
196 def delete_stats(self, repo_name):
184 try:
197 try:
185 self.sa.query(Statistics)\
198 self.sa.query(Statistics)\
186 .filter(Statistics.repository == self.get(repo_name)).delete()
199 .filter(Statistics.repository == \
200 self.get_by_repo_name(repo_name)).delete()
187 self.sa.commit()
201 self.sa.commit()
188 except:
202 except:
189 log.error(traceback.format_exc())
203 log.error(traceback.format_exc())
190 self.sa.rollback()
204 self.sa.rollback()
191 raise
205 raise
192
206
193
207
194 def __create_repo(self, repo_name, alias):
208 def __create_repo(self, repo_name, alias):
195 from rhodecode.lib.utils import check_repo
209 from rhodecode.lib.utils import check_repo
196 repo_path = os.path.join(g.base_path, repo_name)
210 repo_path = os.path.join(g.base_path, repo_name)
197 if check_repo(repo_name, g.base_path):
211 if check_repo(repo_name, g.base_path):
198 log.info('creating repo %s in %s', repo_name, repo_path)
212 log.info('creating repo %s in %s', repo_name, repo_path)
199 backend = get_backend(alias)
213 backend = get_backend(alias)
200 backend(repo_path, create=True)
214 backend(repo_path, create=True)
201
215
202 def __rename_repo(self, old, new):
216 def __rename_repo(self, old, new):
203 log.info('renaming repo from %s to %s', old, new)
217 log.info('renaming repo from %s to %s', old, new)
204
218
205 old_path = os.path.join(g.base_path, old)
219 old_path = os.path.join(g.base_path, old)
206 new_path = os.path.join(g.base_path, new)
220 new_path = os.path.join(g.base_path, new)
207 if os.path.isdir(new_path):
221 if os.path.isdir(new_path):
208 raise Exception('Was trying to rename to already existing dir %s',
222 raise Exception('Was trying to rename to already existing dir %s',
209 new_path)
223 new_path)
210 shutil.move(old_path, new_path)
224 shutil.move(old_path, new_path)
211
225
212 def __delete_repo(self, repo):
226 def __delete_repo(self, repo):
213 rm_path = os.path.join(g.base_path, repo.repo_name)
227 rm_path = os.path.join(g.base_path, repo.repo_name)
214 log.info("Removing %s", rm_path)
228 log.info("Removing %s", rm_path)
215 #disable hg/git
229 #disable hg/git
216 alias = repo.repo_type
230 alias = repo.repo_type
217 shutil.move(os.path.join(rm_path, '.%s' % alias),
231 shutil.move(os.path.join(rm_path, '.%s' % alias),
218 os.path.join(rm_path, 'rm__.%s' % alias))
232 os.path.join(rm_path, 'rm__.%s' % alias))
219 #disable repo
233 #disable repo
220 shutil.move(rm_path, os.path.join(g.base_path, 'rm__%s__%s' \
234 shutil.move(rm_path, os.path.join(g.base_path, 'rm__%s__%s' \
221 % (datetime.today(), repo.repo_name)))
235 % (datetime.today(), repo.repo_name)))
@@ -1,327 +1,343 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # Model for RhodeCode
3 # Model for RhodeCode
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on April 9, 2010
21 Created on April 9, 2010
22 Model for RhodeCode
22 Model for RhodeCode
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from beaker.cache import cache_region, region_invalidate
25 from beaker.cache import cache_region, region_invalidate
26 from mercurial import ui
26 from mercurial import ui
27 from rhodecode import BACKENDS
27 from rhodecode import BACKENDS
28 from rhodecode.lib import helpers as h
28 from rhodecode.lib import helpers as h
29 from rhodecode.lib.auth import HasRepoPermissionAny
29 from rhodecode.lib.auth import HasRepoPermissionAny
30 from rhodecode.lib.utils import get_repos, make_ui
30 from rhodecode.lib.utils import get_repos, make_ui, action_logger
31 from rhodecode.model import meta
31 from rhodecode.model import meta
32 from rhodecode.model.db import Repository, User, RhodeCodeUi, CacheInvalidation, \
32 from rhodecode.model.db import Repository, User, RhodeCodeUi, CacheInvalidation, \
33 UserFollowing
33 UserFollowing
34 from rhodecode.model.caching_query import FromCache
34 from rhodecode.model.caching_query import FromCache
35 from sqlalchemy.orm import joinedload
35 from sqlalchemy.orm import joinedload
36 from sqlalchemy.orm.session import make_transient
36 from sqlalchemy.orm.session import make_transient
37 from vcs import get_backend
37 from vcs import get_backend
38 from vcs.utils.helpers import get_scm
38 from vcs.utils.helpers import get_scm
39 from vcs.exceptions import RepositoryError, VCSError
39 from vcs.exceptions import RepositoryError, VCSError
40 from vcs.utils.lazy import LazyProperty
40 from vcs.utils.lazy import LazyProperty
41 import traceback
41 import traceback
42 import logging
42 import logging
43 import os
43 import os
44 import time
44 import time
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48 class UserTemp(object):
49 def __init__(self, user_id):
50 self.user_id = user_id
51
52 class RepoTemp(object):
53 def __init__(self, repo_id):
54 self.repo_id = repo_id
55
56
48 class ScmModel(object):
57 class ScmModel(object):
49 """
58 """
50 Mercurial Model
59 Mercurial Model
51 """
60 """
52
61
53 def __init__(self):
62 def __init__(self):
54 self.sa = meta.Session()
63 self.sa = meta.Session()
55
64
56 @LazyProperty
65 @LazyProperty
57 def repos_path(self):
66 def repos_path(self):
58 """
67 """
59 Get's the repositories root path from database
68 Get's the repositories root path from database
60 """
69 """
61 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
70 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
62
71
63 return q.ui_value
72 return q.ui_value
64
73
65 def repo_scan(self, repos_path, baseui, initial=False):
74 def repo_scan(self, repos_path, baseui, initial=False):
66 """
75 """
67 Listing of repositories in given path. This path should not be a
76 Listing of repositories in given path. This path should not be a
68 repository itself. Return a dictionary of repository objects
77 repository itself. Return a dictionary of repository objects
69
78
70 :param repos_path: path to directory containing repositories
79 :param repos_path: path to directory containing repositories
71 :param baseui
80 :param baseui
72 :param initial: initial scan
81 :param initial: initial scan
73 """
82 """
74 log.info('scanning for repositories in %s', repos_path)
83 log.info('scanning for repositories in %s', repos_path)
75
84
76 if not isinstance(baseui, ui.ui):
85 if not isinstance(baseui, ui.ui):
77 baseui = make_ui('db')
86 baseui = make_ui('db')
78 repos_list = {}
87 repos_list = {}
79
88
80 for name, path in get_repos(repos_path):
89 for name, path in get_repos(repos_path):
81 try:
90 try:
82 if repos_list.has_key(name):
91 if repos_list.has_key(name):
83 raise RepositoryError('Duplicate repository name %s '
92 raise RepositoryError('Duplicate repository name %s '
84 'found in %s' % (name, path))
93 'found in %s' % (name, path))
85 else:
94 else:
86
95
87 klass = get_backend(path[0])
96 klass = get_backend(path[0])
88
97
89 if path[0] == 'hg' and path[0] in BACKENDS.keys():
98 if path[0] == 'hg' and path[0] in BACKENDS.keys():
90 repos_list[name] = klass(path[1], baseui=baseui)
99 repos_list[name] = klass(path[1], baseui=baseui)
91
100
92 if path[0] == 'git' and path[0] in BACKENDS.keys():
101 if path[0] == 'git' and path[0] in BACKENDS.keys():
93 repos_list[name] = klass(path[1])
102 repos_list[name] = klass(path[1])
94 except OSError:
103 except OSError:
95 continue
104 continue
96
105
97 return repos_list
106 return repos_list
98
107
99 def get_repos(self, all_repos=None):
108 def get_repos(self, all_repos=None):
100 """
109 """
101 Get all repos from db and for each repo create it's backend instance.
110 Get all repos from db and for each repo create it's backend instance.
102 and fill that backed with information from database
111 and fill that backed with information from database
103
112
104 :param all_repos: give specific repositories list, good for filtering
113 :param all_repos: give specific repositories list, good for filtering
105 """
114 """
106 if not all_repos:
115 if not all_repos:
107 all_repos = self.sa.query(Repository)\
116 all_repos = self.sa.query(Repository)\
108 .order_by(Repository.repo_name).all()
117 .order_by(Repository.repo_name).all()
109
118
110 invalidation_list = [str(x.cache_key) for x in \
119 invalidation_list = [str(x.cache_key) for x in \
111 self.sa.query(CacheInvalidation.cache_key)\
120 self.sa.query(CacheInvalidation.cache_key)\
112 .filter(CacheInvalidation.cache_active == False)\
121 .filter(CacheInvalidation.cache_active == False)\
113 .all()]
122 .all()]
114
123
115 for r in all_repos:
124 for r in all_repos:
116
125
117 repo = self.get(r.repo_name, invalidation_list)
126 repo = self.get(r.repo_name, invalidation_list)
118
127
119 if repo is not None:
128 if repo is not None:
120 last_change = repo.last_change
129 last_change = repo.last_change
121 tip = h.get_changeset_safe(repo, 'tip')
130 tip = h.get_changeset_safe(repo, 'tip')
122
131
123 tmp_d = {}
132 tmp_d = {}
124 tmp_d['name'] = repo.name
133 tmp_d['name'] = repo.name
125 tmp_d['name_sort'] = tmp_d['name'].lower()
134 tmp_d['name_sort'] = tmp_d['name'].lower()
126 tmp_d['description'] = repo.dbrepo.description
135 tmp_d['description'] = repo.dbrepo.description
127 tmp_d['description_sort'] = tmp_d['description']
136 tmp_d['description_sort'] = tmp_d['description']
128 tmp_d['last_change'] = last_change
137 tmp_d['last_change'] = last_change
129 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
138 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
130 tmp_d['tip'] = tip.raw_id
139 tmp_d['tip'] = tip.raw_id
131 tmp_d['tip_sort'] = tip.revision
140 tmp_d['tip_sort'] = tip.revision
132 tmp_d['rev'] = tip.revision
141 tmp_d['rev'] = tip.revision
133 tmp_d['contact'] = repo.dbrepo.user.full_contact
142 tmp_d['contact'] = repo.dbrepo.user.full_contact
134 tmp_d['contact_sort'] = tmp_d['contact']
143 tmp_d['contact_sort'] = tmp_d['contact']
135 tmp_d['repo_archives'] = list(repo._get_archives())
144 tmp_d['repo_archives'] = list(repo._get_archives())
136 tmp_d['last_msg'] = tip.message
145 tmp_d['last_msg'] = tip.message
137 tmp_d['repo'] = repo
146 tmp_d['repo'] = repo
138 yield tmp_d
147 yield tmp_d
139
148
140 def get_repo(self, repo_name):
149 def get_repo(self, repo_name):
141 return self.get(repo_name)
150 return self.get(repo_name)
142
151
143 def get(self, repo_name, invalidation_list=None):
152 def get(self, repo_name, invalidation_list=None):
144 """
153 """
145 Get's repository from given name, creates BackendInstance and
154 Get's repository from given name, creates BackendInstance and
146 propagates it's data from database with all additional information
155 propagates it's data from database with all additional information
147 :param repo_name:
156 :param repo_name:
148 """
157 """
149 if not HasRepoPermissionAny('repository.read', 'repository.write',
158 if not HasRepoPermissionAny('repository.read', 'repository.write',
150 'repository.admin')(repo_name, 'get repo check'):
159 'repository.admin')(repo_name, 'get repo check'):
151 return
160 return
152
161
153 @cache_region('long_term')
162 @cache_region('long_term')
154 def _get_repo(repo_name):
163 def _get_repo(repo_name):
155
164
156 repo_path = os.path.join(self.repos_path, repo_name)
165 repo_path = os.path.join(self.repos_path, repo_name)
157 alias = get_scm(repo_path)[0]
166 alias = get_scm(repo_path)[0]
158
167
159 log.debug('Creating instance of %s repository', alias)
168 log.debug('Creating instance of %s repository', alias)
160 backend = get_backend(alias)
169 backend = get_backend(alias)
161
170
162 #TODO: get the baseui from somewhere for this
171 #TODO: get the baseui from somewhere for this
163 if alias == 'hg':
172 if alias == 'hg':
164 from pylons import app_globals as g
173 from pylons import app_globals as g
165 repo = backend(repo_path, create=False, baseui=g.baseui)
174 repo = backend(repo_path, create=False, baseui=g.baseui)
166 #skip hidden web repository
175 #skip hidden web repository
167 if repo._get_hidden():
176 if repo._get_hidden():
168 return
177 return
169 else:
178 else:
170 repo = backend(repo_path, create=False)
179 repo = backend(repo_path, create=False)
171
180
172 dbrepo = self.sa.query(Repository)\
181 dbrepo = self.sa.query(Repository)\
173 .options(joinedload(Repository.fork))\
182 .options(joinedload(Repository.fork))\
174 .options(joinedload(Repository.user))\
183 .options(joinedload(Repository.user))\
175 .filter(Repository.repo_name == repo_name)\
184 .filter(Repository.repo_name == repo_name)\
176 .scalar()
185 .scalar()
177 make_transient(dbrepo)
186 make_transient(dbrepo)
178 repo.dbrepo = dbrepo
187 repo.dbrepo = dbrepo
179 return repo
188 return repo
180
189
181 pre_invalidate = True
190 pre_invalidate = True
182 if invalidation_list:
191 if invalidation_list:
183 pre_invalidate = repo_name in invalidation_list
192 pre_invalidate = repo_name in invalidation_list
184
193
185 if pre_invalidate:
194 if pre_invalidate:
186 invalidate = self._should_invalidate(repo_name)
195 invalidate = self._should_invalidate(repo_name)
187
196
188 if invalidate:
197 if invalidate:
189 log.info('invalidating cache for repository %s', repo_name)
198 log.info('invalidating cache for repository %s', repo_name)
190 region_invalidate(_get_repo, None, repo_name)
199 region_invalidate(_get_repo, None, repo_name)
191 self._mark_invalidated(invalidate)
200 self._mark_invalidated(invalidate)
192
201
193 return _get_repo(repo_name)
202 return _get_repo(repo_name)
194
203
195
204
196
205
197 def mark_for_invalidation(self, repo_name):
206 def mark_for_invalidation(self, repo_name):
198 """
207 """
199 Puts cache invalidation task into db for
208 Puts cache invalidation task into db for
200 further global cache invalidation
209 further global cache invalidation
201
210
202 :param repo_name: this repo that should invalidation take place
211 :param repo_name: this repo that should invalidation take place
203 """
212 """
204 log.debug('marking %s for invalidation', repo_name)
213 log.debug('marking %s for invalidation', repo_name)
205 cache = self.sa.query(CacheInvalidation)\
214 cache = self.sa.query(CacheInvalidation)\
206 .filter(CacheInvalidation.cache_key == repo_name).scalar()
215 .filter(CacheInvalidation.cache_key == repo_name).scalar()
207
216
208 if cache:
217 if cache:
209 #mark this cache as inactive
218 #mark this cache as inactive
210 cache.cache_active = False
219 cache.cache_active = False
211 else:
220 else:
212 log.debug('cache key not found in invalidation db -> creating one')
221 log.debug('cache key not found in invalidation db -> creating one')
213 cache = CacheInvalidation(repo_name)
222 cache = CacheInvalidation(repo_name)
214
223
215 try:
224 try:
216 self.sa.add(cache)
225 self.sa.add(cache)
217 self.sa.commit()
226 self.sa.commit()
218 except:
227 except:
219 log.error(traceback.format_exc())
228 log.error(traceback.format_exc())
220 self.sa.rollback()
229 self.sa.rollback()
221
230
222
231
223 def toggle_following_repo(self, follow_repo_id, user_id):
232 def toggle_following_repo(self, follow_repo_id, user_id):
224
233
225 f = self.sa.query(UserFollowing)\
234 f = self.sa.query(UserFollowing)\
226 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
235 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
227 .filter(UserFollowing.user_id == user_id).scalar()
236 .filter(UserFollowing.user_id == user_id).scalar()
228
237
229 if f is not None:
238 if f is not None:
239
230 try:
240 try:
231 self.sa.delete(f)
241 self.sa.delete(f)
232 self.sa.commit()
242 self.sa.commit()
243 action_logger(UserTemp(user_id),
244 'stopped_following_repo',
245 RepoTemp(follow_repo_id))
233 return
246 return
234 except:
247 except:
235 log.error(traceback.format_exc())
248 log.error(traceback.format_exc())
236 self.sa.rollback()
249 self.sa.rollback()
237 raise
250 raise
238
251
239
252
240 try:
253 try:
241 f = UserFollowing()
254 f = UserFollowing()
242 f.user_id = user_id
255 f.user_id = user_id
243 f.follows_repo_id = follow_repo_id
256 f.follows_repo_id = follow_repo_id
244 self.sa.add(f)
257 self.sa.add(f)
245 self.sa.commit()
258 self.sa.commit()
259 action_logger(UserTemp(user_id),
260 'started_following_repo',
261 RepoTemp(follow_repo_id))
246 except:
262 except:
247 log.error(traceback.format_exc())
263 log.error(traceback.format_exc())
248 self.sa.rollback()
264 self.sa.rollback()
249 raise
265 raise
250
266
251 def toggle_following_user(self, follow_user_id , user_id):
267 def toggle_following_user(self, follow_user_id , user_id):
252 f = self.sa.query(UserFollowing)\
268 f = self.sa.query(UserFollowing)\
253 .filter(UserFollowing.follows_user_id == follow_user_id)\
269 .filter(UserFollowing.follows_user_id == follow_user_id)\
254 .filter(UserFollowing.user_id == user_id).scalar()
270 .filter(UserFollowing.user_id == user_id).scalar()
255
271
256 if f is not None:
272 if f is not None:
257 try:
273 try:
258 self.sa.delete(f)
274 self.sa.delete(f)
259 self.sa.commit()
275 self.sa.commit()
260 return
276 return
261 except:
277 except:
262 log.error(traceback.format_exc())
278 log.error(traceback.format_exc())
263 self.sa.rollback()
279 self.sa.rollback()
264 raise
280 raise
265
281
266 try:
282 try:
267 f = UserFollowing()
283 f = UserFollowing()
268 f.user_id = user_id
284 f.user_id = user_id
269 f.follows_user_id = follow_user_id
285 f.follows_user_id = follow_user_id
270 self.sa.add(f)
286 self.sa.add(f)
271 self.sa.commit()
287 self.sa.commit()
272 except:
288 except:
273 log.error(traceback.format_exc())
289 log.error(traceback.format_exc())
274 self.sa.rollback()
290 self.sa.rollback()
275 raise
291 raise
276
292
277 def is_following_repo(self, repo_name, user_id):
293 def is_following_repo(self, repo_name, user_id):
278 r = self.sa.query(Repository)\
294 r = self.sa.query(Repository)\
279 .filter(Repository.repo_name == repo_name).scalar()
295 .filter(Repository.repo_name == repo_name).scalar()
280
296
281 f = self.sa.query(UserFollowing)\
297 f = self.sa.query(UserFollowing)\
282 .filter(UserFollowing.follows_repository == r)\
298 .filter(UserFollowing.follows_repository == r)\
283 .filter(UserFollowing.user_id == user_id).scalar()
299 .filter(UserFollowing.user_id == user_id).scalar()
284
300
285 return f is not None
301 return f is not None
286
302
287 def is_following_user(self, username, user_id):
303 def is_following_user(self, username, user_id):
288 u = self.sa.query(User)\
304 u = self.sa.query(User)\
289 .filter(User.username == username).scalar()
305 .filter(User.username == username).scalar()
290
306
291 f = self.sa.query(UserFollowing)\
307 f = self.sa.query(UserFollowing)\
292 .filter(UserFollowing.follows_user == u)\
308 .filter(UserFollowing.follows_user == u)\
293 .filter(UserFollowing.user_id == user_id).scalar()
309 .filter(UserFollowing.user_id == user_id).scalar()
294
310
295 return f is not None
311 return f is not None
296
312
297
313
298 def _should_invalidate(self, repo_name):
314 def _should_invalidate(self, repo_name):
299 """
315 """
300 Looks up database for invalidation signals for this repo_name
316 Looks up database for invalidation signals for this repo_name
301 :param repo_name:
317 :param repo_name:
302 """
318 """
303
319
304 ret = self.sa.query(CacheInvalidation)\
320 ret = self.sa.query(CacheInvalidation)\
305 .options(FromCache('sql_cache_short',
321 .options(FromCache('sql_cache_short',
306 'get_invalidation_%s' % repo_name))\
322 'get_invalidation_%s' % repo_name))\
307 .filter(CacheInvalidation.cache_key == repo_name)\
323 .filter(CacheInvalidation.cache_key == repo_name)\
308 .filter(CacheInvalidation.cache_active == False)\
324 .filter(CacheInvalidation.cache_active == False)\
309 .scalar()
325 .scalar()
310
326
311 return ret
327 return ret
312
328
313 def _mark_invalidated(self, cache_key):
329 def _mark_invalidated(self, cache_key):
314 """
330 """
315 Marks all occurences of cache to invaldation as already invalidated
331 Marks all occurences of cache to invaldation as already invalidated
316 @param repo_name:
332 @param repo_name:
317 """
333 """
318 if cache_key:
334 if cache_key:
319 log.debug('marking %s as already invalidated', cache_key)
335 log.debug('marking %s as already invalidated', cache_key)
320 try:
336 try:
321 cache_key.cache_active = True
337 cache_key.cache_active = True
322 self.sa.add(cache_key)
338 self.sa.add(cache_key)
323 self.sa.commit()
339 self.sa.commit()
324 except:
340 except:
325 log.error(traceback.format_exc())
341 log.error(traceback.format_exc())
326 self.sa.rollback()
342 self.sa.rollback()
327
343
General Comments 0
You need to be logged in to leave comments. Login now