##// END OF EJS Templates
Changes for repo groups
marcink -
r1159:187a924e beta
parent child Browse files
Show More
@@ -1,410 +1,422 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.repos
3 rhodecode.controllers.admin.repos
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Admin controller for RhodeCode
6 Admin controller for RhodeCode
7
7
8 :created_on: Apr 7, 2010
8 :created_on: Apr 7, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import logging
28 import logging
29 import traceback
29 import traceback
30 import formencode
30 import formencode
31 from operator import itemgetter
31 from operator import itemgetter
32 from formencode import htmlfill
32 from formencode import htmlfill
33
33
34 from paste.httpexceptions import HTTPInternalServerError
34 from paste.httpexceptions import HTTPInternalServerError
35 from pylons import request, response, session, tmpl_context as c, url
35 from pylons import request, response, session, tmpl_context as c, url
36 from pylons.controllers.util import abort, redirect
36 from pylons.controllers.util import abort, redirect
37 from pylons.i18n.translation import _
37 from pylons.i18n.translation import _
38
38
39 from rhodecode.lib import helpers as h
39 from rhodecode.lib import helpers as h
40 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
41 HasPermissionAnyDecorator
41 HasPermissionAnyDecorator
42 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
43 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
44 from rhodecode.lib.helpers import get_token
44 from rhodecode.lib.helpers import get_token
45 from rhodecode.model.db import User, Repository, UserFollowing, Group
45 from rhodecode.model.db import User, Repository, UserFollowing, Group
46 from rhodecode.model.forms import RepoForm
46 from rhodecode.model.forms import RepoForm
47 from rhodecode.model.scm import ScmModel
47 from rhodecode.model.scm import ScmModel
48 from rhodecode.model.repo import RepoModel
48 from rhodecode.model.repo import RepoModel
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52 class ReposController(BaseController):
52 class ReposController(BaseController):
53 """
53 """
54 REST Controller styled on the Atom Publishing Protocol"""
54 REST Controller styled on the Atom Publishing Protocol"""
55 # To properly map this controller, ensure your config/routing.py
55 # To properly map this controller, ensure your config/routing.py
56 # file has a resource setup:
56 # file has a resource setup:
57 # map.resource('repo', 'repos')
57 # map.resource('repo', 'repos')
58
58
59 @LoginRequired()
59 @LoginRequired()
60 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
60 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
61 def __before__(self):
61 def __before__(self):
62 c.admin_user = session.get('admin_user')
62 c.admin_user = session.get('admin_user')
63 c.admin_username = session.get('admin_username')
63 c.admin_username = session.get('admin_username')
64 super(ReposController, self).__before__()
64 super(ReposController, self).__before__()
65
65
66
66 def __load_defaults(self):
67 repo_model = RepoModel()
67
68
68 def __load_data(self, repo_name):
69 c.repo_groups = [('', '')]
70 parents_link = lambda k:h.literal('&raquo;'.join(
71 map(lambda k:k.group_name,
72 k.parents + [k])
73 )
74 )
75
76 c.repo_groups.extend([(x.group_id, parents_link(x)) for \
77 x in self.sa.query(Group).all()])
78 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
79 c.users_array = repo_model.get_users_js()
80 c.users_groups_array = repo_model.get_users_groups_js()
81
82 def __load_data(self, repo_name=None):
69 """
83 """
70 Load defaults settings for edit, and update
84 Load defaults settings for edit, and update
71
85
72 :param repo_name:
86 :param repo_name:
73 """
87 """
88 self.__load_defaults()
89
74 repo, dbrepo = ScmModel().get(repo_name, retval='repo')
90 repo, dbrepo = ScmModel().get(repo_name, retval='repo')
75
91
76 repo_model = RepoModel()
92 repo_model = RepoModel()
77 c.repo_info = repo_model.get_by_repo_name(repo_name)
93 c.repo_info = repo_model.get_by_repo_name(repo_name)
78
94
95
79 if c.repo_info is None:
96 if c.repo_info is None:
80 h.flash(_('%s repository is not mapped to db perhaps'
97 h.flash(_('%s repository is not mapped to db perhaps'
81 ' it was created or renamed from the filesystem'
98 ' it was created or renamed from the filesystem'
82 ' please run the application again'
99 ' please run the application again'
83 ' in order to rescan repositories') % repo_name,
100 ' in order to rescan repositories') % repo_name,
84 category='error')
101 category='error')
85
102
86 return redirect(url('repos'))
103 return redirect(url('repos'))
87
104
88
105
89
90 c.repo_groups = [('', '')]
91 c.repo_groups.extend([(x.group_id, x.group_name) for x in self.sa.query(Group).all()])
92
93 c.default_user_id = User.by_username('default').user_id
106 c.default_user_id = User.by_username('default').user_id
94 c.in_public_journal = self.sa.query(UserFollowing)\
107 c.in_public_journal = self.sa.query(UserFollowing)\
95 .filter(UserFollowing.user_id == c.default_user_id)\
108 .filter(UserFollowing.user_id == c.default_user_id)\
96 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
109 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
97
110
98 if c.repo_info.stats:
111 if c.repo_info.stats:
99 last_rev = c.repo_info.stats.stat_on_revision
112 last_rev = c.repo_info.stats.stat_on_revision
100 else:
113 else:
101 last_rev = 0
114 last_rev = 0
102 c.stats_revision = last_rev
115 c.stats_revision = last_rev
103
116
104 c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
117 c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
105
118
106 if last_rev == 0 or c.repo_last_rev == 0:
119 if last_rev == 0 or c.repo_last_rev == 0:
107 c.stats_percentage = 0
120 c.stats_percentage = 0
108 else:
121 else:
109 c.stats_percentage = '%.2f' % ((float((last_rev)) /
122 c.stats_percentage = '%.2f' % ((float((last_rev)) /
110 c.repo_last_rev) * 100)
123 c.repo_last_rev) * 100)
111
124
112 c.users_array = repo_model.get_users_js()
125
113 c.users_groups_array = repo_model.get_users_groups_js()
114
126
115 defaults = c.repo_info.get_dict()
127 defaults = c.repo_info.get_dict()
116 group, repo_name = c.repo_info.groups_and_repo
128 group, repo_name = c.repo_info.groups_and_repo
117 defaults['repo_name'] = repo_name
129 defaults['repo_name'] = repo_name
118 defaults['repo_group'] = getattr(group, 'group_id', None)
130 defaults['repo_group'] = getattr(group[-1], 'group_id', None)
131
119 #fill owner
132 #fill owner
120 if c.repo_info.user:
133 if c.repo_info.user:
121 defaults.update({'user':c.repo_info.user.username})
134 defaults.update({'user':c.repo_info.user.username})
122 else:
135 else:
123 replacement_user = self.sa.query(User)\
136 replacement_user = self.sa.query(User)\
124 .filter(User.admin == True).first().username
137 .filter(User.admin == True).first().username
125 defaults.update({'user':replacement_user})
138 defaults.update({'user':replacement_user})
126
139
127
140
128 #fill repository users
141 #fill repository users
129 for p in c.repo_info.repo_to_perm:
142 for p in c.repo_info.repo_to_perm:
130 defaults.update({'u_perm_%s' % p.user.username:
143 defaults.update({'u_perm_%s' % p.user.username:
131 p.permission.permission_name})
144 p.permission.permission_name})
132
145
133 #fill repository groups
146 #fill repository groups
134 for p in c.repo_info.users_group_to_perm:
147 for p in c.repo_info.users_group_to_perm:
135 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
148 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
136 p.permission.permission_name})
149 p.permission.permission_name})
137
150
138
151
139 return defaults
152 return defaults
140
153
141
154
142 @HasPermissionAllDecorator('hg.admin')
155 @HasPermissionAllDecorator('hg.admin')
143 def index(self, format='html'):
156 def index(self, format='html'):
144 """GET /repos: All items in the collection"""
157 """GET /repos: All items in the collection"""
145 # url('repos')
158 # url('repos')
146 cached_repo_list = ScmModel().get_repos()
159 cached_repo_list = ScmModel().get_repos()
147 c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort'))
160 c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort'))
148 return render('admin/repos/repos.html')
161 return render('admin/repos/repos.html')
149
162
150 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
163 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
151 def create(self):
164 def create(self):
152 """
165 """
153 POST /repos: Create a new item"""
166 POST /repos: Create a new item"""
154 # url('repos')
167 # url('repos')
155 repo_model = RepoModel()
168 repo_model = RepoModel()
156 c.repo_groups = [('', '')]
169 self.__load_defaults()
157 c.repo_groups.extend([(x.group_id, x.group_name) for x in self.sa.query(Group).all()])
158 form_result = {}
170 form_result = {}
159 try:
171 try:
160 form_result = RepoForm()(repo_groups=c.repo_groups).to_python(dict(request.POST))
172 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
173 .to_python(dict(request.POST))
161 repo_model.create(form_result, self.rhodecode_user)
174 repo_model.create(form_result, self.rhodecode_user)
162 if form_result['clone_uri']:
175 if form_result['clone_uri']:
163 h.flash(_('created repository %s from %s') \
176 h.flash(_('created repository %s from %s') \
164 % (form_result['repo_name'], form_result['clone_uri']),
177 % (form_result['repo_name'], form_result['clone_uri']),
165 category='success')
178 category='success')
166 else:
179 else:
167 h.flash(_('created repository %s') % form_result['repo_name'],
180 h.flash(_('created repository %s') % form_result['repo_name'],
168 category='success')
181 category='success')
169
182
170 if request.POST.get('user_created'):
183 if request.POST.get('user_created'):
171 action_logger(self.rhodecode_user, 'user_created_repo',
184 action_logger(self.rhodecode_user, 'user_created_repo',
172 form_result['repo_name'], '', self.sa)
185 form_result['repo_name'], '', self.sa)
173 else:
186 else:
174 action_logger(self.rhodecode_user, 'admin_created_repo',
187 action_logger(self.rhodecode_user, 'admin_created_repo',
175 form_result['repo_name'], '', self.sa)
188 form_result['repo_name'], '', self.sa)
176
189
177 except formencode.Invalid, errors:
190 except formencode.Invalid, errors:
178
191
179 c.new_repo = errors.value['repo_name']
192 c.new_repo = errors.value['repo_name']
180 c.repo_groups = [('', '')]
181 c.repo_groups.extend([(x.group_id, x.group_name) for x in self.sa.query(Group).all()])
182
193
183 if request.POST.get('user_created'):
194 if request.POST.get('user_created'):
184 r = render('admin/repos/repo_add_create_repository.html')
195 r = render('admin/repos/repo_add_create_repository.html')
185 else:
196 else:
186 r = render('admin/repos/repo_add.html')
197 r = render('admin/repos/repo_add.html')
187
198
188 return htmlfill.render(
199 return htmlfill.render(
189 r,
200 r,
190 defaults=errors.value,
201 defaults=errors.value,
191 errors=errors.error_dict or {},
202 errors=errors.error_dict or {},
192 prefix_error=False,
203 prefix_error=False,
193 encoding="UTF-8")
204 encoding="UTF-8")
194
205
195 except Exception:
206 except Exception:
196 log.error(traceback.format_exc())
207 log.error(traceback.format_exc())
197 msg = _('error occurred during creation of repository %s') \
208 msg = _('error occurred during creation of repository %s') \
198 % form_result.get('repo_name')
209 % form_result.get('repo_name')
199 h.flash(msg, category='error')
210 h.flash(msg, category='error')
200 if request.POST.get('user_created'):
211 if request.POST.get('user_created'):
201 return redirect(url('home'))
212 return redirect(url('home'))
202 return redirect(url('repos'))
213 return redirect(url('repos'))
203
214
204 @HasPermissionAllDecorator('hg.admin')
215 @HasPermissionAllDecorator('hg.admin')
205 def new(self, format='html'):
216 def new(self, format='html'):
206 """GET /repos/new: Form to create a new item"""
217 """GET /repos/new: Form to create a new item"""
207 new_repo = request.GET.get('repo', '')
218 new_repo = request.GET.get('repo', '')
208 c.new_repo = repo_name_slug(new_repo)
219 c.new_repo = repo_name_slug(new_repo)
209 c.repo_groups = [('', '')]
220 self.__load_defaults()
210 c.repo_groups.extend([(x.group_id, x.group_name) for x in self.sa.query(Group).all()])
211 return render('admin/repos/repo_add.html')
221 return render('admin/repos/repo_add.html')
212
222
213 @HasPermissionAllDecorator('hg.admin')
223 @HasPermissionAllDecorator('hg.admin')
214 def update(self, repo_name):
224 def update(self, repo_name):
215 """
225 """
216 PUT /repos/repo_name: Update an existing item"""
226 PUT /repos/repo_name: Update an existing item"""
217 # Forms posted to this method should contain a hidden field:
227 # Forms posted to this method should contain a hidden field:
218 # <input type="hidden" name="_method" value="PUT" />
228 # <input type="hidden" name="_method" value="PUT" />
219 # Or using helpers:
229 # Or using helpers:
220 # h.form(url('repo', repo_name=ID),
230 # h.form(url('repo', repo_name=ID),
221 # method='put')
231 # method='put')
222 # url('repo', repo_name=ID)
232 # url('repo', repo_name=ID)
233 self.__load_defaults()
223 repo_model = RepoModel()
234 repo_model = RepoModel()
224 changed_name = repo_name
235 changed_name = repo_name
225 _form = RepoForm(edit=True, old_data={'repo_name':repo_name})()
236 _form = RepoForm(edit=True, old_data={'repo_name':repo_name},
237 repo_groups=c.repo_groups_choices)()
226 try:
238 try:
227 form_result = _form.to_python(dict(request.POST))
239 form_result = _form.to_python(dict(request.POST))
228 repo_model.update(repo_name, form_result)
240 repo_model.update(repo_name, form_result)
229 invalidate_cache('get_repo_cached_%s' % repo_name)
241 invalidate_cache('get_repo_cached_%s' % repo_name)
230 h.flash(_('Repository %s updated successfully' % repo_name),
242 h.flash(_('Repository %s updated successfully' % repo_name),
231 category='success')
243 category='success')
232 changed_name = form_result['repo_name']
244 changed_name = form_result['repo_name']
233 action_logger(self.rhodecode_user, 'admin_updated_repo',
245 action_logger(self.rhodecode_user, 'admin_updated_repo',
234 changed_name, '', self.sa)
246 changed_name, '', self.sa)
235
247
236 except formencode.Invalid, errors:
248 except formencode.Invalid, errors:
237 defaults = self.__load_data(repo_name)
249 defaults = self.__load_data(repo_name)
238 defaults.update(errors.value)
250 defaults.update(errors.value)
239 return htmlfill.render(
251 return htmlfill.render(
240 render('admin/repos/repo_edit.html'),
252 render('admin/repos/repo_edit.html'),
241 defaults=defaults,
253 defaults=defaults,
242 errors=errors.error_dict or {},
254 errors=errors.error_dict or {},
243 prefix_error=False,
255 prefix_error=False,
244 encoding="UTF-8")
256 encoding="UTF-8")
245
257
246 except Exception:
258 except Exception:
247 log.error(traceback.format_exc())
259 log.error(traceback.format_exc())
248 h.flash(_('error occurred during update of repository %s') \
260 h.flash(_('error occurred during update of repository %s') \
249 % repo_name, category='error')
261 % repo_name, category='error')
250 return redirect(url('edit_repo', repo_name=changed_name))
262 return redirect(url('edit_repo', repo_name=changed_name))
251
263
252 @HasPermissionAllDecorator('hg.admin')
264 @HasPermissionAllDecorator('hg.admin')
253 def delete(self, repo_name):
265 def delete(self, repo_name):
254 """
266 """
255 DELETE /repos/repo_name: Delete an existing item"""
267 DELETE /repos/repo_name: Delete an existing item"""
256 # Forms posted to this method should contain a hidden field:
268 # Forms posted to this method should contain a hidden field:
257 # <input type="hidden" name="_method" value="DELETE" />
269 # <input type="hidden" name="_method" value="DELETE" />
258 # Or using helpers:
270 # Or using helpers:
259 # h.form(url('repo', repo_name=ID),
271 # h.form(url('repo', repo_name=ID),
260 # method='delete')
272 # method='delete')
261 # url('repo', repo_name=ID)
273 # url('repo', repo_name=ID)
262
274
263 repo_model = RepoModel()
275 repo_model = RepoModel()
264 repo = repo_model.get_by_repo_name(repo_name)
276 repo = repo_model.get_by_repo_name(repo_name)
265 if not repo:
277 if not repo:
266 h.flash(_('%s repository is not mapped to db perhaps'
278 h.flash(_('%s repository is not mapped to db perhaps'
267 ' it was moved or renamed from the filesystem'
279 ' it was moved or renamed from the filesystem'
268 ' please run the application again'
280 ' please run the application again'
269 ' in order to rescan repositories') % repo_name,
281 ' in order to rescan repositories') % repo_name,
270 category='error')
282 category='error')
271
283
272 return redirect(url('repos'))
284 return redirect(url('repos'))
273 try:
285 try:
274 action_logger(self.rhodecode_user, 'admin_deleted_repo',
286 action_logger(self.rhodecode_user, 'admin_deleted_repo',
275 repo_name, '', self.sa)
287 repo_name, '', self.sa)
276 repo_model.delete(repo)
288 repo_model.delete(repo)
277 invalidate_cache('get_repo_cached_%s' % repo_name)
289 invalidate_cache('get_repo_cached_%s' % repo_name)
278 h.flash(_('deleted repository %s') % repo_name, category='success')
290 h.flash(_('deleted repository %s') % repo_name, category='success')
279
291
280 except Exception, e:
292 except Exception, e:
281 log.error(traceback.format_exc())
293 log.error(traceback.format_exc())
282 h.flash(_('An error occurred during deletion of %s') % repo_name,
294 h.flash(_('An error occurred during deletion of %s') % repo_name,
283 category='error')
295 category='error')
284
296
285 return redirect(url('repos'))
297 return redirect(url('repos'))
286
298
287 @HasPermissionAllDecorator('hg.admin')
299 @HasPermissionAllDecorator('hg.admin')
288 def delete_perm_user(self, repo_name):
300 def delete_perm_user(self, repo_name):
289 """
301 """
290 DELETE an existing repository permission user
302 DELETE an existing repository permission user
291
303
292 :param repo_name:
304 :param repo_name:
293 """
305 """
294
306
295 try:
307 try:
296 repo_model = RepoModel()
308 repo_model = RepoModel()
297 repo_model.delete_perm_user(request.POST, repo_name)
309 repo_model.delete_perm_user(request.POST, repo_name)
298 except Exception, e:
310 except Exception, e:
299 h.flash(_('An error occurred during deletion of repository user'),
311 h.flash(_('An error occurred during deletion of repository user'),
300 category='error')
312 category='error')
301 raise HTTPInternalServerError()
313 raise HTTPInternalServerError()
302
314
303 @HasPermissionAllDecorator('hg.admin')
315 @HasPermissionAllDecorator('hg.admin')
304 def delete_perm_users_group(self, repo_name):
316 def delete_perm_users_group(self, repo_name):
305 """
317 """
306 DELETE an existing repository permission users group
318 DELETE an existing repository permission users group
307
319
308 :param repo_name:
320 :param repo_name:
309 """
321 """
310 try:
322 try:
311 repo_model = RepoModel()
323 repo_model = RepoModel()
312 repo_model.delete_perm_users_group(request.POST, repo_name)
324 repo_model.delete_perm_users_group(request.POST, repo_name)
313 except Exception, e:
325 except Exception, e:
314 h.flash(_('An error occurred during deletion of repository'
326 h.flash(_('An error occurred during deletion of repository'
315 ' users groups'),
327 ' users groups'),
316 category='error')
328 category='error')
317 raise HTTPInternalServerError()
329 raise HTTPInternalServerError()
318
330
319 @HasPermissionAllDecorator('hg.admin')
331 @HasPermissionAllDecorator('hg.admin')
320 def repo_stats(self, repo_name):
332 def repo_stats(self, repo_name):
321 """
333 """
322 DELETE an existing repository statistics
334 DELETE an existing repository statistics
323
335
324 :param repo_name:
336 :param repo_name:
325 """
337 """
326
338
327 try:
339 try:
328 repo_model = RepoModel()
340 repo_model = RepoModel()
329 repo_model.delete_stats(repo_name)
341 repo_model.delete_stats(repo_name)
330 except Exception, e:
342 except Exception, e:
331 h.flash(_('An error occurred during deletion of repository stats'),
343 h.flash(_('An error occurred during deletion of repository stats'),
332 category='error')
344 category='error')
333 return redirect(url('edit_repo', repo_name=repo_name))
345 return redirect(url('edit_repo', repo_name=repo_name))
334
346
335 @HasPermissionAllDecorator('hg.admin')
347 @HasPermissionAllDecorator('hg.admin')
336 def repo_cache(self, repo_name):
348 def repo_cache(self, repo_name):
337 """
349 """
338 INVALIDATE existing repository cache
350 INVALIDATE existing repository cache
339
351
340 :param repo_name:
352 :param repo_name:
341 """
353 """
342
354
343 try:
355 try:
344 ScmModel().mark_for_invalidation(repo_name)
356 ScmModel().mark_for_invalidation(repo_name)
345 except Exception, e:
357 except Exception, e:
346 h.flash(_('An error occurred during cache invalidation'),
358 h.flash(_('An error occurred during cache invalidation'),
347 category='error')
359 category='error')
348 return redirect(url('edit_repo', repo_name=repo_name))
360 return redirect(url('edit_repo', repo_name=repo_name))
349
361
350 @HasPermissionAllDecorator('hg.admin')
362 @HasPermissionAllDecorator('hg.admin')
351 def repo_public_journal(self, repo_name):
363 def repo_public_journal(self, repo_name):
352 """
364 """
353 Set's this repository to be visible in public journal,
365 Set's this repository to be visible in public journal,
354 in other words assing default user to follow this repo
366 in other words assing default user to follow this repo
355
367
356 :param repo_name:
368 :param repo_name:
357 """
369 """
358
370
359 cur_token = request.POST.get('auth_token')
371 cur_token = request.POST.get('auth_token')
360 token = get_token()
372 token = get_token()
361 if cur_token == token:
373 if cur_token == token:
362 try:
374 try:
363 repo_id = Repository.by_repo_name(repo_name).repo_id
375 repo_id = Repository.by_repo_name(repo_name).repo_id
364 user_id = User.by_username('default').user_id
376 user_id = User.by_username('default').user_id
365 self.scm_model.toggle_following_repo(repo_id, user_id)
377 self.scm_model.toggle_following_repo(repo_id, user_id)
366 h.flash(_('Updated repository visibility in public journal'),
378 h.flash(_('Updated repository visibility in public journal'),
367 category='success')
379 category='success')
368 except:
380 except:
369 h.flash(_('An error occurred during setting this'
381 h.flash(_('An error occurred during setting this'
370 ' repository in public journal'),
382 ' repository in public journal'),
371 category='error')
383 category='error')
372
384
373 else:
385 else:
374 h.flash(_('Token mismatch'), category='error')
386 h.flash(_('Token mismatch'), category='error')
375 return redirect(url('edit_repo', repo_name=repo_name))
387 return redirect(url('edit_repo', repo_name=repo_name))
376
388
377 @HasPermissionAllDecorator('hg.admin')
389 @HasPermissionAllDecorator('hg.admin')
378 def repo_pull(self, repo_name):
390 def repo_pull(self, repo_name):
379 """
391 """
380 Runs task to update given repository with remote changes,
392 Runs task to update given repository with remote changes,
381 ie. make pull on remote location
393 ie. make pull on remote location
382
394
383 :param repo_name:
395 :param repo_name:
384 """
396 """
385 try:
397 try:
386 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
398 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
387 h.flash(_('Pulled from remote location'), category='success')
399 h.flash(_('Pulled from remote location'), category='success')
388 except Exception, e:
400 except Exception, e:
389 h.flash(_('An error occurred during pull from remote location'),
401 h.flash(_('An error occurred during pull from remote location'),
390 category='error')
402 category='error')
391
403
392 return redirect(url('edit_repo', repo_name=repo_name))
404 return redirect(url('edit_repo', repo_name=repo_name))
393
405
394 @HasPermissionAllDecorator('hg.admin')
406 @HasPermissionAllDecorator('hg.admin')
395 def show(self, repo_name, format='html'):
407 def show(self, repo_name, format='html'):
396 """GET /repos/repo_name: Show a specific item"""
408 """GET /repos/repo_name: Show a specific item"""
397 # url('repo', repo_name=ID)
409 # url('repo', repo_name=ID)
398
410
399 @HasPermissionAllDecorator('hg.admin')
411 @HasPermissionAllDecorator('hg.admin')
400 def edit(self, repo_name, format='html'):
412 def edit(self, repo_name, format='html'):
401 """GET /repos/repo_name/edit: Form to edit an existing item"""
413 """GET /repos/repo_name/edit: Form to edit an existing item"""
402 # url('edit_repo', repo_name=ID)
414 # url('edit_repo', repo_name=ID)
403 defaults = self.__load_data(repo_name)
415 defaults = self.__load_data(repo_name)
404
416
405 return htmlfill.render(
417 return htmlfill.render(
406 render('admin/repos/repo_edit.html'),
418 render('admin/repos/repo_edit.html'),
407 defaults=defaults,
419 defaults=defaults,
408 encoding="UTF-8",
420 encoding="UTF-8",
409 force_defaults=False
421 force_defaults=False
410 )
422 )
@@ -1,682 +1,697 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 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10
10
11 from datetime import datetime
11 from datetime import datetime
12 from pygments.formatters import HtmlFormatter
12 from pygments.formatters import HtmlFormatter
13 from pygments import highlight as code_highlight
13 from pygments import highlight as code_highlight
14 from pylons import url, request, config
14 from pylons import url, request, config
15 from pylons.i18n.translation import _, ungettext
15 from pylons.i18n.translation import _, ungettext
16
16
17 from webhelpers.html import literal, HTML, escape
17 from webhelpers.html import literal, HTML, escape
18 from webhelpers.html.tools import *
18 from webhelpers.html.tools import *
19 from webhelpers.html.builder import make_tag
19 from webhelpers.html.builder import make_tag
20 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
20 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
21 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
21 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
22 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
22 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
23 password, textarea, title, ul, xml_declaration, radio
23 password, textarea, title, ul, xml_declaration, radio
24 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
24 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
25 mail_to, strip_links, strip_tags, tag_re
25 mail_to, strip_links, strip_tags, tag_re
26 from webhelpers.number import format_byte_size, format_bit_size
26 from webhelpers.number import format_byte_size, format_bit_size
27 from webhelpers.pylonslib import Flash as _Flash
27 from webhelpers.pylonslib import Flash as _Flash
28 from webhelpers.pylonslib.secure_form import secure_form
28 from webhelpers.pylonslib.secure_form import secure_form
29 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
29 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
30 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
30 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
31 replace_whitespace, urlify, truncate, wrap_paragraphs
31 replace_whitespace, urlify, truncate, wrap_paragraphs
32 from webhelpers.date import time_ago_in_words
32 from webhelpers.date import time_ago_in_words
33 from webhelpers.paginate import Page
33 from webhelpers.paginate import Page
34 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
34 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
35 convert_boolean_attrs, NotGiven
35 convert_boolean_attrs, NotGiven
36
36
37 from vcs.utils.annotate import annotate_highlight
37 from vcs.utils.annotate import annotate_highlight
38 from rhodecode.lib.utils import repo_name_slug
38 from rhodecode.lib.utils import repo_name_slug
39 from rhodecode.lib import str2bool
39 from rhodecode.lib import str2bool
40
40
41 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
41 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
42 """
42 """
43 Reset button
43 Reset button
44 """
44 """
45 _set_input_attrs(attrs, type, name, value)
45 _set_input_attrs(attrs, type, name, value)
46 _set_id_attr(attrs, id, name)
46 _set_id_attr(attrs, id, name)
47 convert_boolean_attrs(attrs, ["disabled"])
47 convert_boolean_attrs(attrs, ["disabled"])
48 return HTML.input(**attrs)
48 return HTML.input(**attrs)
49
49
50 reset = _reset
50 reset = _reset
51
51
52
52
53 def get_token():
53 def get_token():
54 """Return the current authentication token, creating one if one doesn't
54 """Return the current authentication token, creating one if one doesn't
55 already exist.
55 already exist.
56 """
56 """
57 token_key = "_authentication_token"
57 token_key = "_authentication_token"
58 from pylons import session
58 from pylons import session
59 if not token_key in session:
59 if not token_key in session:
60 try:
60 try:
61 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
61 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
62 except AttributeError: # Python < 2.4
62 except AttributeError: # Python < 2.4
63 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
63 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
64 session[token_key] = token
64 session[token_key] = token
65 if hasattr(session, 'save'):
65 if hasattr(session, 'save'):
66 session.save()
66 session.save()
67 return session[token_key]
67 return session[token_key]
68
68
69 class _GetError(object):
69 class _GetError(object):
70 """Get error from form_errors, and represent it as span wrapped error
70 """Get error from form_errors, and represent it as span wrapped error
71 message
71 message
72
72
73 :param field_name: field to fetch errors for
73 :param field_name: field to fetch errors for
74 :param form_errors: form errors dict
74 :param form_errors: form errors dict
75 """
75 """
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 class _ToolTip(object):
84 class _ToolTip(object):
85
85
86 def __call__(self, tooltip_title, trim_at=50):
86 def __call__(self, tooltip_title, trim_at=50):
87 """Special function just to wrap our text into nice formatted
87 """Special function just to wrap our text into nice formatted
88 autowrapped text
88 autowrapped text
89
89
90 :param tooltip_title:
90 :param tooltip_title:
91 """
91 """
92
92
93 return wrap_paragraphs(escape(tooltip_title), trim_at)\
93 return wrap_paragraphs(escape(tooltip_title), trim_at)\
94 .replace('\n', '<br/>')
94 .replace('\n', '<br/>')
95
95
96 def activate(self):
96 def activate(self):
97 """Adds tooltip mechanism to the given Html all tooltips have to have
97 """Adds tooltip mechanism to the given Html all tooltips have to have
98 set class `tooltip` and set attribute `tooltip_title`.
98 set class `tooltip` and set attribute `tooltip_title`.
99 Then a tooltip will be generated based on that. All with yui js tooltip
99 Then a tooltip will be generated based on that. All with yui js tooltip
100 """
100 """
101
101
102 js = '''
102 js = '''
103 YAHOO.util.Event.onDOMReady(function(){
103 YAHOO.util.Event.onDOMReady(function(){
104 function toolTipsId(){
104 function toolTipsId(){
105 var ids = [];
105 var ids = [];
106 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
106 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
107
107
108 for (var i = 0; i < tts.length; i++) {
108 for (var i = 0; i < tts.length; i++) {
109 //if element doesn't not have and id autogenerate one for tooltip
109 //if element doesn't not have and id autogenerate one for tooltip
110
110
111 if (!tts[i].id){
111 if (!tts[i].id){
112 tts[i].id='tt'+i*100;
112 tts[i].id='tt'+i*100;
113 }
113 }
114 ids.push(tts[i].id);
114 ids.push(tts[i].id);
115 }
115 }
116 return ids
116 return ids
117 };
117 };
118 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
118 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
119 context: toolTipsId(),
119 context: toolTipsId(),
120 monitorresize:false,
120 monitorresize:false,
121 xyoffset :[0,0],
121 xyoffset :[0,0],
122 autodismissdelay:300000,
122 autodismissdelay:300000,
123 hidedelay:5,
123 hidedelay:5,
124 showdelay:20,
124 showdelay:20,
125 });
125 });
126
126
127 // Set the text for the tooltip just before we display it. Lazy method
127 // Set the text for the tooltip just before we display it. Lazy method
128 myToolTips.contextTriggerEvent.subscribe(
128 myToolTips.contextTriggerEvent.subscribe(
129 function(type, args) {
129 function(type, args) {
130
130
131 var context = args[0];
131 var context = args[0];
132
132
133 //positioning of tooltip
133 //positioning of tooltip
134 var tt_w = this.element.clientWidth;//tooltip width
134 var tt_w = this.element.clientWidth;//tooltip width
135 var tt_h = this.element.clientHeight;//tooltip height
135 var tt_h = this.element.clientHeight;//tooltip height
136
136
137 var context_w = context.offsetWidth;
137 var context_w = context.offsetWidth;
138 var context_h = context.offsetHeight;
138 var context_h = context.offsetHeight;
139
139
140 var pos_x = YAHOO.util.Dom.getX(context);
140 var pos_x = YAHOO.util.Dom.getX(context);
141 var pos_y = YAHOO.util.Dom.getY(context);
141 var pos_y = YAHOO.util.Dom.getY(context);
142
142
143 var display_strategy = 'right';
143 var display_strategy = 'right';
144 var xy_pos = [0,0];
144 var xy_pos = [0,0];
145 switch (display_strategy){
145 switch (display_strategy){
146
146
147 case 'top':
147 case 'top':
148 var cur_x = (pos_x+context_w/2)-(tt_w/2);
148 var cur_x = (pos_x+context_w/2)-(tt_w/2);
149 var cur_y = (pos_y-tt_h-4);
149 var cur_y = (pos_y-tt_h-4);
150 xy_pos = [cur_x,cur_y];
150 xy_pos = [cur_x,cur_y];
151 break;
151 break;
152 case 'bottom':
152 case 'bottom':
153 var cur_x = (pos_x+context_w/2)-(tt_w/2);
153 var cur_x = (pos_x+context_w/2)-(tt_w/2);
154 var cur_y = pos_y+context_h+4;
154 var cur_y = pos_y+context_h+4;
155 xy_pos = [cur_x,cur_y];
155 xy_pos = [cur_x,cur_y];
156 break;
156 break;
157 case 'left':
157 case 'left':
158 var cur_x = (pos_x-tt_w-4);
158 var cur_x = (pos_x-tt_w-4);
159 var cur_y = pos_y-((tt_h/2)-context_h/2);
159 var cur_y = pos_y-((tt_h/2)-context_h/2);
160 xy_pos = [cur_x,cur_y];
160 xy_pos = [cur_x,cur_y];
161 break;
161 break;
162 case 'right':
162 case 'right':
163 var cur_x = (pos_x+context_w+4);
163 var cur_x = (pos_x+context_w+4);
164 var cur_y = pos_y-((tt_h/2)-context_h/2);
164 var cur_y = pos_y-((tt_h/2)-context_h/2);
165 xy_pos = [cur_x,cur_y];
165 xy_pos = [cur_x,cur_y];
166 break;
166 break;
167 default:
167 default:
168 var cur_x = (pos_x+context_w/2)-(tt_w/2);
168 var cur_x = (pos_x+context_w/2)-(tt_w/2);
169 var cur_y = pos_y-tt_h-4;
169 var cur_y = pos_y-tt_h-4;
170 xy_pos = [cur_x,cur_y];
170 xy_pos = [cur_x,cur_y];
171 break;
171 break;
172
172
173 }
173 }
174
174
175 this.cfg.setProperty("xy",xy_pos);
175 this.cfg.setProperty("xy",xy_pos);
176
176
177 });
177 });
178
178
179 //Mouse out
179 //Mouse out
180 myToolTips.contextMouseOutEvent.subscribe(
180 myToolTips.contextMouseOutEvent.subscribe(
181 function(type, args) {
181 function(type, args) {
182 var context = args[0];
182 var context = args[0];
183
183
184 });
184 });
185 });
185 });
186 '''
186 '''
187 return literal(js)
187 return literal(js)
188
188
189 tooltip = _ToolTip()
189 tooltip = _ToolTip()
190
190
191 class _FilesBreadCrumbs(object):
191 class _FilesBreadCrumbs(object):
192
192
193 def __call__(self, repo_name, rev, paths):
193 def __call__(self, repo_name, rev, paths):
194 if isinstance(paths, str):
194 if isinstance(paths, str):
195 paths = paths.decode('utf-8', 'replace')
195 paths = paths.decode('utf-8', 'replace')
196 url_l = [link_to(repo_name, url('files_home',
196 url_l = [link_to(repo_name, url('files_home',
197 repo_name=repo_name,
197 repo_name=repo_name,
198 revision=rev, f_path=''))]
198 revision=rev, f_path=''))]
199 paths_l = paths.split('/')
199 paths_l = paths.split('/')
200 for cnt, p in enumerate(paths_l):
200 for cnt, p in enumerate(paths_l):
201 if p != '':
201 if p != '':
202 url_l.append(link_to(p, url('files_home',
202 url_l.append(link_to(p, url('files_home',
203 repo_name=repo_name,
203 repo_name=repo_name,
204 revision=rev,
204 revision=rev,
205 f_path='/'.join(paths_l[:cnt + 1]))))
205 f_path='/'.join(paths_l[:cnt + 1]))))
206
206
207 return literal('/'.join(url_l))
207 return literal('/'.join(url_l))
208
208
209 files_breadcrumbs = _FilesBreadCrumbs()
209 files_breadcrumbs = _FilesBreadCrumbs()
210
210
211 class CodeHtmlFormatter(HtmlFormatter):
211 class CodeHtmlFormatter(HtmlFormatter):
212 """My code Html Formatter for source codes
212 """My code Html Formatter for source codes
213 """
213 """
214
214
215 def wrap(self, source, outfile):
215 def wrap(self, source, outfile):
216 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
216 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
217
217
218 def _wrap_code(self, source):
218 def _wrap_code(self, source):
219 for cnt, it in enumerate(source):
219 for cnt, it in enumerate(source):
220 i, t = it
220 i, t = it
221 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
221 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
222 yield i, t
222 yield i, t
223
223
224 def _wrap_tablelinenos(self, inner):
224 def _wrap_tablelinenos(self, inner):
225 dummyoutfile = StringIO.StringIO()
225 dummyoutfile = StringIO.StringIO()
226 lncount = 0
226 lncount = 0
227 for t, line in inner:
227 for t, line in inner:
228 if t:
228 if t:
229 lncount += 1
229 lncount += 1
230 dummyoutfile.write(line)
230 dummyoutfile.write(line)
231
231
232 fl = self.linenostart
232 fl = self.linenostart
233 mw = len(str(lncount + fl - 1))
233 mw = len(str(lncount + fl - 1))
234 sp = self.linenospecial
234 sp = self.linenospecial
235 st = self.linenostep
235 st = self.linenostep
236 la = self.lineanchors
236 la = self.lineanchors
237 aln = self.anchorlinenos
237 aln = self.anchorlinenos
238 nocls = self.noclasses
238 nocls = self.noclasses
239 if sp:
239 if sp:
240 lines = []
240 lines = []
241
241
242 for i in range(fl, fl + lncount):
242 for i in range(fl, fl + lncount):
243 if i % st == 0:
243 if i % st == 0:
244 if i % sp == 0:
244 if i % sp == 0:
245 if aln:
245 if aln:
246 lines.append('<a href="#%s%d" class="special">%*d</a>' %
246 lines.append('<a href="#%s%d" class="special">%*d</a>' %
247 (la, i, mw, i))
247 (la, i, mw, i))
248 else:
248 else:
249 lines.append('<span class="special">%*d</span>' % (mw, i))
249 lines.append('<span class="special">%*d</span>' % (mw, i))
250 else:
250 else:
251 if aln:
251 if aln:
252 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
252 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
253 else:
253 else:
254 lines.append('%*d' % (mw, i))
254 lines.append('%*d' % (mw, i))
255 else:
255 else:
256 lines.append('')
256 lines.append('')
257 ls = '\n'.join(lines)
257 ls = '\n'.join(lines)
258 else:
258 else:
259 lines = []
259 lines = []
260 for i in range(fl, fl + lncount):
260 for i in range(fl, fl + lncount):
261 if i % st == 0:
261 if i % st == 0:
262 if aln:
262 if aln:
263 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
263 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
264 else:
264 else:
265 lines.append('%*d' % (mw, i))
265 lines.append('%*d' % (mw, i))
266 else:
266 else:
267 lines.append('')
267 lines.append('')
268 ls = '\n'.join(lines)
268 ls = '\n'.join(lines)
269
269
270 # in case you wonder about the seemingly redundant <div> here: since the
270 # in case you wonder about the seemingly redundant <div> here: since the
271 # content in the other cell also is wrapped in a div, some browsers in
271 # content in the other cell also is wrapped in a div, some browsers in
272 # some configurations seem to mess up the formatting...
272 # some configurations seem to mess up the formatting...
273 if nocls:
273 if nocls:
274 yield 0, ('<table class="%stable">' % self.cssclass +
274 yield 0, ('<table class="%stable">' % self.cssclass +
275 '<tr><td><div class="linenodiv" '
275 '<tr><td><div class="linenodiv" '
276 'style="background-color: #f0f0f0; padding-right: 10px">'
276 'style="background-color: #f0f0f0; padding-right: 10px">'
277 '<pre style="line-height: 125%">' +
277 '<pre style="line-height: 125%">' +
278 ls + '</pre></div></td><td class="code">')
278 ls + '</pre></div></td><td class="code">')
279 else:
279 else:
280 yield 0, ('<table class="%stable">' % self.cssclass +
280 yield 0, ('<table class="%stable">' % self.cssclass +
281 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
281 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
282 ls + '</pre></div></td><td class="code">')
282 ls + '</pre></div></td><td class="code">')
283 yield 0, dummyoutfile.getvalue()
283 yield 0, dummyoutfile.getvalue()
284 yield 0, '</td></tr></table>'
284 yield 0, '</td></tr></table>'
285
285
286
286
287 def pygmentize(filenode, **kwargs):
287 def pygmentize(filenode, **kwargs):
288 """pygmentize function using pygments
288 """pygmentize function using pygments
289
289
290 :param filenode:
290 :param filenode:
291 """
291 """
292
292
293 return literal(code_highlight(filenode.content,
293 return literal(code_highlight(filenode.content,
294 filenode.lexer, CodeHtmlFormatter(**kwargs)))
294 filenode.lexer, CodeHtmlFormatter(**kwargs)))
295
295
296 def pygmentize_annotation(filenode, **kwargs):
296 def pygmentize_annotation(filenode, **kwargs):
297 """pygmentize function for annotation
297 """pygmentize function for annotation
298
298
299 :param filenode:
299 :param filenode:
300 """
300 """
301
301
302 color_dict = {}
302 color_dict = {}
303 def gen_color(n=10000):
303 def gen_color(n=10000):
304 """generator for getting n of evenly distributed colors using
304 """generator for getting n of evenly distributed colors using
305 hsv color and golden ratio. It always return same order of colors
305 hsv color and golden ratio. It always return same order of colors
306
306
307 :returns: RGB tuple
307 :returns: RGB tuple
308 """
308 """
309 import colorsys
309 import colorsys
310 golden_ratio = 0.618033988749895
310 golden_ratio = 0.618033988749895
311 h = 0.22717784590367374
311 h = 0.22717784590367374
312
312
313 for c in xrange(n):
313 for c in xrange(n):
314 h += golden_ratio
314 h += golden_ratio
315 h %= 1
315 h %= 1
316 HSV_tuple = [h, 0.95, 0.95]
316 HSV_tuple = [h, 0.95, 0.95]
317 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
317 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
318 yield map(lambda x:str(int(x * 256)), RGB_tuple)
318 yield map(lambda x:str(int(x * 256)), RGB_tuple)
319
319
320 cgenerator = gen_color()
320 cgenerator = gen_color()
321
321
322 def get_color_string(cs):
322 def get_color_string(cs):
323 if color_dict.has_key(cs):
323 if color_dict.has_key(cs):
324 col = color_dict[cs]
324 col = color_dict[cs]
325 else:
325 else:
326 col = color_dict[cs] = cgenerator.next()
326 col = color_dict[cs] = cgenerator.next()
327 return "color: rgb(%s)! important;" % (', '.join(col))
327 return "color: rgb(%s)! important;" % (', '.join(col))
328
328
329 def url_func(changeset):
329 def url_func(changeset):
330 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
330 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
331 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
331 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
332
332
333 tooltip_html = tooltip_html % (changeset.author,
333 tooltip_html = tooltip_html % (changeset.author,
334 changeset.date,
334 changeset.date,
335 tooltip(changeset.message))
335 tooltip(changeset.message))
336 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
336 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
337 short_id(changeset.raw_id))
337 short_id(changeset.raw_id))
338 uri = link_to(
338 uri = link_to(
339 lnk_format,
339 lnk_format,
340 url('changeset_home', repo_name=changeset.repository.name,
340 url('changeset_home', repo_name=changeset.repository.name,
341 revision=changeset.raw_id),
341 revision=changeset.raw_id),
342 style=get_color_string(changeset.raw_id),
342 style=get_color_string(changeset.raw_id),
343 class_='tooltip',
343 class_='tooltip',
344 title=tooltip_html
344 title=tooltip_html
345 )
345 )
346
346
347 uri += '\n'
347 uri += '\n'
348 return uri
348 return uri
349 return literal(annotate_highlight(filenode, url_func, **kwargs))
349 return literal(annotate_highlight(filenode, url_func, **kwargs))
350
350
351 def get_changeset_safe(repo, rev):
351 def get_changeset_safe(repo, rev):
352 from vcs.backends.base import BaseRepository
352 from vcs.backends.base import BaseRepository
353 from vcs.exceptions import RepositoryError
353 from vcs.exceptions import RepositoryError
354 if not isinstance(repo, BaseRepository):
354 if not isinstance(repo, BaseRepository):
355 raise Exception('You must pass an Repository '
355 raise Exception('You must pass an Repository '
356 'object as first argument got %s', type(repo))
356 'object as first argument got %s', type(repo))
357
357
358 try:
358 try:
359 cs = repo.get_changeset(rev)
359 cs = repo.get_changeset(rev)
360 except RepositoryError:
360 except RepositoryError:
361 from rhodecode.lib.utils import EmptyChangeset
361 from rhodecode.lib.utils import EmptyChangeset
362 cs = EmptyChangeset()
362 cs = EmptyChangeset()
363 return cs
363 return cs
364
364
365
365
366 def is_following_repo(repo_name, user_id):
366 def is_following_repo(repo_name, user_id):
367 from rhodecode.model.scm import ScmModel
367 from rhodecode.model.scm import ScmModel
368 return ScmModel().is_following_repo(repo_name, user_id)
368 return ScmModel().is_following_repo(repo_name, user_id)
369
369
370 flash = _Flash()
370 flash = _Flash()
371
371
372
372
373 #==============================================================================
373 #==============================================================================
374 # MERCURIAL FILTERS available via h.
374 # MERCURIAL FILTERS available via h.
375 #==============================================================================
375 #==============================================================================
376 from mercurial import util
376 from mercurial import util
377 from mercurial.templatefilters import person as _person
377 from mercurial.templatefilters import person as _person
378
378
379 def _age(curdate):
379 def _age(curdate):
380 """turns a datetime into an age string."""
380 """turns a datetime into an age string."""
381
381
382 if not curdate:
382 if not curdate:
383 return ''
383 return ''
384
384
385 agescales = [("year", 3600 * 24 * 365),
385 agescales = [("year", 3600 * 24 * 365),
386 ("month", 3600 * 24 * 30),
386 ("month", 3600 * 24 * 30),
387 ("day", 3600 * 24),
387 ("day", 3600 * 24),
388 ("hour", 3600),
388 ("hour", 3600),
389 ("minute", 60),
389 ("minute", 60),
390 ("second", 1), ]
390 ("second", 1), ]
391
391
392 age = datetime.now() - curdate
392 age = datetime.now() - curdate
393 age_seconds = (age.days * agescales[2][1]) + age.seconds
393 age_seconds = (age.days * agescales[2][1]) + age.seconds
394 pos = 1
394 pos = 1
395 for scale in agescales:
395 for scale in agescales:
396 if scale[1] <= age_seconds:
396 if scale[1] <= age_seconds:
397 if pos == 6:pos = 5
397 if pos == 6:pos = 5
398 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
398 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
399 pos += 1
399 pos += 1
400
400
401 return _('just now')
401 return _('just now')
402
402
403 age = lambda x:_age(x)
403 age = lambda x:_age(x)
404 capitalize = lambda x: x.capitalize()
404 capitalize = lambda x: x.capitalize()
405 email = util.email
405 email = util.email
406 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
406 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
407 person = lambda x: _person(x)
407 person = lambda x: _person(x)
408 short_id = lambda x: x[:12]
408 short_id = lambda x: x[:12]
409
409
410
410
411 def bool2icon(value):
411 def bool2icon(value):
412 """Returns True/False values represented as small html image of true/false
412 """Returns True/False values represented as small html image of true/false
413 icons
413 icons
414
414
415 :param value: bool value
415 :param value: bool value
416 """
416 """
417
417
418 if value is True:
418 if value is True:
419 return HTML.tag('img', src=url("/images/icons/accept.png"),
419 return HTML.tag('img', src=url("/images/icons/accept.png"),
420 alt=_('True'))
420 alt=_('True'))
421
421
422 if value is False:
422 if value is False:
423 return HTML.tag('img', src=url("/images/icons/cancel.png"),
423 return HTML.tag('img', src=url("/images/icons/cancel.png"),
424 alt=_('False'))
424 alt=_('False'))
425
425
426 return value
426 return value
427
427
428
428
429 def action_parser(user_log, feed=False):
429 def action_parser(user_log, feed=False):
430 """This helper will action_map the specified string action into translated
430 """This helper will action_map the specified string action into translated
431 fancy names with icons and links
431 fancy names with icons and links
432
432
433 :param user_log: user log instance
433 :param user_log: user log instance
434 :param feed: use output for feeds (no html and fancy icons)
434 :param feed: use output for feeds (no html and fancy icons)
435 """
435 """
436
436
437 action = user_log.action
437 action = user_log.action
438 action_params = ' '
438 action_params = ' '
439
439
440 x = action.split(':')
440 x = action.split(':')
441
441
442 if len(x) > 1:
442 if len(x) > 1:
443 action, action_params = x
443 action, action_params = x
444
444
445 def get_cs_links():
445 def get_cs_links():
446 revs_limit = 5 #display this amount always
446 revs_limit = 5 #display this amount always
447 revs_top_limit = 50 #show upto this amount of changesets hidden
447 revs_top_limit = 50 #show upto this amount of changesets hidden
448 revs = action_params.split(',')
448 revs = action_params.split(',')
449 repo_name = user_log.repository.repo_name
449 repo_name = user_log.repository.repo_name
450
450
451 from rhodecode.model.scm import ScmModel
451 from rhodecode.model.scm import ScmModel
452 repo, dbrepo = ScmModel().get(repo_name, retval='repo',
452 repo, dbrepo = ScmModel().get(repo_name, retval='repo',
453 invalidation_list=[])
453 invalidation_list=[])
454
454
455 message = lambda rev: get_changeset_safe(repo, rev).message
455 message = lambda rev: get_changeset_safe(repo, rev).message
456
456
457 cs_links = " " + ', '.join ([link_to(rev,
457 cs_links = " " + ', '.join ([link_to(rev,
458 url('changeset_home',
458 url('changeset_home',
459 repo_name=repo_name,
459 repo_name=repo_name,
460 revision=rev), title=tooltip(message(rev)),
460 revision=rev), title=tooltip(message(rev)),
461 class_='tooltip') for rev in revs[:revs_limit] ])
461 class_='tooltip') for rev in revs[:revs_limit] ])
462
462
463 compare_view = (' <div class="compare_view tooltip" title="%s">'
463 compare_view = (' <div class="compare_view tooltip" title="%s">'
464 '<a href="%s">%s</a> '
464 '<a href="%s">%s</a> '
465 '</div>' % (_('Show all combined changesets %s->%s' \
465 '</div>' % (_('Show all combined changesets %s->%s' \
466 % (revs[0], revs[-1])),
466 % (revs[0], revs[-1])),
467 url('changeset_home', repo_name=repo_name,
467 url('changeset_home', repo_name=repo_name,
468 revision='%s...%s' % (revs[0], revs[-1])
468 revision='%s...%s' % (revs[0], revs[-1])
469 ),
469 ),
470 _('compare view'))
470 _('compare view'))
471 )
471 )
472
472
473 if len(revs) > revs_limit:
473 if len(revs) > revs_limit:
474 uniq_id = revs[0]
474 uniq_id = revs[0]
475 html_tmpl = ('<span> %s '
475 html_tmpl = ('<span> %s '
476 '<a class="show_more" id="_%s" href="#more">%s</a> '
476 '<a class="show_more" id="_%s" href="#more">%s</a> '
477 '%s</span>')
477 '%s</span>')
478 if not feed:
478 if not feed:
479 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
479 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
480 % (len(revs) - revs_limit),
480 % (len(revs) - revs_limit),
481 _('revisions'))
481 _('revisions'))
482
482
483 if not feed:
483 if not feed:
484 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
484 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
485 else:
485 else:
486 html_tmpl = '<span id="%s"> %s </span>'
486 html_tmpl = '<span id="%s"> %s </span>'
487
487
488 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
488 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
489 url('changeset_home',
489 url('changeset_home',
490 repo_name=repo_name, revision=rev),
490 repo_name=repo_name, revision=rev),
491 title=message(rev), class_='tooltip')
491 title=message(rev), class_='tooltip')
492 for rev in revs[revs_limit:revs_top_limit]]))
492 for rev in revs[revs_limit:revs_top_limit]]))
493 if len(revs) > 1:
493 if len(revs) > 1:
494 cs_links += compare_view
494 cs_links += compare_view
495 return cs_links
495 return cs_links
496
496
497 def get_fork_name():
497 def get_fork_name():
498 repo_name = action_params
498 repo_name = action_params
499 return _('fork name ') + str(link_to(action_params, url('summary_home',
499 return _('fork name ') + str(link_to(action_params, url('summary_home',
500 repo_name=repo_name,)))
500 repo_name=repo_name,)))
501
501
502 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
502 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
503 'user_created_repo':(_('[created] repository'), None),
503 'user_created_repo':(_('[created] repository'), None),
504 'user_forked_repo':(_('[forked] repository'), get_fork_name),
504 'user_forked_repo':(_('[forked] repository'), get_fork_name),
505 'user_updated_repo':(_('[updated] repository'), None),
505 'user_updated_repo':(_('[updated] repository'), None),
506 'admin_deleted_repo':(_('[delete] repository'), None),
506 'admin_deleted_repo':(_('[delete] repository'), None),
507 'admin_created_repo':(_('[created] repository'), None),
507 'admin_created_repo':(_('[created] repository'), None),
508 'admin_forked_repo':(_('[forked] repository'), None),
508 'admin_forked_repo':(_('[forked] repository'), None),
509 'admin_updated_repo':(_('[updated] repository'), None),
509 'admin_updated_repo':(_('[updated] repository'), None),
510 'push':(_('[pushed] into'), get_cs_links),
510 'push':(_('[pushed] into'), get_cs_links),
511 'push_remote':(_('[pulled from remote] into'), get_cs_links),
511 'push_remote':(_('[pulled from remote] into'), get_cs_links),
512 'pull':(_('[pulled] from'), None),
512 'pull':(_('[pulled] from'), None),
513 'started_following_repo':(_('[started following] repository'), None),
513 'started_following_repo':(_('[started following] repository'), None),
514 'stopped_following_repo':(_('[stopped following] repository'), None),
514 'stopped_following_repo':(_('[stopped following] repository'), None),
515 }
515 }
516
516
517 action_str = action_map.get(action, action)
517 action_str = action_map.get(action, action)
518 if feed:
518 if feed:
519 action = action_str[0].replace('[', '').replace(']', '')
519 action = action_str[0].replace('[', '').replace(']', '')
520 else:
520 else:
521 action = action_str[0].replace('[', '<span class="journal_highlight">')\
521 action = action_str[0].replace('[', '<span class="journal_highlight">')\
522 .replace(']', '</span>')
522 .replace(']', '</span>')
523
523
524 action_params_func = lambda :""
524 action_params_func = lambda :""
525
525
526 if callable(action_str[1]):
526 if callable(action_str[1]):
527 action_params_func = action_str[1]
527 action_params_func = action_str[1]
528
528
529 return [literal(action), action_params_func]
529 return [literal(action), action_params_func]
530
530
531 def action_parser_icon(user_log):
531 def action_parser_icon(user_log):
532 action = user_log.action
532 action = user_log.action
533 action_params = None
533 action_params = None
534 x = action.split(':')
534 x = action.split(':')
535
535
536 if len(x) > 1:
536 if len(x) > 1:
537 action, action_params = x
537 action, action_params = x
538
538
539 tmpl = """<img src="%s%s" alt="%s"/>"""
539 tmpl = """<img src="%s%s" alt="%s"/>"""
540 map = {'user_deleted_repo':'database_delete.png',
540 map = {'user_deleted_repo':'database_delete.png',
541 'user_created_repo':'database_add.png',
541 'user_created_repo':'database_add.png',
542 'user_forked_repo':'arrow_divide.png',
542 'user_forked_repo':'arrow_divide.png',
543 'user_updated_repo':'database_edit.png',
543 'user_updated_repo':'database_edit.png',
544 'admin_deleted_repo':'database_delete.png',
544 'admin_deleted_repo':'database_delete.png',
545 'admin_created_repo':'database_add.png',
545 'admin_created_repo':'database_add.png',
546 'admin_forked_repo':'arrow_divide.png',
546 'admin_forked_repo':'arrow_divide.png',
547 'admin_updated_repo':'database_edit.png',
547 'admin_updated_repo':'database_edit.png',
548 'push':'script_add.png',
548 'push':'script_add.png',
549 'push_remote':'connect.png',
549 'push_remote':'connect.png',
550 'pull':'down_16.png',
550 'pull':'down_16.png',
551 'started_following_repo':'heart_add.png',
551 'started_following_repo':'heart_add.png',
552 'stopped_following_repo':'heart_delete.png',
552 'stopped_following_repo':'heart_delete.png',
553 }
553 }
554 return literal(tmpl % ((url('/images/icons/')),
554 return literal(tmpl % ((url('/images/icons/')),
555 map.get(action, action), action))
555 map.get(action, action), action))
556
556
557
557
558 #==============================================================================
558 #==============================================================================
559 # PERMS
559 # PERMS
560 #==============================================================================
560 #==============================================================================
561 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
561 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
562 HasRepoPermissionAny, HasRepoPermissionAll
562 HasRepoPermissionAny, HasRepoPermissionAll
563
563
564 #==============================================================================
564 #==============================================================================
565 # GRAVATAR URL
565 # GRAVATAR URL
566 #==============================================================================
566 #==============================================================================
567
567
568 def gravatar_url(email_address, size=30):
568 def gravatar_url(email_address, size=30):
569 if not str2bool(config['app_conf'].get('use_gravatar')):
569 if not str2bool(config['app_conf'].get('use_gravatar')):
570 return "/images/user%s.png" % size
570 return "/images/user%s.png" % size
571
571
572 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
572 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
573 default = 'identicon'
573 default = 'identicon'
574 baseurl_nossl = "http://www.gravatar.com/avatar/"
574 baseurl_nossl = "http://www.gravatar.com/avatar/"
575 baseurl_ssl = "https://secure.gravatar.com/avatar/"
575 baseurl_ssl = "https://secure.gravatar.com/avatar/"
576 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
576 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
577
577
578 if isinstance(email_address, unicode):
578 if isinstance(email_address, unicode):
579 #hashlib crashes on unicode items
579 #hashlib crashes on unicode items
580 email_address = email_address.encode('utf8', 'replace')
580 email_address = email_address.encode('utf8', 'replace')
581 # construct the url
581 # construct the url
582 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
582 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
583 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
583 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
584
584
585 return gravatar_url
585 return gravatar_url
586
586
587
587
588 #==============================================================================
588 #==============================================================================
589 # REPO PAGER
589 # REPO PAGER
590 #==============================================================================
590 #==============================================================================
591 class RepoPage(Page):
591 class RepoPage(Page):
592
592
593 def __init__(self, collection, page=1, items_per_page=20,
593 def __init__(self, collection, page=1, items_per_page=20,
594 item_count=None, url=None, branch_name=None, **kwargs):
594 item_count=None, url=None, branch_name=None, **kwargs):
595
595
596 """Create a "RepoPage" instance. special pager for paging
596 """Create a "RepoPage" instance. special pager for paging
597 repository
597 repository
598 """
598 """
599 self._url_generator = url
599 self._url_generator = url
600
600
601 # Safe the kwargs class-wide so they can be used in the pager() method
601 # Safe the kwargs class-wide so they can be used in the pager() method
602 self.kwargs = kwargs
602 self.kwargs = kwargs
603
603
604 # Save a reference to the collection
604 # Save a reference to the collection
605 self.original_collection = collection
605 self.original_collection = collection
606
606
607 self.collection = collection
607 self.collection = collection
608
608
609 # The self.page is the number of the current page.
609 # The self.page is the number of the current page.
610 # The first page has the number 1!
610 # The first page has the number 1!
611 try:
611 try:
612 self.page = int(page) # make it int() if we get it as a string
612 self.page = int(page) # make it int() if we get it as a string
613 except (ValueError, TypeError):
613 except (ValueError, TypeError):
614 self.page = 1
614 self.page = 1
615
615
616 self.items_per_page = items_per_page
616 self.items_per_page = items_per_page
617
617
618 # Unless the user tells us how many items the collections has
618 # Unless the user tells us how many items the collections has
619 # we calculate that ourselves.
619 # we calculate that ourselves.
620 if item_count is not None:
620 if item_count is not None:
621 self.item_count = item_count
621 self.item_count = item_count
622 else:
622 else:
623 self.item_count = len(self.collection)
623 self.item_count = len(self.collection)
624
624
625 # Compute the number of the first and last available page
625 # Compute the number of the first and last available page
626 if self.item_count > 0:
626 if self.item_count > 0:
627 self.first_page = 1
627 self.first_page = 1
628 self.page_count = ((self.item_count - 1) / self.items_per_page) + 1
628 self.page_count = ((self.item_count - 1) / self.items_per_page) + 1
629 self.last_page = self.first_page + self.page_count - 1
629 self.last_page = self.first_page + self.page_count - 1
630
630
631 # Make sure that the requested page number is the range of valid pages
631 # Make sure that the requested page number is the range of valid pages
632 if self.page > self.last_page:
632 if self.page > self.last_page:
633 self.page = self.last_page
633 self.page = self.last_page
634 elif self.page < self.first_page:
634 elif self.page < self.first_page:
635 self.page = self.first_page
635 self.page = self.first_page
636
636
637 # Note: the number of items on this page can be less than
637 # Note: the number of items on this page can be less than
638 # items_per_page if the last page is not full
638 # items_per_page if the last page is not full
639 self.first_item = max(0, (self.item_count) - (self.page * items_per_page))
639 self.first_item = max(0, (self.item_count) - (self.page * items_per_page))
640 self.last_item = ((self.item_count - 1) - items_per_page * (self.page - 1))
640 self.last_item = ((self.item_count - 1) - items_per_page * (self.page - 1))
641
641
642 iterator = self.collection.get_changesets(start=self.first_item,
642 iterator = self.collection.get_changesets(start=self.first_item,
643 end=self.last_item,
643 end=self.last_item,
644 reverse=True,
644 reverse=True,
645 branch_name=branch_name)
645 branch_name=branch_name)
646 self.items = list(iterator)
646 self.items = list(iterator)
647
647
648 # Links to previous and next page
648 # Links to previous and next page
649 if self.page > self.first_page:
649 if self.page > self.first_page:
650 self.previous_page = self.page - 1
650 self.previous_page = self.page - 1
651 else:
651 else:
652 self.previous_page = None
652 self.previous_page = None
653
653
654 if self.page < self.last_page:
654 if self.page < self.last_page:
655 self.next_page = self.page + 1
655 self.next_page = self.page + 1
656 else:
656 else:
657 self.next_page = None
657 self.next_page = None
658
658
659 # No items available
659 # No items available
660 else:
660 else:
661 self.first_page = None
661 self.first_page = None
662 self.page_count = 0
662 self.page_count = 0
663 self.last_page = None
663 self.last_page = None
664 self.first_item = None
664 self.first_item = None
665 self.last_item = None
665 self.last_item = None
666 self.previous_page = None
666 self.previous_page = None
667 self.next_page = None
667 self.next_page = None
668 self.items = []
668 self.items = []
669
669
670 # This is a subclass of the 'list' type. Initialise the list now.
670 # This is a subclass of the 'list' type. Initialise the list now.
671 list.__init__(self, self.items)
671 list.__init__(self, self.items)
672
672
673
673
674 def changed_tooltip(nodes):
674 def changed_tooltip(nodes):
675 if nodes:
675 if nodes:
676 pref = ': <br/> '
676 pref = ': <br/> '
677 suf = ''
677 suf = ''
678 if len(nodes) > 30:
678 if len(nodes) > 30:
679 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
679 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
680 return literal(pref + '<br/> '.join([x.path.decode('utf-8', 'replace') for x in nodes[:30]]) + suf)
680 return literal(pref + '<br/> '.join([x.path.decode('utf-8', 'replace') for x in nodes[:30]]) + suf)
681 else:
681 else:
682 return ': ' + _('No Files')
682 return ': ' + _('No Files')
683
684
685
686 def repo_link(groups_and_repos):
687 groups, repo_name = groups_and_repos
688
689 if not groups:
690 return repo_name
691 else:
692 def make_link(group):
693 return link_to(group.group_name, url('/', group.group_id))
694 return literal(' &raquo; '.join(map(make_link, groups)) + \
695 " &raquo; " + repo_name)
696
697
@@ -1,369 +1,384 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27 import logging
27 import logging
28 import datetime
28 import datetime
29 from datetime import date
29 from datetime import date
30
30
31 from sqlalchemy import *
31 from sqlalchemy import *
32 from sqlalchemy.exc import DatabaseError
32 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.orm import relationship, backref
33 from sqlalchemy.orm import relationship, backref
34 from sqlalchemy.orm.interfaces import MapperExtension
34 from sqlalchemy.orm.interfaces import MapperExtension
35
35
36 from rhodecode.model.meta import Base, Session
36 from rhodecode.model.meta import Base, Session
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40 #==============================================================================
40 #==============================================================================
41 # MAPPER EXTENSIONS
41 # MAPPER EXTENSIONS
42 #==============================================================================
42 #==============================================================================
43
43
44 class RepositoryMapper(MapperExtension):
44 class RepositoryMapper(MapperExtension):
45 def after_update(self, mapper, connection, instance):
45 def after_update(self, mapper, connection, instance):
46 pass
46 pass
47
47
48
48
49 class RhodeCodeSettings(Base):
49 class RhodeCodeSettings(Base):
50 __tablename__ = 'rhodecode_settings'
50 __tablename__ = 'rhodecode_settings'
51 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
51 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
52 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
52 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
53 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
53 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
54 app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
54 app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
55
55
56 def __init__(self, k='', v=''):
56 def __init__(self, k='', v=''):
57 self.app_settings_name = k
57 self.app_settings_name = k
58 self.app_settings_value = v
58 self.app_settings_value = v
59
59
60 def __repr__(self):
60 def __repr__(self):
61 return "<%s('%s:%s')>" % (self.__class__.__name__,
61 return "<%s('%s:%s')>" % (self.__class__.__name__,
62 self.app_settings_name, self.app_settings_value)
62 self.app_settings_name, self.app_settings_value)
63
63
64 class RhodeCodeUi(Base):
64 class RhodeCodeUi(Base):
65 __tablename__ = 'rhodecode_ui'
65 __tablename__ = 'rhodecode_ui'
66 __table_args__ = {'useexisting':True}
66 __table_args__ = {'useexisting':True}
67 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
67 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
68 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
68 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
69 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
69 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
70 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
70 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
71 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
71 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
72
72
73
73
74 class User(Base):
74 class User(Base):
75 __tablename__ = 'users'
75 __tablename__ = 'users'
76 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True})
76 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True})
77 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
77 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
78 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
78 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
79 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
79 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
80 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
80 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
81 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
81 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
82 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
82 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
83 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
83 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
84 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
84 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
85 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
85 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
86 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
86 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
87 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
87 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
88
88
89 user_log = relationship('UserLog', cascade='all')
89 user_log = relationship('UserLog', cascade='all')
90 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
90 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
91
91
92 repositories = relationship('Repository')
92 repositories = relationship('Repository')
93 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
93 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
94
94
95 group_member = relationship('UsersGroupMember', cascade='all')
95 group_member = relationship('UsersGroupMember', cascade='all')
96
96
97 @property
97 @property
98 def full_contact(self):
98 def full_contact(self):
99 return '%s %s <%s>' % (self.name, self.lastname, self.email)
99 return '%s %s <%s>' % (self.name, self.lastname, self.email)
100
100
101 @property
101 @property
102 def short_contact(self):
102 def short_contact(self):
103 return '%s %s' % (self.name, self.lastname)
103 return '%s %s' % (self.name, self.lastname)
104
104
105
105
106 @property
106 @property
107 def is_admin(self):
107 def is_admin(self):
108 return self.admin
108 return self.admin
109
109
110 def __repr__(self):
110 def __repr__(self):
111 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
111 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
112 self.user_id, self.username)
112 self.user_id, self.username)
113
113
114 @classmethod
114 @classmethod
115 def by_username(cls, username):
115 def by_username(cls, username):
116 return Session.query(cls).filter(cls.username == username).one()
116 return Session.query(cls).filter(cls.username == username).one()
117
117
118
118
119 def update_lastlogin(self):
119 def update_lastlogin(self):
120 """Update user lastlogin"""
120 """Update user lastlogin"""
121
121
122 try:
122 try:
123 session = Session.object_session(self)
123 session = Session.object_session(self)
124 self.last_login = datetime.datetime.now()
124 self.last_login = datetime.datetime.now()
125 session.add(self)
125 session.add(self)
126 session.commit()
126 session.commit()
127 log.debug('updated user %s lastlogin', self.username)
127 log.debug('updated user %s lastlogin', self.username)
128 except (DatabaseError,):
128 except (DatabaseError,):
129 session.rollback()
129 session.rollback()
130
130
131
131
132 class UserLog(Base):
132 class UserLog(Base):
133 __tablename__ = 'user_logs'
133 __tablename__ = 'user_logs'
134 __table_args__ = {'useexisting':True}
134 __table_args__ = {'useexisting':True}
135 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
135 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
136 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
136 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
137 repository_id = Column("repository_id", Integer(length=None, convert_unicode=False, assert_unicode=None), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
137 repository_id = Column("repository_id", Integer(length=None, convert_unicode=False, assert_unicode=None), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
138 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
138 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
139 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
139 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
140 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
140 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
141 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
141 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
142
142
143 @property
143 @property
144 def action_as_day(self):
144 def action_as_day(self):
145 return date(*self.action_date.timetuple()[:3])
145 return date(*self.action_date.timetuple()[:3])
146
146
147 user = relationship('User')
147 user = relationship('User')
148 repository = relationship('Repository')
148 repository = relationship('Repository')
149
149
150
150
151 class UsersGroup(Base):
151 class UsersGroup(Base):
152 __tablename__ = 'users_groups'
152 __tablename__ = 'users_groups'
153 __table_args__ = {'useexisting':True}
153 __table_args__ = {'useexisting':True}
154
154
155 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
155 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
156 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
156 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
157 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
157 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
158
158
159 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
159 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
160
160
161 class UsersGroupMember(Base):
161 class UsersGroupMember(Base):
162 __tablename__ = 'users_groups_members'
162 __tablename__ = 'users_groups_members'
163 __table_args__ = {'useexisting':True}
163 __table_args__ = {'useexisting':True}
164
164
165 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
165 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
166 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
166 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
167 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
167 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
168
168
169 user = relationship('User', lazy='joined')
169 user = relationship('User', lazy='joined')
170 users_group = relationship('UsersGroup')
170 users_group = relationship('UsersGroup')
171
171
172 def __init__(self, gr_id='', u_id=''):
172 def __init__(self, gr_id='', u_id=''):
173 self.users_group_id = gr_id
173 self.users_group_id = gr_id
174 self.user_id = u_id
174 self.user_id = u_id
175
175
176 class Repository(Base):
176 class Repository(Base):
177 __tablename__ = 'repositories'
177 __tablename__ = 'repositories'
178 __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},)
178 __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},)
179 __mapper_args__ = {'extension':RepositoryMapper()}
179 __mapper_args__ = {'extension':RepositoryMapper()}
180
180
181 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
181 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
182 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
182 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
183 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
183 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
184 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
184 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
185 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
185 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
186 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
186 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
187 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
187 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
188 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
188 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
189 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
189 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
190 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
190 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
191 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
191 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
192
192
193
193
194 user = relationship('User')
194 user = relationship('User')
195 fork = relationship('Repository', remote_side=repo_id)
195 fork = relationship('Repository', remote_side=repo_id)
196 group = relationship('Group')
196 group = relationship('Group')
197 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
197 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
198 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
198 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
199 stats = relationship('Statistics', cascade='all', uselist=False)
199 stats = relationship('Statistics', cascade='all', uselist=False)
200
200
201 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
201 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
202
202
203 logs = relationship('UserLog', cascade='all')
203 logs = relationship('UserLog', cascade='all')
204
204
205 def __repr__(self):
205 def __repr__(self):
206 return "<%s('%s:%s')>" % (self.__class__.__name__,
206 return "<%s('%s:%s')>" % (self.__class__.__name__,
207 self.repo_id, self.repo_name)
207 self.repo_id, self.repo_name)
208
208
209 @classmethod
209 @classmethod
210 def by_repo_name(cls, repo_name):
210 def by_repo_name(cls, repo_name):
211 return Session.query(cls).filter(cls.repo_name == repo_name).one()
211 return Session.query(cls).filter(cls.repo_name == repo_name).one()
212
212
213 @property
213 @property
214 def just_name(self):
214 def just_name(self):
215 return self.repo_name.split('/')[-1]
215 return self.repo_name.split('/')[-1]
216
216
217 @property
217 @property
218 def groups_with_parents(self):
218 def groups_with_parents(self):
219 groups = []
219 groups = []
220 if self.group is None:
220 if self.group is None:
221 return groups
221 return groups
222
222
223 cur_gr = self.group
223 cur_gr = self.group
224 groups.insert(0, cur_gr)
224 groups.insert(0, cur_gr)
225 while 1:
225 while 1:
226 gr = getattr(cur_gr, 'parent_group', None)
226 gr = getattr(cur_gr, 'parent_group', None)
227 cur_gr = cur_gr.parent_group
227 cur_gr = cur_gr.parent_group
228 if gr is None:
228 if gr is None:
229 break
229 break
230 groups.insert(0, gr)
230 groups.insert(0, gr)
231
231
232 return groups
232 return groups
233
233
234 @property
234 @property
235 def groups_and_repo(self):
235 def groups_and_repo(self):
236 return self.groups_with_parents, self.just_name
236 return self.groups_with_parents, self.just_name
237
237
238
238
239 class Group(Base):
239 class Group(Base):
240 __tablename__ = 'groups'
240 __tablename__ = 'groups'
241 __table_args__ = (UniqueConstraint('group_name'), {'useexisting':True},)
241 __table_args__ = (UniqueConstraint('group_name'), {'useexisting':True},)
242
242
243 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
243 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
244 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
244 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
245 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
245 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
246
246
247 parent_group = relationship('Group', remote_side=group_id)
247 parent_group = relationship('Group', remote_side=group_id)
248
248
249
249
250 def __init__(self, group_name='', parent_group=None):
250 def __init__(self, group_name='', parent_group=None):
251 self.group_name = group_name
251 self.group_name = group_name
252 self.parent_group = parent_group
252 self.parent_group = parent_group
253
253
254 def __repr__(self):
254 def __repr__(self):
255 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
255 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
256 self.group_name)
256 self.group_name)
257
257
258 @property
259 def parents(self):
260 groups = []
261 if self.parent_group is None:
262 return groups
263 cur_gr = self.parent_group
264 groups.insert(0, cur_gr)
265 while 1:
266 gr = getattr(cur_gr, 'parent_group', None)
267 cur_gr = cur_gr.parent_group
268 if gr is None:
269 break
270 groups.insert(0, gr)
271 return groups
272
258 class Permission(Base):
273 class Permission(Base):
259 __tablename__ = 'permissions'
274 __tablename__ = 'permissions'
260 __table_args__ = {'useexisting':True}
275 __table_args__ = {'useexisting':True}
261 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
276 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
262 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
277 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
263 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
278 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264
279
265 def __repr__(self):
280 def __repr__(self):
266 return "<%s('%s:%s')>" % (self.__class__.__name__,
281 return "<%s('%s:%s')>" % (self.__class__.__name__,
267 self.permission_id, self.permission_name)
282 self.permission_id, self.permission_name)
268
283
269 class RepoToPerm(Base):
284 class RepoToPerm(Base):
270 __tablename__ = 'repo_to_perm'
285 __tablename__ = 'repo_to_perm'
271 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
286 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
272 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
287 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
273 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
288 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
274 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
289 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
275 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
290 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
276
291
277 user = relationship('User')
292 user = relationship('User')
278 permission = relationship('Permission')
293 permission = relationship('Permission')
279 repository = relationship('Repository')
294 repository = relationship('Repository')
280
295
281 class UserToPerm(Base):
296 class UserToPerm(Base):
282 __tablename__ = 'user_to_perm'
297 __tablename__ = 'user_to_perm'
283 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True})
298 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True})
284 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
299 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
285 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
300 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
286 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
301 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
287
302
288 user = relationship('User')
303 user = relationship('User')
289 permission = relationship('Permission')
304 permission = relationship('Permission')
290
305
291
306
292 class UsersGroupToPerm(Base):
307 class UsersGroupToPerm(Base):
293 __tablename__ = 'users_group_to_perm'
308 __tablename__ = 'users_group_to_perm'
294 __table_args__ = (UniqueConstraint('users_group_id', 'permission_id'), {'useexisting':True})
309 __table_args__ = (UniqueConstraint('users_group_id', 'permission_id'), {'useexisting':True})
295 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
310 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
296 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
311 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
297 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
312 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
298 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
313 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
299
314
300 users_group = relationship('UsersGroup')
315 users_group = relationship('UsersGroup')
301 permission = relationship('Permission')
316 permission = relationship('Permission')
302 repository = relationship('Repository')
317 repository = relationship('Repository')
303
318
304 class GroupToPerm(Base):
319 class GroupToPerm(Base):
305 __tablename__ = 'group_to_perm'
320 __tablename__ = 'group_to_perm'
306 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'useexisting':True})
321 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'useexisting':True})
307
322
308 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
323 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
309 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
324 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
310 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
325 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
311 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
326 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
312
327
313 user = relationship('User')
328 user = relationship('User')
314 permission = relationship('Permission')
329 permission = relationship('Permission')
315 group = relationship('Group')
330 group = relationship('Group')
316
331
317 class Statistics(Base):
332 class Statistics(Base):
318 __tablename__ = 'statistics'
333 __tablename__ = 'statistics'
319 __table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True})
334 __table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True})
320 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
335 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
321 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
336 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
322 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
337 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
323 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
338 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
324 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
339 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
325 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
340 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
326
341
327 repository = relationship('Repository', single_parent=True)
342 repository = relationship('Repository', single_parent=True)
328
343
329 class UserFollowing(Base):
344 class UserFollowing(Base):
330 __tablename__ = 'user_followings'
345 __tablename__ = 'user_followings'
331 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
346 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
332 UniqueConstraint('user_id', 'follows_user_id')
347 UniqueConstraint('user_id', 'follows_user_id')
333 , {'useexisting':True})
348 , {'useexisting':True})
334
349
335 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
350 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
336 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
351 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
337 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
352 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
338 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
353 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
339
354
340 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
355 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
341
356
342 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
357 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
343 follows_repository = relationship('Repository', order_by='Repository.repo_name')
358 follows_repository = relationship('Repository', order_by='Repository.repo_name')
344
359
345 class CacheInvalidation(Base):
360 class CacheInvalidation(Base):
346 __tablename__ = 'cache_invalidation'
361 __tablename__ = 'cache_invalidation'
347 __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
362 __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
348 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
363 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
349 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
364 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
350 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
365 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
351 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
366 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
352
367
353
368
354 def __init__(self, cache_key, cache_args=''):
369 def __init__(self, cache_key, cache_args=''):
355 self.cache_key = cache_key
370 self.cache_key = cache_key
356 self.cache_args = cache_args
371 self.cache_args = cache_args
357 self.cache_active = False
372 self.cache_active = False
358
373
359 def __repr__(self):
374 def __repr__(self):
360 return "<%s('%s:%s')>" % (self.__class__.__name__,
375 return "<%s('%s:%s')>" % (self.__class__.__name__,
361 self.cache_id, self.cache_key)
376 self.cache_id, self.cache_key)
362
377
363 class DbMigrateVersion(Base):
378 class DbMigrateVersion(Base):
364 __tablename__ = 'db_migrate_version'
379 __tablename__ = 'db_migrate_version'
365 __table_args__ = {'useexisting':True}
380 __table_args__ = {'useexisting':True}
366 repository_id = Column('repository_id', String(250), primary_key=True)
381 repository_id = Column('repository_id', String(250), primary_key=True)
367 repository_path = Column('repository_path', Text)
382 repository_path = Column('repository_path', Text)
368 version = Column('version', Integer)
383 version = Column('version', Integer)
369
384
@@ -1,555 +1,555 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 import os
22 import os
23 import re
23 import re
24 import logging
24 import logging
25
25
26 import formencode
26 import formencode
27 from formencode import All
27 from formencode import All
28 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
28 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
29 Email, Bool, StringBoolean, Set
29 Email, Bool, StringBoolean, Set
30
30
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32 from webhelpers.pylonslib.secure_form import authentication_token
32 from webhelpers.pylonslib.secure_form import authentication_token
33
33
34 from rhodecode.lib.utils import repo_name_slug
34 from rhodecode.lib.utils import repo_name_slug
35 from rhodecode.lib.auth import authenticate, get_crypt_password
35 from rhodecode.lib.auth import authenticate, get_crypt_password
36 from rhodecode.lib.exceptions import LdapImportError
36 from rhodecode.lib.exceptions import LdapImportError
37 from rhodecode.model import meta
37 from rhodecode.model import meta
38 from rhodecode.model.user import UserModel
38 from rhodecode.model.user import UserModel
39 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.users_group import UsersGroupModel
40 from rhodecode.model.users_group import UsersGroupModel
41 from rhodecode.model.db import User, UsersGroup
41 from rhodecode.model.db import User, UsersGroup
42 from rhodecode import BACKENDS
42 from rhodecode import BACKENDS
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46 #this is needed to translate the messages using _() in validators
46 #this is needed to translate the messages using _() in validators
47 class State_obj(object):
47 class State_obj(object):
48 _ = staticmethod(_)
48 _ = staticmethod(_)
49
49
50 #===============================================================================
50 #===============================================================================
51 # VALIDATORS
51 # VALIDATORS
52 #===============================================================================
52 #===============================================================================
53 class ValidAuthToken(formencode.validators.FancyValidator):
53 class ValidAuthToken(formencode.validators.FancyValidator):
54 messages = {'invalid_token':_('Token mismatch')}
54 messages = {'invalid_token':_('Token mismatch')}
55
55
56 def validate_python(self, value, state):
56 def validate_python(self, value, state):
57
57
58 if value != authentication_token():
58 if value != authentication_token():
59 raise formencode.Invalid(self.message('invalid_token', state,
59 raise formencode.Invalid(self.message('invalid_token', state,
60 search_number=value), value, state)
60 search_number=value), value, state)
61
61
62 def ValidUsername(edit, old_data):
62 def ValidUsername(edit, old_data):
63 class _ValidUsername(formencode.validators.FancyValidator):
63 class _ValidUsername(formencode.validators.FancyValidator):
64
64
65 def validate_python(self, value, state):
65 def validate_python(self, value, state):
66 if value in ['default', 'new_user']:
66 if value in ['default', 'new_user']:
67 raise formencode.Invalid(_('Invalid username'), value, state)
67 raise formencode.Invalid(_('Invalid username'), value, state)
68 #check if user is unique
68 #check if user is unique
69 old_un = None
69 old_un = None
70 if edit:
70 if edit:
71 old_un = UserModel().get(old_data.get('user_id')).username
71 old_un = UserModel().get(old_data.get('user_id')).username
72
72
73 if old_un != value or not edit:
73 if old_un != value or not edit:
74 if UserModel().get_by_username(value, cache=False,
74 if UserModel().get_by_username(value, cache=False,
75 case_insensitive=True):
75 case_insensitive=True):
76 raise formencode.Invalid(_('This username already exists') ,
76 raise formencode.Invalid(_('This username already exists') ,
77 value, state)
77 value, state)
78
78
79
79
80 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
80 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
81 raise formencode.Invalid(_('Username may only contain '
81 raise formencode.Invalid(_('Username may only contain '
82 'alphanumeric characters underscores, '
82 'alphanumeric characters underscores, '
83 'periods or dashes and must begin with '
83 'periods or dashes and must begin with '
84 'alphanumeric character'),
84 'alphanumeric character'),
85 value, state)
85 value, state)
86
86
87
87
88
88
89 return _ValidUsername
89 return _ValidUsername
90
90
91
91
92
92
93 def ValidUsersGroup(edit, old_data):
93 def ValidUsersGroup(edit, old_data):
94
94
95 class _ValidUsersGroup(formencode.validators.FancyValidator):
95 class _ValidUsersGroup(formencode.validators.FancyValidator):
96
96
97 def validate_python(self, value, state):
97 def validate_python(self, value, state):
98 if value in ['default']:
98 if value in ['default']:
99 raise formencode.Invalid(_('Invalid group name'), value, state)
99 raise formencode.Invalid(_('Invalid group name'), value, state)
100 #check if group is unique
100 #check if group is unique
101 old_ugname = None
101 old_ugname = None
102 if edit:
102 if edit:
103 old_ugname = UsersGroupModel()\
103 old_ugname = UsersGroupModel()\
104 .get(old_data.get('users_group_id')).users_group_name
104 .get(old_data.get('users_group_id')).users_group_name
105
105
106 if old_ugname != value or not edit:
106 if old_ugname != value or not edit:
107 if UsersGroupModel().get_by_groupname(value, cache=False,
107 if UsersGroupModel().get_by_groupname(value, cache=False,
108 case_insensitive=True):
108 case_insensitive=True):
109 raise formencode.Invalid(_('This users group already exists') ,
109 raise formencode.Invalid(_('This users group already exists') ,
110 value, state)
110 value, state)
111
111
112
112
113 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
113 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
114 raise formencode.Invalid(_('Group name may only contain '
114 raise formencode.Invalid(_('Group name may only contain '
115 'alphanumeric characters underscores, '
115 'alphanumeric characters underscores, '
116 'periods or dashes and must begin with '
116 'periods or dashes and must begin with '
117 'alphanumeric character'),
117 'alphanumeric character'),
118 value, state)
118 value, state)
119
119
120 return _ValidUsersGroup
120 return _ValidUsersGroup
121
121
122
122
123
123
124 class ValidPassword(formencode.validators.FancyValidator):
124 class ValidPassword(formencode.validators.FancyValidator):
125
125
126 def to_python(self, value, state):
126 def to_python(self, value, state):
127
127
128 if value:
128 if value:
129
129
130 if value.get('password'):
130 if value.get('password'):
131 try:
131 try:
132 value['password'] = get_crypt_password(value['password'])
132 value['password'] = get_crypt_password(value['password'])
133 except UnicodeEncodeError:
133 except UnicodeEncodeError:
134 e_dict = {'password':_('Invalid characters in password')}
134 e_dict = {'password':_('Invalid characters in password')}
135 raise formencode.Invalid('', value, state, error_dict=e_dict)
135 raise formencode.Invalid('', value, state, error_dict=e_dict)
136
136
137 if value.get('password_confirmation'):
137 if value.get('password_confirmation'):
138 try:
138 try:
139 value['password_confirmation'] = \
139 value['password_confirmation'] = \
140 get_crypt_password(value['password_confirmation'])
140 get_crypt_password(value['password_confirmation'])
141 except UnicodeEncodeError:
141 except UnicodeEncodeError:
142 e_dict = {'password_confirmation':_('Invalid characters in password')}
142 e_dict = {'password_confirmation':_('Invalid characters in password')}
143 raise formencode.Invalid('', value, state, error_dict=e_dict)
143 raise formencode.Invalid('', value, state, error_dict=e_dict)
144
144
145 if value.get('new_password'):
145 if value.get('new_password'):
146 try:
146 try:
147 value['new_password'] = \
147 value['new_password'] = \
148 get_crypt_password(value['new_password'])
148 get_crypt_password(value['new_password'])
149 except UnicodeEncodeError:
149 except UnicodeEncodeError:
150 e_dict = {'new_password':_('Invalid characters in password')}
150 e_dict = {'new_password':_('Invalid characters in password')}
151 raise formencode.Invalid('', value, state, error_dict=e_dict)
151 raise formencode.Invalid('', value, state, error_dict=e_dict)
152
152
153 return value
153 return value
154
154
155 class ValidPasswordsMatch(formencode.validators.FancyValidator):
155 class ValidPasswordsMatch(formencode.validators.FancyValidator):
156
156
157 def validate_python(self, value, state):
157 def validate_python(self, value, state):
158
158
159 if value['password'] != value['password_confirmation']:
159 if value['password'] != value['password_confirmation']:
160 e_dict = {'password_confirmation':
160 e_dict = {'password_confirmation':
161 _('Password do not match')}
161 _('Password do not match')}
162 raise formencode.Invalid('', value, state, error_dict=e_dict)
162 raise formencode.Invalid('', value, state, error_dict=e_dict)
163
163
164 class ValidAuth(formencode.validators.FancyValidator):
164 class ValidAuth(formencode.validators.FancyValidator):
165 messages = {
165 messages = {
166 'invalid_password':_('invalid password'),
166 'invalid_password':_('invalid password'),
167 'invalid_login':_('invalid user name'),
167 'invalid_login':_('invalid user name'),
168 'disabled_account':_('Your account is disabled')
168 'disabled_account':_('Your account is disabled')
169
169
170 }
170 }
171 #error mapping
171 #error mapping
172 e_dict = {'username':messages['invalid_login'],
172 e_dict = {'username':messages['invalid_login'],
173 'password':messages['invalid_password']}
173 'password':messages['invalid_password']}
174 e_dict_disable = {'username':messages['disabled_account']}
174 e_dict_disable = {'username':messages['disabled_account']}
175
175
176 def validate_python(self, value, state):
176 def validate_python(self, value, state):
177 password = value['password']
177 password = value['password']
178 username = value['username']
178 username = value['username']
179 user = UserModel().get_by_username(username)
179 user = UserModel().get_by_username(username)
180
180
181 if authenticate(username, password):
181 if authenticate(username, password):
182 return value
182 return value
183 else:
183 else:
184 if user and user.active is False:
184 if user and user.active is False:
185 log.warning('user %s is disabled', username)
185 log.warning('user %s is disabled', username)
186 raise formencode.Invalid(self.message('disabled_account',
186 raise formencode.Invalid(self.message('disabled_account',
187 state=State_obj),
187 state=State_obj),
188 value, state,
188 value, state,
189 error_dict=self.e_dict_disable)
189 error_dict=self.e_dict_disable)
190 else:
190 else:
191 log.warning('user %s not authenticated', username)
191 log.warning('user %s not authenticated', username)
192 raise formencode.Invalid(self.message('invalid_password',
192 raise formencode.Invalid(self.message('invalid_password',
193 state=State_obj), value, state,
193 state=State_obj), value, state,
194 error_dict=self.e_dict)
194 error_dict=self.e_dict)
195
195
196 class ValidRepoUser(formencode.validators.FancyValidator):
196 class ValidRepoUser(formencode.validators.FancyValidator):
197
197
198 def to_python(self, value, state):
198 def to_python(self, value, state):
199 sa = meta.Session()
199 sa = meta.Session()
200 try:
200 try:
201 self.user_db = sa.query(User)\
201 self.user_db = sa.query(User)\
202 .filter(User.active == True)\
202 .filter(User.active == True)\
203 .filter(User.username == value).one()
203 .filter(User.username == value).one()
204 except Exception:
204 except Exception:
205 raise formencode.Invalid(_('This username is not valid'),
205 raise formencode.Invalid(_('This username is not valid'),
206 value, state)
206 value, state)
207 finally:
207 finally:
208 meta.Session.remove()
208 meta.Session.remove()
209
209
210 return self.user_db.user_id
210 return self.user_db.user_id
211
211
212 def ValidRepoName(edit, old_data):
212 def ValidRepoName(edit, old_data):
213 class _ValidRepoName(formencode.validators.FancyValidator):
213 class _ValidRepoName(formencode.validators.FancyValidator):
214
214
215 def to_python(self, value, state):
215 def to_python(self, value, state):
216 slug = repo_name_slug(value)
216 slug = repo_name_slug(value)
217 if slug in ['_admin']:
217 if slug in ['_admin']:
218 raise formencode.Invalid(_('This repository name is disallowed'),
218 raise formencode.Invalid(_('This repository name is disallowed'),
219 value, state)
219 value, state)
220 if old_data.get('repo_name') != value or not edit:
220 if old_data.get('repo_name') != value or not edit:
221 if RepoModel().get_by_repo_name(slug, cache=False):
221 if RepoModel().get_by_repo_name(slug, cache=False):
222 raise formencode.Invalid(_('This repository already exists') ,
222 raise formencode.Invalid(_('This repository already exists') ,
223 value, state)
223 value, state)
224 return slug
224 return slug
225
225
226
226
227 return _ValidRepoName
227 return _ValidRepoName
228
228
229 def ValidForkType(old_data):
229 def ValidForkType(old_data):
230 class _ValidForkType(formencode.validators.FancyValidator):
230 class _ValidForkType(formencode.validators.FancyValidator):
231
231
232 def to_python(self, value, state):
232 def to_python(self, value, state):
233 if old_data['repo_type'] != value:
233 if old_data['repo_type'] != value:
234 raise formencode.Invalid(_('Fork have to be the same type as original'),
234 raise formencode.Invalid(_('Fork have to be the same type as original'),
235 value, state)
235 value, state)
236 return value
236 return value
237 return _ValidForkType
237 return _ValidForkType
238
238
239 class ValidPerms(formencode.validators.FancyValidator):
239 class ValidPerms(formencode.validators.FancyValidator):
240 messages = {'perm_new_member_name':_('This username or users group name'
240 messages = {'perm_new_member_name':_('This username or users group name'
241 ' is not valid')}
241 ' is not valid')}
242
242
243 def to_python(self, value, state):
243 def to_python(self, value, state):
244 perms_update = []
244 perms_update = []
245 perms_new = []
245 perms_new = []
246 #build a list of permission to update and new permission to create
246 #build a list of permission to update and new permission to create
247 for k, v in value.items():
247 for k, v in value.items():
248 #means new added member to permissions
248 #means new added member to permissions
249 if k.startswith('perm_new_member'):
249 if k.startswith('perm_new_member'):
250 new_perm = value.get('perm_new_member', False)
250 new_perm = value.get('perm_new_member', False)
251 new_member = value.get('perm_new_member_name', False)
251 new_member = value.get('perm_new_member_name', False)
252 new_type = value.get('perm_new_member_type')
252 new_type = value.get('perm_new_member_type')
253
253
254 if new_member and new_perm:
254 if new_member and new_perm:
255 if (new_member, new_perm, new_type) not in perms_new:
255 if (new_member, new_perm, new_type) not in perms_new:
256 perms_new.append((new_member, new_perm, new_type))
256 perms_new.append((new_member, new_perm, new_type))
257 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
257 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
258 member = k[7:]
258 member = k[7:]
259 t = {'u':'user',
259 t = {'u':'user',
260 'g':'users_group'}[k[0]]
260 'g':'users_group'}[k[0]]
261 if member == 'default':
261 if member == 'default':
262 if value['private']:
262 if value['private']:
263 #set none for default when updating to private repo
263 #set none for default when updating to private repo
264 v = 'repository.none'
264 v = 'repository.none'
265 perms_update.append((member, v, t))
265 perms_update.append((member, v, t))
266
266
267 value['perms_updates'] = perms_update
267 value['perms_updates'] = perms_update
268 value['perms_new'] = perms_new
268 value['perms_new'] = perms_new
269
269
270 #update permissions
270 #update permissions
271 sa = meta.Session
271 sa = meta.Session
272 for k, v, t in perms_new:
272 for k, v, t in perms_new:
273 try:
273 try:
274 if t is 'user':
274 if t is 'user':
275 self.user_db = sa.query(User)\
275 self.user_db = sa.query(User)\
276 .filter(User.active == True)\
276 .filter(User.active == True)\
277 .filter(User.username == k).one()
277 .filter(User.username == k).one()
278 if t is 'users_group':
278 if t is 'users_group':
279 self.user_db = sa.query(UsersGroup)\
279 self.user_db = sa.query(UsersGroup)\
280 .filter(UsersGroup.users_group_active == True)\
280 .filter(UsersGroup.users_group_active == True)\
281 .filter(UsersGroup.users_group_name == k).one()
281 .filter(UsersGroup.users_group_name == k).one()
282
282
283 except Exception:
283 except Exception:
284 msg = self.message('perm_new_member_name',
284 msg = self.message('perm_new_member_name',
285 state=State_obj)
285 state=State_obj)
286 raise formencode.Invalid(msg, value, state,
286 raise formencode.Invalid(msg, value, state,
287 error_dict={'perm_new_member_name':msg})
287 error_dict={'perm_new_member_name':msg})
288 return value
288 return value
289
289
290 class ValidSettings(formencode.validators.FancyValidator):
290 class ValidSettings(formencode.validators.FancyValidator):
291
291
292 def to_python(self, value, state):
292 def to_python(self, value, state):
293 #settings form can't edit user
293 #settings form can't edit user
294 if value.has_key('user'):
294 if value.has_key('user'):
295 del['value']['user']
295 del['value']['user']
296
296
297 return value
297 return value
298
298
299 class ValidPath(formencode.validators.FancyValidator):
299 class ValidPath(formencode.validators.FancyValidator):
300 def to_python(self, value, state):
300 def to_python(self, value, state):
301
301
302 if not os.path.isdir(value):
302 if not os.path.isdir(value):
303 msg = _('This is not a valid path')
303 msg = _('This is not a valid path')
304 raise formencode.Invalid(msg, value, state,
304 raise formencode.Invalid(msg, value, state,
305 error_dict={'paths_root_path':msg})
305 error_dict={'paths_root_path':msg})
306 return value
306 return value
307
307
308 def UniqSystemEmail(old_data):
308 def UniqSystemEmail(old_data):
309 class _UniqSystemEmail(formencode.validators.FancyValidator):
309 class _UniqSystemEmail(formencode.validators.FancyValidator):
310 def to_python(self, value, state):
310 def to_python(self, value, state):
311 value = value.lower()
311 value = value.lower()
312 if old_data.get('email') != value:
312 if old_data.get('email') != value:
313 sa = meta.Session()
313 sa = meta.Session()
314 try:
314 try:
315 user = sa.query(User).filter(User.email == value).scalar()
315 user = sa.query(User).filter(User.email == value).scalar()
316 if user:
316 if user:
317 raise formencode.Invalid(_("This e-mail address is already taken") ,
317 raise formencode.Invalid(_("This e-mail address is already taken") ,
318 value, state)
318 value, state)
319 finally:
319 finally:
320 meta.Session.remove()
320 meta.Session.remove()
321
321
322 return value
322 return value
323
323
324 return _UniqSystemEmail
324 return _UniqSystemEmail
325
325
326 class ValidSystemEmail(formencode.validators.FancyValidator):
326 class ValidSystemEmail(formencode.validators.FancyValidator):
327 def to_python(self, value, state):
327 def to_python(self, value, state):
328 value = value.lower()
328 value = value.lower()
329 sa = meta.Session
329 sa = meta.Session
330 try:
330 try:
331 user = sa.query(User).filter(User.email == value).scalar()
331 user = sa.query(User).filter(User.email == value).scalar()
332 if user is None:
332 if user is None:
333 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
333 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
334 value, state)
334 value, state)
335 finally:
335 finally:
336 meta.Session.remove()
336 meta.Session.remove()
337
337
338 return value
338 return value
339
339
340 class LdapLibValidator(formencode.validators.FancyValidator):
340 class LdapLibValidator(formencode.validators.FancyValidator):
341
341
342 def to_python(self, value, state):
342 def to_python(self, value, state):
343
343
344 try:
344 try:
345 import ldap
345 import ldap
346 except ImportError:
346 except ImportError:
347 raise LdapImportError
347 raise LdapImportError
348 return value
348 return value
349
349
350 class AttrLoginValidator(formencode.validators.FancyValidator):
350 class AttrLoginValidator(formencode.validators.FancyValidator):
351
351
352 def to_python(self, value, state):
352 def to_python(self, value, state):
353
353
354 if not value or not isinstance(value, (str, unicode)):
354 if not value or not isinstance(value, (str, unicode)):
355 raise formencode.Invalid(_("The LDAP Login attribute of the CN "
355 raise formencode.Invalid(_("The LDAP Login attribute of the CN "
356 "must be specified - this is the name "
356 "must be specified - this is the name "
357 "of the attribute that is equivalent "
357 "of the attribute that is equivalent "
358 "to 'username'"),
358 "to 'username'"),
359 value, state)
359 value, state)
360
360
361 return value
361 return value
362
362
363 #===============================================================================
363 #===============================================================================
364 # FORMS
364 # FORMS
365 #===============================================================================
365 #===============================================================================
366 class LoginForm(formencode.Schema):
366 class LoginForm(formencode.Schema):
367 allow_extra_fields = True
367 allow_extra_fields = True
368 filter_extra_fields = True
368 filter_extra_fields = True
369 username = UnicodeString(
369 username = UnicodeString(
370 strip=True,
370 strip=True,
371 min=1,
371 min=1,
372 not_empty=True,
372 not_empty=True,
373 messages={
373 messages={
374 'empty':_('Please enter a login'),
374 'empty':_('Please enter a login'),
375 'tooShort':_('Enter a value %(min)i characters long or more')}
375 'tooShort':_('Enter a value %(min)i characters long or more')}
376 )
376 )
377
377
378 password = UnicodeString(
378 password = UnicodeString(
379 strip=True,
379 strip=True,
380 min=6,
380 min=6,
381 not_empty=True,
381 not_empty=True,
382 messages={
382 messages={
383 'empty':_('Please enter a password'),
383 'empty':_('Please enter a password'),
384 'tooShort':_('Enter %(min)i characters or more')}
384 'tooShort':_('Enter %(min)i characters or more')}
385 )
385 )
386
386
387
387
388 #chained validators have access to all data
388 #chained validators have access to all data
389 chained_validators = [ValidAuth]
389 chained_validators = [ValidAuth]
390
390
391 def UserForm(edit=False, old_data={}):
391 def UserForm(edit=False, old_data={}):
392 class _UserForm(formencode.Schema):
392 class _UserForm(formencode.Schema):
393 allow_extra_fields = True
393 allow_extra_fields = True
394 filter_extra_fields = True
394 filter_extra_fields = True
395 username = All(UnicodeString(strip=True, min=1, not_empty=True),
395 username = All(UnicodeString(strip=True, min=1, not_empty=True),
396 ValidUsername(edit, old_data))
396 ValidUsername(edit, old_data))
397 if edit:
397 if edit:
398 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
398 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
399 admin = StringBoolean(if_missing=False)
399 admin = StringBoolean(if_missing=False)
400 else:
400 else:
401 password = All(UnicodeString(strip=True, min=6, not_empty=True))
401 password = All(UnicodeString(strip=True, min=6, not_empty=True))
402 active = StringBoolean(if_missing=False)
402 active = StringBoolean(if_missing=False)
403 name = UnicodeString(strip=True, min=1, not_empty=True)
403 name = UnicodeString(strip=True, min=1, not_empty=True)
404 lastname = UnicodeString(strip=True, min=1, not_empty=True)
404 lastname = UnicodeString(strip=True, min=1, not_empty=True)
405 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
405 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
406
406
407 chained_validators = [ValidPassword]
407 chained_validators = [ValidPassword]
408
408
409 return _UserForm
409 return _UserForm
410
410
411
411
412 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
412 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
413 class _UsersGroupForm(formencode.Schema):
413 class _UsersGroupForm(formencode.Schema):
414 allow_extra_fields = True
414 allow_extra_fields = True
415 filter_extra_fields = True
415 filter_extra_fields = True
416
416
417 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
417 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
418 ValidUsersGroup(edit, old_data))
418 ValidUsersGroup(edit, old_data))
419
419
420 users_group_active = StringBoolean(if_missing=False)
420 users_group_active = StringBoolean(if_missing=False)
421
421
422 if edit:
422 if edit:
423 users_group_members = OneOf(available_members, hideList=False,
423 users_group_members = OneOf(available_members, hideList=False,
424 testValueList=True,
424 testValueList=True,
425 if_missing=None, not_empty=False)
425 if_missing=None, not_empty=False)
426
426
427 return _UsersGroupForm
427 return _UsersGroupForm
428
428
429 def RegisterForm(edit=False, old_data={}):
429 def RegisterForm(edit=False, old_data={}):
430 class _RegisterForm(formencode.Schema):
430 class _RegisterForm(formencode.Schema):
431 allow_extra_fields = True
431 allow_extra_fields = True
432 filter_extra_fields = True
432 filter_extra_fields = True
433 username = All(ValidUsername(edit, old_data),
433 username = All(ValidUsername(edit, old_data),
434 UnicodeString(strip=True, min=1, not_empty=True))
434 UnicodeString(strip=True, min=1, not_empty=True))
435 password = All(UnicodeString(strip=True, min=6, not_empty=True))
435 password = All(UnicodeString(strip=True, min=6, not_empty=True))
436 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
436 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
437 active = StringBoolean(if_missing=False)
437 active = StringBoolean(if_missing=False)
438 name = UnicodeString(strip=True, min=1, not_empty=True)
438 name = UnicodeString(strip=True, min=1, not_empty=True)
439 lastname = UnicodeString(strip=True, min=1, not_empty=True)
439 lastname = UnicodeString(strip=True, min=1, not_empty=True)
440 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
440 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
441
441
442 chained_validators = [ValidPasswordsMatch, ValidPassword]
442 chained_validators = [ValidPasswordsMatch, ValidPassword]
443
443
444 return _RegisterForm
444 return _RegisterForm
445
445
446 def PasswordResetForm():
446 def PasswordResetForm():
447 class _PasswordResetForm(formencode.Schema):
447 class _PasswordResetForm(formencode.Schema):
448 allow_extra_fields = True
448 allow_extra_fields = True
449 filter_extra_fields = True
449 filter_extra_fields = True
450 email = All(ValidSystemEmail(), Email(not_empty=True))
450 email = All(ValidSystemEmail(), Email(not_empty=True))
451 return _PasswordResetForm
451 return _PasswordResetForm
452
452
453 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
453 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
454 repo_groups=[]):
454 repo_groups=[]):
455 class _RepoForm(formencode.Schema):
455 class _RepoForm(formencode.Schema):
456 allow_extra_fields = True
456 allow_extra_fields = True
457 filter_extra_fields = False
457 filter_extra_fields = False
458 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
458 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
459 ValidRepoName(edit, old_data))
459 ValidRepoName(edit, old_data))
460 clone_uri = UnicodeString(strip=True, min=1, not_empty=False)
460 clone_uri = UnicodeString(strip=True, min=1, not_empty=False)
461 repo_group = OneOf(repo_groups)
461 repo_group = OneOf(repo_groups, hideList=True)
462 repo_type = OneOf(supported_backends)
462 repo_type = OneOf(supported_backends)
463 description = UnicodeString(strip=True, min=1, not_empty=True)
463 description = UnicodeString(strip=True, min=1, not_empty=True)
464 private = StringBoolean(if_missing=False)
464 private = StringBoolean(if_missing=False)
465 enable_statistics = StringBoolean(if_missing=False)
465 enable_statistics = StringBoolean(if_missing=False)
466 enable_downloads = StringBoolean(if_missing=False)
466 enable_downloads = StringBoolean(if_missing=False)
467
467
468 if edit:
468 if edit:
469 #this is repo owner
469 #this is repo owner
470 user = All(Int(not_empty=True), ValidRepoUser)
470 user = All(Int(not_empty=True), ValidRepoUser)
471
471
472 chained_validators = [ValidPerms]
472 chained_validators = [ValidPerms]
473 return _RepoForm
473 return _RepoForm
474
474
475 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
475 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
476 class _RepoForkForm(formencode.Schema):
476 class _RepoForkForm(formencode.Schema):
477 allow_extra_fields = True
477 allow_extra_fields = True
478 filter_extra_fields = False
478 filter_extra_fields = False
479 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
479 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
480 ValidRepoName(edit, old_data))
480 ValidRepoName(edit, old_data))
481 description = UnicodeString(strip=True, min=1, not_empty=True)
481 description = UnicodeString(strip=True, min=1, not_empty=True)
482 private = StringBoolean(if_missing=False)
482 private = StringBoolean(if_missing=False)
483 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
483 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
484 return _RepoForkForm
484 return _RepoForkForm
485
485
486 def RepoSettingsForm(edit=False, old_data={}):
486 def RepoSettingsForm(edit=False, old_data={}):
487 class _RepoForm(formencode.Schema):
487 class _RepoForm(formencode.Schema):
488 allow_extra_fields = True
488 allow_extra_fields = True
489 filter_extra_fields = False
489 filter_extra_fields = False
490 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
490 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
491 ValidRepoName(edit, old_data))
491 ValidRepoName(edit, old_data))
492 description = UnicodeString(strip=True, min=1, not_empty=True)
492 description = UnicodeString(strip=True, min=1, not_empty=True)
493 private = StringBoolean(if_missing=False)
493 private = StringBoolean(if_missing=False)
494
494
495 chained_validators = [ValidPerms, ValidSettings]
495 chained_validators = [ValidPerms, ValidSettings]
496 return _RepoForm
496 return _RepoForm
497
497
498
498
499 def ApplicationSettingsForm():
499 def ApplicationSettingsForm():
500 class _ApplicationSettingsForm(formencode.Schema):
500 class _ApplicationSettingsForm(formencode.Schema):
501 allow_extra_fields = True
501 allow_extra_fields = True
502 filter_extra_fields = False
502 filter_extra_fields = False
503 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
503 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
504 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
504 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
505 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
505 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
506
506
507 return _ApplicationSettingsForm
507 return _ApplicationSettingsForm
508
508
509 def ApplicationUiSettingsForm():
509 def ApplicationUiSettingsForm():
510 class _ApplicationUiSettingsForm(formencode.Schema):
510 class _ApplicationUiSettingsForm(formencode.Schema):
511 allow_extra_fields = True
511 allow_extra_fields = True
512 filter_extra_fields = False
512 filter_extra_fields = False
513 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
513 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
514 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
514 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
515 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
515 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
516 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
516 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
517 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
517 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
518 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
518 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
519
519
520 return _ApplicationUiSettingsForm
520 return _ApplicationUiSettingsForm
521
521
522 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
522 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
523 class _DefaultPermissionsForm(formencode.Schema):
523 class _DefaultPermissionsForm(formencode.Schema):
524 allow_extra_fields = True
524 allow_extra_fields = True
525 filter_extra_fields = True
525 filter_extra_fields = True
526 overwrite_default = StringBoolean(if_missing=False)
526 overwrite_default = StringBoolean(if_missing=False)
527 anonymous = OneOf(['True', 'False'], if_missing=False)
527 anonymous = OneOf(['True', 'False'], if_missing=False)
528 default_perm = OneOf(perms_choices)
528 default_perm = OneOf(perms_choices)
529 default_register = OneOf(register_choices)
529 default_register = OneOf(register_choices)
530 default_create = OneOf(create_choices)
530 default_create = OneOf(create_choices)
531
531
532 return _DefaultPermissionsForm
532 return _DefaultPermissionsForm
533
533
534
534
535 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices):
535 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices):
536 class _LdapSettingsForm(formencode.Schema):
536 class _LdapSettingsForm(formencode.Schema):
537 allow_extra_fields = True
537 allow_extra_fields = True
538 filter_extra_fields = True
538 filter_extra_fields = True
539 pre_validators = [LdapLibValidator]
539 pre_validators = [LdapLibValidator]
540 ldap_active = StringBoolean(if_missing=False)
540 ldap_active = StringBoolean(if_missing=False)
541 ldap_host = UnicodeString(strip=True,)
541 ldap_host = UnicodeString(strip=True,)
542 ldap_port = Number(strip=True,)
542 ldap_port = Number(strip=True,)
543 ldap_ldaps = StringBoolean(if_missing=False)
543 ldap_ldaps = StringBoolean(if_missing=False)
544 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
544 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
545 ldap_dn_user = UnicodeString(strip=True,)
545 ldap_dn_user = UnicodeString(strip=True,)
546 ldap_dn_pass = UnicodeString(strip=True,)
546 ldap_dn_pass = UnicodeString(strip=True,)
547 ldap_base_dn = UnicodeString(strip=True,)
547 ldap_base_dn = UnicodeString(strip=True,)
548 ldap_filter = UnicodeString(strip=True,)
548 ldap_filter = UnicodeString(strip=True,)
549 ldap_search_scope = OneOf(search_scope_choices)
549 ldap_search_scope = OneOf(search_scope_choices)
550 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
550 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
551 ldap_attr_firstname = UnicodeString(strip=True,)
551 ldap_attr_firstname = UnicodeString(strip=True,)
552 ldap_attr_lastname = UnicodeString(strip=True,)
552 ldap_attr_lastname = UnicodeString(strip=True,)
553 ldap_attr_email = UnicodeString(strip=True,)
553 ldap_attr_email = UnicodeString(strip=True,)
554
554
555 return _LdapSettingsForm
555 return _LdapSettingsForm
@@ -1,383 +1,383 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Edit repository')} ${c.repo_info.repo_name} - ${c.rhodecode_name}
5 ${_('Edit repository')} ${c.repo_info.repo_name} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
10 &raquo;
11 ${h.link_to(_('Repositories'),h.url('repos'))}
11 ${h.link_to(_('Repositories'),h.url('repos'))}
12 &raquo;
12 &raquo;
13 ${_('edit')} &raquo; ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 ${_('edit')} &raquo; ${h.link_to(c.repo_info.just_name,h.url('summary_home',repo_name=c.repo_name))}
14 </%def>
14 </%def>
15
15
16 <%def name="page_nav()">
16 <%def name="page_nav()">
17 ${self.menu('admin')}
17 ${self.menu('admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box box-left">
21 <div class="box box-left">
22 <!-- box / title -->
22 <!-- box / title -->
23 <div class="title">
23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 </div>
25 </div>
26 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
26 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
27 <div class="form">
27 <div class="form">
28 <!-- fields -->
28 <!-- fields -->
29 <div class="fields">
29 <div class="fields">
30 <div class="field">
30 <div class="field">
31 <div class="label">
31 <div class="label">
32 <label for="repo_name">${_('Name')}:</label>
32 <label for="repo_name">${_('Name')}:</label>
33 </div>
33 </div>
34 <div class="input">
34 <div class="input">
35 ${h.text('repo_name',class_="medium")}
35 ${h.text('repo_name',class_="medium")}
36 </div>
36 </div>
37 </div>
37 </div>
38 <div class="field">
38 <div class="field">
39 <div class="label">
39 <div class="label">
40 <label for="clone_uri">${_('Clone uri')}:</label>
40 <label for="clone_uri">${_('Clone uri')}:</label>
41 </div>
41 </div>
42 <div class="input">
42 <div class="input">
43 ${h.text('clone_uri',class_="small")}
43 ${h.text('clone_uri',class_="medium")}
44 </div>
44 </div>
45 </div>
45 </div>
46 <div class="field">
46 <div class="field">
47 <div class="label">
47 <div class="label">
48 <label for="repo_group">${_('Repository group')}:</label>
48 <label for="repo_group">${_('Repository group')}:</label>
49 </div>
49 </div>
50 <div class="input">
50 <div class="input">
51 ${h.select('repo_group','',c.repo_groups,class_="medium")}
51 ${h.select('repo_group','',c.repo_groups,class_="medium")}
52 <span>${h.link_to(_('add new group'),h.url(''))}</span>
52 <span>${h.link_to(_('add new group'),h.url(''))}</span>
53 </div>
53 </div>
54 </div>
54 </div>
55 <div class="field">
55 <div class="field">
56 <div class="label">
56 <div class="label">
57 <label for="repo_type">${_('Type')}:</label>
57 <label for="repo_type">${_('Type')}:</label>
58 </div>
58 </div>
59 <div class="input">
59 <div class="input">
60 ${h.select('repo_type','hg',c.backends,class_="medium")}
60 ${h.select('repo_type','hg',c.backends,class_="medium")}
61 </div>
61 </div>
62 </div>
62 </div>
63 <div class="field">
63 <div class="field">
64 <div class="label label-textarea">
64 <div class="label label-textarea">
65 <label for="description">${_('Description')}:</label>
65 <label for="description">${_('Description')}:</label>
66 </div>
66 </div>
67 <div class="textarea text-area editor">
67 <div class="textarea text-area editor">
68 ${h.textarea('description',cols=23,rows=5)}
68 ${h.textarea('description',cols=23,rows=5)}
69 </div>
69 </div>
70 </div>
70 </div>
71
71
72 <div class="field">
72 <div class="field">
73 <div class="label label-checkbox">
73 <div class="label label-checkbox">
74 <label for="private">${_('Private')}:</label>
74 <label for="private">${_('Private')}:</label>
75 </div>
75 </div>
76 <div class="checkboxes">
76 <div class="checkboxes">
77 ${h.checkbox('private',value="True")}
77 ${h.checkbox('private',value="True")}
78 </div>
78 </div>
79 </div>
79 </div>
80 <div class="field">
80 <div class="field">
81 <div class="label label-checkbox">
81 <div class="label label-checkbox">
82 <label for="enable_statistics">${_('Enable statistics')}:</label>
82 <label for="enable_statistics">${_('Enable statistics')}:</label>
83 </div>
83 </div>
84 <div class="checkboxes">
84 <div class="checkboxes">
85 ${h.checkbox('enable_statistics',value="True")}
85 ${h.checkbox('enable_statistics',value="True")}
86 </div>
86 </div>
87 </div>
87 </div>
88 <div class="field">
88 <div class="field">
89 <div class="label label-checkbox">
89 <div class="label label-checkbox">
90 <label for="enable_downloads">${_('Enable downloads')}:</label>
90 <label for="enable_downloads">${_('Enable downloads')}:</label>
91 </div>
91 </div>
92 <div class="checkboxes">
92 <div class="checkboxes">
93 ${h.checkbox('enable_downloads',value="True")}
93 ${h.checkbox('enable_downloads',value="True")}
94 </div>
94 </div>
95 </div>
95 </div>
96 <div class="field">
96 <div class="field">
97 <div class="label">
97 <div class="label">
98 <label for="user">${_('Owner')}:</label>
98 <label for="user">${_('Owner')}:</label>
99 </div>
99 </div>
100 <div class="input input-small ac">
100 <div class="input input-small ac">
101 <div class="perm_ac">
101 <div class="perm_ac">
102 ${h.text('user',class_='yui-ac-input')}
102 ${h.text('user',class_='yui-ac-input')}
103 <div id="owner_container"></div>
103 <div id="owner_container"></div>
104 </div>
104 </div>
105 </div>
105 </div>
106 </div>
106 </div>
107
107
108 <div class="field">
108 <div class="field">
109 <div class="label">
109 <div class="label">
110 <label for="input">${_('Permissions')}:</label>
110 <label for="input">${_('Permissions')}:</label>
111 </div>
111 </div>
112 <div class="input">
112 <div class="input">
113 <%include file="repo_edit_perms.html"/>
113 <%include file="repo_edit_perms.html"/>
114 </div>
114 </div>
115
115
116 <div class="buttons">
116 <div class="buttons">
117 ${h.submit('save','Save',class_="ui-button")}
117 ${h.submit('save','Save',class_="ui-button")}
118 ${h.reset('reset','Reset',class_="ui-button")}
118 ${h.reset('reset','Reset',class_="ui-button")}
119 </div>
119 </div>
120 </div>
120 </div>
121 </div>
121 </div>
122 </div>
122 </div>
123 ${h.end_form()}
123 ${h.end_form()}
124 <script type="text/javascript">
124 <script type="text/javascript">
125 YAHOO.util.Event.onDOMReady(function(){
125 YAHOO.util.Event.onDOMReady(function(){
126 var D = YAHOO.util.Dom;
126 var D = YAHOO.util.Dom;
127 if(!D.hasClass('perm_new_member_name','error')){
127 if(!D.hasClass('perm_new_member_name','error')){
128 D.setStyle('add_perm_input','display','none');
128 D.setStyle('add_perm_input','display','none');
129 }
129 }
130 YAHOO.util.Event.addListener('add_perm','click',function(){
130 YAHOO.util.Event.addListener('add_perm','click',function(){
131 D.setStyle('add_perm_input','display','');
131 D.setStyle('add_perm_input','display','');
132 D.setStyle('add_perm','opacity','0.6');
132 D.setStyle('add_perm','opacity','0.6');
133 D.setStyle('add_perm','cursor','default');
133 D.setStyle('add_perm','cursor','default');
134 });
134 });
135 });
135 });
136 </script>
136 </script>
137 <script type="text/javascript">
137 <script type="text/javascript">
138 YAHOO.example.FnMultipleFields = function(){
138 YAHOO.example.FnMultipleFields = function(){
139 var myUsers = ${c.users_array|n};
139 var myUsers = ${c.users_array|n};
140 var myGroups = ${c.users_groups_array|n};
140 var myGroups = ${c.users_groups_array|n};
141
141
142 // Define a custom search function for the DataSource of users
142 // Define a custom search function for the DataSource of users
143 var matchUsers = function(sQuery) {
143 var matchUsers = function(sQuery) {
144 // Case insensitive matching
144 // Case insensitive matching
145 var query = sQuery.toLowerCase();
145 var query = sQuery.toLowerCase();
146 var i=0;
146 var i=0;
147 var l=myUsers.length;
147 var l=myUsers.length;
148 var matches = [];
148 var matches = [];
149
149
150 // Match against each name of each contact
150 // Match against each name of each contact
151 for(; i<l; i++) {
151 for(; i<l; i++) {
152 contact = myUsers[i];
152 contact = myUsers[i];
153 if((contact.fname.toLowerCase().indexOf(query) > -1) ||
153 if((contact.fname.toLowerCase().indexOf(query) > -1) ||
154 (contact.lname.toLowerCase().indexOf(query) > -1) ||
154 (contact.lname.toLowerCase().indexOf(query) > -1) ||
155 (contact.nname && (contact.nname.toLowerCase().indexOf(query) > -1))) {
155 (contact.nname && (contact.nname.toLowerCase().indexOf(query) > -1))) {
156 matches[matches.length] = contact;
156 matches[matches.length] = contact;
157 }
157 }
158 }
158 }
159 return matches;
159 return matches;
160 };
160 };
161
161
162 // Define a custom search function for the DataSource of usersGroups
162 // Define a custom search function for the DataSource of usersGroups
163 var matchGroups = function(sQuery) {
163 var matchGroups = function(sQuery) {
164 // Case insensitive matching
164 // Case insensitive matching
165 var query = sQuery.toLowerCase();
165 var query = sQuery.toLowerCase();
166 var i=0;
166 var i=0;
167 var l=myGroups.length;
167 var l=myGroups.length;
168 var matches = [];
168 var matches = [];
169
169
170 // Match against each name of each contact
170 // Match against each name of each contact
171 for(; i<l; i++) {
171 for(; i<l; i++) {
172 matched_group = myGroups[i];
172 matched_group = myGroups[i];
173 if(matched_group.grname.toLowerCase().indexOf(query) > -1) {
173 if(matched_group.grname.toLowerCase().indexOf(query) > -1) {
174 matches[matches.length] = matched_group;
174 matches[matches.length] = matched_group;
175 }
175 }
176 }
176 }
177 return matches;
177 return matches;
178 };
178 };
179
179
180 //match all
180 //match all
181 var matchAll = function(sQuery){
181 var matchAll = function(sQuery){
182 u = matchUsers(sQuery);
182 u = matchUsers(sQuery);
183 g = matchGroups(sQuery);
183 g = matchGroups(sQuery);
184 return u.concat(g);
184 return u.concat(g);
185 };
185 };
186
186
187 // DataScheme for members
187 // DataScheme for members
188 var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
188 var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
189 memberDS.responseSchema = {
189 memberDS.responseSchema = {
190 fields: ["id", "fname", "lname", "nname", "grname", "grmembers"]
190 fields: ["id", "fname", "lname", "nname", "grname", "grmembers"]
191 };
191 };
192
192
193 // DataScheme for owner
193 // DataScheme for owner
194 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
194 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
195 ownerDS.responseSchema = {
195 ownerDS.responseSchema = {
196 fields: ["id", "fname", "lname", "nname"]
196 fields: ["id", "fname", "lname", "nname"]
197 };
197 };
198
198
199 // Instantiate AutoComplete for perms
199 // Instantiate AutoComplete for perms
200 var membersAC = new YAHOO.widget.AutoComplete("perm_new_member_name", "perm_container", memberDS);
200 var membersAC = new YAHOO.widget.AutoComplete("perm_new_member_name", "perm_container", memberDS);
201 membersAC.useShadow = false;
201 membersAC.useShadow = false;
202 membersAC.resultTypeList = false;
202 membersAC.resultTypeList = false;
203
203
204 // Instantiate AutoComplete for owner
204 // Instantiate AutoComplete for owner
205 var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
205 var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
206 ownerAC.useShadow = false;
206 ownerAC.useShadow = false;
207 ownerAC.resultTypeList = false;
207 ownerAC.resultTypeList = false;
208
208
209
209
210 // Helper highlight function for the formatter
210 // Helper highlight function for the formatter
211 var highlightMatch = function(full, snippet, matchindex) {
211 var highlightMatch = function(full, snippet, matchindex) {
212 return full.substring(0, matchindex) +
212 return full.substring(0, matchindex) +
213 "<span class='match'>" +
213 "<span class='match'>" +
214 full.substr(matchindex, snippet.length) +
214 full.substr(matchindex, snippet.length) +
215 "</span>" +
215 "</span>" +
216 full.substring(matchindex + snippet.length);
216 full.substring(matchindex + snippet.length);
217 };
217 };
218
218
219 // Custom formatter to highlight the matching letters
219 // Custom formatter to highlight the matching letters
220 var custom_formatter = function(oResultData, sQuery, sResultMatch) {
220 var custom_formatter = function(oResultData, sQuery, sResultMatch) {
221 var query = sQuery.toLowerCase();
221 var query = sQuery.toLowerCase();
222
222
223 if (oResultData.grname != undefined){
223 if (oResultData.grname != undefined){
224 var grname = oResultData.grname;
224 var grname = oResultData.grname;
225 var grmembers = oResultData.grmembers;
225 var grmembers = oResultData.grmembers;
226 var grnameMatchIndex = grname.toLowerCase().indexOf(query);
226 var grnameMatchIndex = grname.toLowerCase().indexOf(query);
227 var grprefix = "${_('Group')}: ";
227 var grprefix = "${_('Group')}: ";
228 var grsuffix = " ("+grmembers+" ${_('members')})";
228 var grsuffix = " ("+grmembers+" ${_('members')})";
229
229
230 if (grnameMatchIndex > -1){
230 if (grnameMatchIndex > -1){
231 return grprefix+highlightMatch(grname,query,grnameMatchIndex)+grsuffix;
231 return grprefix+highlightMatch(grname,query,grnameMatchIndex)+grsuffix;
232 }
232 }
233
233
234 return grprefix+oResultData.grname+grsuffix;
234 return grprefix+oResultData.grname+grsuffix;
235 }
235 }
236 else if(oResultData.fname != undefined){
236 else if(oResultData.fname != undefined){
237
237
238 var fname = oResultData.fname,
238 var fname = oResultData.fname,
239 lname = oResultData.lname,
239 lname = oResultData.lname,
240 nname = oResultData.nname || "", // Guard against null value
240 nname = oResultData.nname || "", // Guard against null value
241 fnameMatchIndex = fname.toLowerCase().indexOf(query),
241 fnameMatchIndex = fname.toLowerCase().indexOf(query),
242 lnameMatchIndex = lname.toLowerCase().indexOf(query),
242 lnameMatchIndex = lname.toLowerCase().indexOf(query),
243 nnameMatchIndex = nname.toLowerCase().indexOf(query),
243 nnameMatchIndex = nname.toLowerCase().indexOf(query),
244 displayfname, displaylname, displaynname;
244 displayfname, displaylname, displaynname;
245
245
246 if(fnameMatchIndex > -1) {
246 if(fnameMatchIndex > -1) {
247 displayfname = highlightMatch(fname, query, fnameMatchIndex);
247 displayfname = highlightMatch(fname, query, fnameMatchIndex);
248 }
248 }
249 else {
249 else {
250 displayfname = fname;
250 displayfname = fname;
251 }
251 }
252
252
253 if(lnameMatchIndex > -1) {
253 if(lnameMatchIndex > -1) {
254 displaylname = highlightMatch(lname, query, lnameMatchIndex);
254 displaylname = highlightMatch(lname, query, lnameMatchIndex);
255 }
255 }
256 else {
256 else {
257 displaylname = lname;
257 displaylname = lname;
258 }
258 }
259
259
260 if(nnameMatchIndex > -1) {
260 if(nnameMatchIndex > -1) {
261 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
261 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
262 }
262 }
263 else {
263 else {
264 displaynname = nname ? "(" + nname + ")" : "";
264 displaynname = nname ? "(" + nname + ")" : "";
265 }
265 }
266
266
267 return displayfname + " " + displaylname + " " + displaynname;
267 return displayfname + " " + displaylname + " " + displaynname;
268 }
268 }
269 else{
269 else{
270 return '';
270 return '';
271 }
271 }
272 };
272 };
273 membersAC.formatResult = custom_formatter;
273 membersAC.formatResult = custom_formatter;
274 ownerAC.formatResult = custom_formatter;
274 ownerAC.formatResult = custom_formatter;
275
275
276 var myHandler = function(sType, aArgs) {
276 var myHandler = function(sType, aArgs) {
277
277
278 var myAC = aArgs[0]; // reference back to the AC instance
278 var myAC = aArgs[0]; // reference back to the AC instance
279 var elLI = aArgs[1]; // reference to the selected LI element
279 var elLI = aArgs[1]; // reference to the selected LI element
280 var oData = aArgs[2]; // object literal of selected item's result data
280 var oData = aArgs[2]; // object literal of selected item's result data
281
281
282 //fill the autocomplete with value
282 //fill the autocomplete with value
283 if(oData.nname != undefined){
283 if(oData.nname != undefined){
284 //users
284 //users
285 myAC.getInputEl().value = oData.nname;
285 myAC.getInputEl().value = oData.nname;
286 YUD.get('perm_new_member_type').value = 'user';
286 YUD.get('perm_new_member_type').value = 'user';
287 }
287 }
288 else{
288 else{
289 //groups
289 //groups
290 myAC.getInputEl().value = oData.grname;
290 myAC.getInputEl().value = oData.grname;
291 YUD.get('perm_new_member_type').value = 'users_group';
291 YUD.get('perm_new_member_type').value = 'users_group';
292 }
292 }
293
293
294 };
294 };
295
295
296 membersAC.itemSelectEvent.subscribe(myHandler);
296 membersAC.itemSelectEvent.subscribe(myHandler);
297 ownerAC.itemSelectEvent.subscribe(myHandler);
297 ownerAC.itemSelectEvent.subscribe(myHandler);
298
298
299 return {
299 return {
300 memberDS: memberDS,
300 memberDS: memberDS,
301 ownerDS: ownerDS,
301 ownerDS: ownerDS,
302 membersAC: membersAC,
302 membersAC: membersAC,
303 ownerAC: ownerAC,
303 ownerAC: ownerAC,
304 };
304 };
305 }();
305 }();
306
306
307 </script>
307 </script>
308
308
309 </div>
309 </div>
310
310
311 <div class="box box-right">
311 <div class="box box-right">
312 <div class="title">
312 <div class="title">
313 <h5>${_('Administration')}</h5>
313 <h5>${_('Administration')}</h5>
314 </div>
314 </div>
315
315
316 <h3>${_('Statistics')}</h3>
316 <h3>${_('Statistics')}</h3>
317 ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')}
317 ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')}
318 <div class="form">
318 <div class="form">
319 <div class="fields">
319 <div class="fields">
320 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="refresh_icon action_button",onclick="return confirm('Confirm to remove current statistics');")}
320 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="refresh_icon action_button",onclick="return confirm('Confirm to remove current statistics');")}
321 <div class="field" style="border:none">
321 <div class="field" style="border:none">
322 <ul>
322 <ul>
323 <li>${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}</li>
323 <li>${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}</li>
324 <li>${_('Percentage of stats gathered')}: ${c.stats_percentage} %</li>
324 <li>${_('Percentage of stats gathered')}: ${c.stats_percentage} %</li>
325 </ul>
325 </ul>
326 </div>
326 </div>
327
327
328 </div>
328 </div>
329 </div>
329 </div>
330 ${h.end_form()}
330 ${h.end_form()}
331
331
332 %if c.repo_info.clone_uri:
332 %if c.repo_info.clone_uri:
333 <h3>${_('Remote')}</h3>
333 <h3>${_('Remote')}</h3>
334 ${h.form(url('repo_pull', repo_name=c.repo_info.repo_name),method='put')}
334 ${h.form(url('repo_pull', repo_name=c.repo_info.repo_name),method='put')}
335 <div class="form">
335 <div class="form">
336 <div class="fields">
336 <div class="fields">
337 ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="pull_icon action_button",onclick="return confirm('Confirm to pull changes from remote side');")}
337 ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="pull_icon action_button",onclick="return confirm('Confirm to pull changes from remote side');")}
338 <div class="field" style="border:none">
338 <div class="field" style="border:none">
339 <ul>
339 <ul>
340 <li><a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri}</a></li>
340 <li><a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri}</a></li>
341 </ul>
341 </ul>
342 </div>
342 </div>
343 </div>
343 </div>
344 </div>
344 </div>
345 ${h.end_form()}
345 ${h.end_form()}
346 %endif
346 %endif
347
347
348 <h3>${_('Cache')}</h3>
348 <h3>${_('Cache')}</h3>
349 ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')}
349 ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')}
350 <div class="form">
350 <div class="form">
351 <div class="fields">
351 <div class="fields">
352 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="refresh_icon action_button",onclick="return confirm('Confirm to invalidate repository cache');")}
352 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="refresh_icon action_button",onclick="return confirm('Confirm to invalidate repository cache');")}
353 </div>
353 </div>
354 </div>
354 </div>
355 ${h.end_form()}
355 ${h.end_form()}
356
356
357 <h3>${_('Public journal')}</h3>
357 <h3>${_('Public journal')}</h3>
358 ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')}
358 ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')}
359 <div class="form">
359 <div class="form">
360 <div class="fields">
360 <div class="fields">
361 ${h.hidden('auth_token',str(h.get_token()))}
361 ${h.hidden('auth_token',str(h.get_token()))}
362 %if c.in_public_journal:
362 %if c.in_public_journal:
363 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="stop_following_icon action_button")}
363 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="stop_following_icon action_button")}
364 %else:
364 %else:
365 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="start_following_icon action_button")}
365 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="start_following_icon action_button")}
366 %endif
366 %endif
367 </div>
367 </div>
368 </div>
368 </div>
369 ${h.end_form()}
369 ${h.end_form()}
370
370
371 <h3>${_('Delete')}</h3>
371 <h3>${_('Delete')}</h3>
372 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
372 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
373 <div class="form">
373 <div class="form">
374 <div class="fields">
374 <div class="fields">
375 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
375 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
376 </div>
376 </div>
377 </div>
377 </div>
378 ${h.end_form()}
378 ${h.end_form()}
379
379
380 </div>
380 </div>
381
381
382
382
383 </%def> No newline at end of file
383 </%def>
General Comments 0
You need to be logged in to leave comments. Login now