##// END OF EJS Templates
fixes #260 Put repo in group, then move group to another group -> repo becomes unavailable
marcink -
r1539:bd604cf7 beta
parent child Browse files
Show More
@@ -1,431 +1,431
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 modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import formencode
28 import formencode
29 from operator import itemgetter
29 from operator import itemgetter
30 from formencode import htmlfill
30 from formencode import htmlfill
31
31
32 from paste.httpexceptions import HTTPInternalServerError
32 from paste.httpexceptions import HTTPInternalServerError
33 from pylons import request, response, session, tmpl_context as c, url
33 from pylons import request, response, session, tmpl_context as c, url
34 from pylons.controllers.util import abort, redirect
34 from pylons.controllers.util import abort, redirect
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
39 HasPermissionAnyDecorator
39 HasPermissionAnyDecorator
40 from rhodecode.lib.base import BaseController, render
40 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
41 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
42 from rhodecode.lib.helpers import get_token
42 from rhodecode.lib.helpers import get_token
43 from rhodecode.model.db import User, Repository, UserFollowing, Group
43 from rhodecode.model.db import User, Repository, UserFollowing, Group
44 from rhodecode.model.forms import RepoForm
44 from rhodecode.model.forms import RepoForm
45 from rhodecode.model.scm import ScmModel
45 from rhodecode.model.scm import ScmModel
46 from rhodecode.model.repo import RepoModel
46 from rhodecode.model.repo import RepoModel
47 from sqlalchemy.exc import IntegrityError
47 from sqlalchemy.exc import IntegrityError
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
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 def __load_defaults(self):
66 def __load_defaults(self):
67 repo_model = RepoModel()
67 repo_model = RepoModel()
68
68
69 c.repo_groups = [('', '')]
69 c.repo_groups = [('', '')]
70 parents_link = lambda k: h.literal('&raquo;'.join(
70 parents_link = lambda k: h.literal('&raquo;'.join(
71 map(lambda k: k.group_name,
71 map(lambda k: k.group_name,
72 k.parents + [k])
72 k.parents + [k])
73 )
73 )
74 )
74 )
75
75
76 c.repo_groups.extend([(x.group_id, parents_link(x)) for \
76 c.repo_groups.extend([(x.group_id, parents_link(x)) for \
77 x in self.sa.query(Group).all()])
77 x in self.sa.query(Group).all()])
78 c.repo_groups = sorted(c.repo_groups,
78 c.repo_groups = sorted(c.repo_groups,
79 key=lambda t: t[1].split('&raquo;')[0])
79 key=lambda t: t[1].split('&raquo;')[0])
80 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
80 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
81 c.users_array = repo_model.get_users_js()
81 c.users_array = repo_model.get_users_js()
82 c.users_groups_array = repo_model.get_users_groups_js()
82 c.users_groups_array = repo_model.get_users_groups_js()
83
83
84 def __load_data(self, repo_name=None):
84 def __load_data(self, repo_name=None):
85 """
85 """
86 Load defaults settings for edit, and update
86 Load defaults settings for edit, and update
87
87
88 :param repo_name:
88 :param repo_name:
89 """
89 """
90 self.__load_defaults()
90 self.__load_defaults()
91
91
92 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
92 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
93 repo = scm_repo = db_repo.scm_instance
93 repo = scm_repo = db_repo.scm_instance
94
94
95 if c.repo_info is None:
95 if c.repo_info is None:
96 h.flash(_('%s repository is not mapped to db perhaps'
96 h.flash(_('%s repository is not mapped to db perhaps'
97 ' it was created or renamed from the filesystem'
97 ' it was created or renamed from the filesystem'
98 ' please run the application again'
98 ' please run the application again'
99 ' in order to rescan repositories') % repo_name,
99 ' in order to rescan repositories') % repo_name,
100 category='error')
100 category='error')
101
101
102 return redirect(url('repos'))
102 return redirect(url('repos'))
103
103
104 c.default_user_id = User.get_by_username('default').user_id
104 c.default_user_id = User.get_by_username('default').user_id
105 c.in_public_journal = self.sa.query(UserFollowing)\
105 c.in_public_journal = self.sa.query(UserFollowing)\
106 .filter(UserFollowing.user_id == c.default_user_id)\
106 .filter(UserFollowing.user_id == c.default_user_id)\
107 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
107 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
108
108
109 if c.repo_info.stats:
109 if c.repo_info.stats:
110 last_rev = c.repo_info.stats.stat_on_revision
110 last_rev = c.repo_info.stats.stat_on_revision
111 else:
111 else:
112 last_rev = 0
112 last_rev = 0
113 c.stats_revision = last_rev
113 c.stats_revision = last_rev
114
114
115 c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
115 c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
116
116
117 if last_rev == 0 or c.repo_last_rev == 0:
117 if last_rev == 0 or c.repo_last_rev == 0:
118 c.stats_percentage = 0
118 c.stats_percentage = 0
119 else:
119 else:
120 c.stats_percentage = '%.2f' % ((float((last_rev)) /
120 c.stats_percentage = '%.2f' % ((float((last_rev)) /
121 c.repo_last_rev) * 100)
121 c.repo_last_rev) * 100)
122
122
123 defaults = c.repo_info.get_dict()
123 defaults = c.repo_info.get_dict()
124 group, repo_name = c.repo_info.groups_and_repo
124 group, repo_name = c.repo_info.groups_and_repo
125 defaults['repo_name'] = repo_name
125 defaults['repo_name'] = repo_name
126 defaults['repo_group'] = getattr(group[-1] if group else None,
126 defaults['repo_group'] = getattr(group[-1] if group else None,
127 'group_id', None)
127 'group_id', None)
128
128
129 #fill owner
129 #fill owner
130 if c.repo_info.user:
130 if c.repo_info.user:
131 defaults.update({'user': c.repo_info.user.username})
131 defaults.update({'user': c.repo_info.user.username})
132 else:
132 else:
133 replacement_user = self.sa.query(User)\
133 replacement_user = self.sa.query(User)\
134 .filter(User.admin == True).first().username
134 .filter(User.admin == True).first().username
135 defaults.update({'user': replacement_user})
135 defaults.update({'user': replacement_user})
136
136
137 #fill repository users
137 #fill repository users
138 for p in c.repo_info.repo_to_perm:
138 for p in c.repo_info.repo_to_perm:
139 defaults.update({'u_perm_%s' % p.user.username:
139 defaults.update({'u_perm_%s' % p.user.username:
140 p.permission.permission_name})
140 p.permission.permission_name})
141
141
142 #fill repository groups
142 #fill repository groups
143 for p in c.repo_info.users_group_to_perm:
143 for p in c.repo_info.users_group_to_perm:
144 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
144 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
145 p.permission.permission_name})
145 p.permission.permission_name})
146
146
147 return defaults
147 return defaults
148
148
149 @HasPermissionAllDecorator('hg.admin')
149 @HasPermissionAllDecorator('hg.admin')
150 def index(self, format='html'):
150 def index(self, format='html'):
151 """GET /repos: All items in the collection"""
151 """GET /repos: All items in the collection"""
152 # url('repos')
152 # url('repos')
153
153
154 c.repos_list = ScmModel().get_repos(Repository.query()
154 c.repos_list = ScmModel().get_repos(Repository.query()
155 .order_by(Repository.repo_name)
155 .order_by(Repository.repo_name)
156 .all(), sort_key='name_sort')
156 .all(), sort_key='name_sort')
157 return render('admin/repos/repos.html')
157 return render('admin/repos/repos.html')
158
158
159 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
159 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
160 def create(self):
160 def create(self):
161 """
161 """
162 POST /repos: Create a new item"""
162 POST /repos: Create a new item"""
163 # url('repos')
163 # url('repos')
164 repo_model = RepoModel()
164 repo_model = RepoModel()
165 self.__load_defaults()
165 self.__load_defaults()
166 form_result = {}
166 form_result = {}
167 try:
167 try:
168 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
168 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
169 .to_python(dict(request.POST))
169 .to_python(dict(request.POST))
170 repo_model.create(form_result, self.rhodecode_user)
170 repo_model.create(form_result, self.rhodecode_user)
171 if form_result['clone_uri']:
171 if form_result['clone_uri']:
172 h.flash(_('created repository %s from %s') \
172 h.flash(_('created repository %s from %s') \
173 % (form_result['repo_name'], form_result['clone_uri']),
173 % (form_result['repo_name'], form_result['clone_uri']),
174 category='success')
174 category='success')
175 else:
175 else:
176 h.flash(_('created repository %s') % form_result['repo_name'],
176 h.flash(_('created repository %s') % form_result['repo_name'],
177 category='success')
177 category='success')
178
178
179 if request.POST.get('user_created'):
179 if request.POST.get('user_created'):
180 #created by regular non admin user
180 #created by regular non admin user
181 action_logger(self.rhodecode_user, 'user_created_repo',
181 action_logger(self.rhodecode_user, 'user_created_repo',
182 form_result['repo_name_full'], '', self.sa)
182 form_result['repo_name_full'], '', self.sa)
183 else:
183 else:
184 action_logger(self.rhodecode_user, 'admin_created_repo',
184 action_logger(self.rhodecode_user, 'admin_created_repo',
185 form_result['repo_name_full'], '', self.sa)
185 form_result['repo_name_full'], '', self.sa)
186
186
187 except formencode.Invalid, errors:
187 except formencode.Invalid, errors:
188
188
189 c.new_repo = errors.value['repo_name']
189 c.new_repo = errors.value['repo_name']
190
190
191 if request.POST.get('user_created'):
191 if request.POST.get('user_created'):
192 r = render('admin/repos/repo_add_create_repository.html')
192 r = render('admin/repos/repo_add_create_repository.html')
193 else:
193 else:
194 r = render('admin/repos/repo_add.html')
194 r = render('admin/repos/repo_add.html')
195
195
196 return htmlfill.render(
196 return htmlfill.render(
197 r,
197 r,
198 defaults=errors.value,
198 defaults=errors.value,
199 errors=errors.error_dict or {},
199 errors=errors.error_dict or {},
200 prefix_error=False,
200 prefix_error=False,
201 encoding="UTF-8")
201 encoding="UTF-8")
202
202
203 except Exception:
203 except Exception:
204 log.error(traceback.format_exc())
204 log.error(traceback.format_exc())
205 msg = _('error occurred during creation of repository %s') \
205 msg = _('error occurred during creation of repository %s') \
206 % form_result.get('repo_name')
206 % form_result.get('repo_name')
207 h.flash(msg, category='error')
207 h.flash(msg, category='error')
208 if request.POST.get('user_created'):
208 if request.POST.get('user_created'):
209 return redirect(url('home'))
209 return redirect(url('home'))
210 return redirect(url('repos'))
210 return redirect(url('repos'))
211
211
212 @HasPermissionAllDecorator('hg.admin')
212 @HasPermissionAllDecorator('hg.admin')
213 def new(self, format='html'):
213 def new(self, format='html'):
214 """GET /repos/new: Form to create a new item"""
214 """GET /repos/new: Form to create a new item"""
215 new_repo = request.GET.get('repo', '')
215 new_repo = request.GET.get('repo', '')
216 c.new_repo = repo_name_slug(new_repo)
216 c.new_repo = repo_name_slug(new_repo)
217 self.__load_defaults()
217 self.__load_defaults()
218 return render('admin/repos/repo_add.html')
218 return render('admin/repos/repo_add.html')
219
219
220 @HasPermissionAllDecorator('hg.admin')
220 @HasPermissionAllDecorator('hg.admin')
221 def update(self, repo_name):
221 def update(self, repo_name):
222 """
222 """
223 PUT /repos/repo_name: Update an existing item"""
223 PUT /repos/repo_name: Update an existing item"""
224 # Forms posted to this method should contain a hidden field:
224 # Forms posted to this method should contain a hidden field:
225 # <input type="hidden" name="_method" value="PUT" />
225 # <input type="hidden" name="_method" value="PUT" />
226 # Or using helpers:
226 # Or using helpers:
227 # h.form(url('repo', repo_name=ID),
227 # h.form(url('repo', repo_name=ID),
228 # method='put')
228 # method='put')
229 # url('repo', repo_name=ID)
229 # url('repo', repo_name=ID)
230 self.__load_defaults()
230 self.__load_defaults()
231 repo_model = RepoModel()
231 repo_model = RepoModel()
232 changed_name = repo_name
232 changed_name = repo_name
233 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
233 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
234 repo_groups=c.repo_groups_choices)()
234 repo_groups=c.repo_groups_choices)()
235 try:
235 try:
236 form_result = _form.to_python(dict(request.POST))
236 form_result = _form.to_python(dict(request.POST))
237 repo_model.update(repo_name, form_result)
237 repo = repo_model.update(repo_name, form_result)
238 invalidate_cache('get_repo_cached_%s' % repo_name)
238 invalidate_cache('get_repo_cached_%s' % repo_name)
239 h.flash(_('Repository %s updated successfully' % repo_name),
239 h.flash(_('Repository %s updated successfully' % repo_name),
240 category='success')
240 category='success')
241 changed_name = form_result['repo_name_full']
241 changed_name = repo.repo_name
242 action_logger(self.rhodecode_user, 'admin_updated_repo',
242 action_logger(self.rhodecode_user, 'admin_updated_repo',
243 changed_name, '', self.sa)
243 changed_name, '', self.sa)
244
244
245 except formencode.Invalid, errors:
245 except formencode.Invalid, errors:
246 defaults = self.__load_data(repo_name)
246 defaults = self.__load_data(repo_name)
247 defaults.update(errors.value)
247 defaults.update(errors.value)
248 return htmlfill.render(
248 return htmlfill.render(
249 render('admin/repos/repo_edit.html'),
249 render('admin/repos/repo_edit.html'),
250 defaults=defaults,
250 defaults=defaults,
251 errors=errors.error_dict or {},
251 errors=errors.error_dict or {},
252 prefix_error=False,
252 prefix_error=False,
253 encoding="UTF-8")
253 encoding="UTF-8")
254
254
255 except Exception:
255 except Exception:
256 log.error(traceback.format_exc())
256 log.error(traceback.format_exc())
257 h.flash(_('error occurred during update of repository %s') \
257 h.flash(_('error occurred during update of repository %s') \
258 % repo_name, category='error')
258 % repo_name, category='error')
259 return redirect(url('edit_repo', repo_name=changed_name))
259 return redirect(url('edit_repo', repo_name=changed_name))
260
260
261 @HasPermissionAllDecorator('hg.admin')
261 @HasPermissionAllDecorator('hg.admin')
262 def delete(self, repo_name):
262 def delete(self, repo_name):
263 """
263 """
264 DELETE /repos/repo_name: Delete an existing item"""
264 DELETE /repos/repo_name: Delete an existing item"""
265 # Forms posted to this method should contain a hidden field:
265 # Forms posted to this method should contain a hidden field:
266 # <input type="hidden" name="_method" value="DELETE" />
266 # <input type="hidden" name="_method" value="DELETE" />
267 # Or using helpers:
267 # Or using helpers:
268 # h.form(url('repo', repo_name=ID),
268 # h.form(url('repo', repo_name=ID),
269 # method='delete')
269 # method='delete')
270 # url('repo', repo_name=ID)
270 # url('repo', repo_name=ID)
271
271
272 repo_model = RepoModel()
272 repo_model = RepoModel()
273 repo = repo_model.get_by_repo_name(repo_name)
273 repo = repo_model.get_by_repo_name(repo_name)
274 if not repo:
274 if not repo:
275 h.flash(_('%s repository is not mapped to db perhaps'
275 h.flash(_('%s repository is not mapped to db perhaps'
276 ' it was moved or renamed from the filesystem'
276 ' it was moved or renamed from the filesystem'
277 ' please run the application again'
277 ' please run the application again'
278 ' in order to rescan repositories') % repo_name,
278 ' in order to rescan repositories') % repo_name,
279 category='error')
279 category='error')
280
280
281 return redirect(url('repos'))
281 return redirect(url('repos'))
282 try:
282 try:
283 action_logger(self.rhodecode_user, 'admin_deleted_repo',
283 action_logger(self.rhodecode_user, 'admin_deleted_repo',
284 repo_name, '', self.sa)
284 repo_name, '', self.sa)
285 repo_model.delete(repo)
285 repo_model.delete(repo)
286 invalidate_cache('get_repo_cached_%s' % repo_name)
286 invalidate_cache('get_repo_cached_%s' % repo_name)
287 h.flash(_('deleted repository %s') % repo_name, category='success')
287 h.flash(_('deleted repository %s') % repo_name, category='success')
288
288
289 except IntegrityError, e:
289 except IntegrityError, e:
290 if e.message.find('repositories_fork_id_fkey'):
290 if e.message.find('repositories_fork_id_fkey'):
291 log.error(traceback.format_exc())
291 log.error(traceback.format_exc())
292 h.flash(_('Cannot delete %s it still contains attached '
292 h.flash(_('Cannot delete %s it still contains attached '
293 'forks') % repo_name,
293 'forks') % repo_name,
294 category='warning')
294 category='warning')
295 else:
295 else:
296 log.error(traceback.format_exc())
296 log.error(traceback.format_exc())
297 h.flash(_('An error occurred during '
297 h.flash(_('An error occurred during '
298 'deletion of %s') % repo_name,
298 'deletion of %s') % repo_name,
299 category='error')
299 category='error')
300
300
301 except Exception, e:
301 except Exception, e:
302 log.error(traceback.format_exc())
302 log.error(traceback.format_exc())
303 h.flash(_('An error occurred during deletion of %s') % repo_name,
303 h.flash(_('An error occurred during deletion of %s') % repo_name,
304 category='error')
304 category='error')
305
305
306 return redirect(url('repos'))
306 return redirect(url('repos'))
307
307
308 @HasPermissionAllDecorator('hg.admin')
308 @HasPermissionAllDecorator('hg.admin')
309 def delete_perm_user(self, repo_name):
309 def delete_perm_user(self, repo_name):
310 """
310 """
311 DELETE an existing repository permission user
311 DELETE an existing repository permission user
312
312
313 :param repo_name:
313 :param repo_name:
314 """
314 """
315
315
316 try:
316 try:
317 repo_model = RepoModel()
317 repo_model = RepoModel()
318 repo_model.delete_perm_user(request.POST, repo_name)
318 repo_model.delete_perm_user(request.POST, repo_name)
319 except Exception, e:
319 except Exception, e:
320 h.flash(_('An error occurred during deletion of repository user'),
320 h.flash(_('An error occurred during deletion of repository user'),
321 category='error')
321 category='error')
322 raise HTTPInternalServerError()
322 raise HTTPInternalServerError()
323
323
324 @HasPermissionAllDecorator('hg.admin')
324 @HasPermissionAllDecorator('hg.admin')
325 def delete_perm_users_group(self, repo_name):
325 def delete_perm_users_group(self, repo_name):
326 """
326 """
327 DELETE an existing repository permission users group
327 DELETE an existing repository permission users group
328
328
329 :param repo_name:
329 :param repo_name:
330 """
330 """
331 try:
331 try:
332 repo_model = RepoModel()
332 repo_model = RepoModel()
333 repo_model.delete_perm_users_group(request.POST, repo_name)
333 repo_model.delete_perm_users_group(request.POST, repo_name)
334 except Exception, e:
334 except Exception, e:
335 h.flash(_('An error occurred during deletion of repository'
335 h.flash(_('An error occurred during deletion of repository'
336 ' users groups'),
336 ' users groups'),
337 category='error')
337 category='error')
338 raise HTTPInternalServerError()
338 raise HTTPInternalServerError()
339
339
340 @HasPermissionAllDecorator('hg.admin')
340 @HasPermissionAllDecorator('hg.admin')
341 def repo_stats(self, repo_name):
341 def repo_stats(self, repo_name):
342 """
342 """
343 DELETE an existing repository statistics
343 DELETE an existing repository statistics
344
344
345 :param repo_name:
345 :param repo_name:
346 """
346 """
347
347
348 try:
348 try:
349 repo_model = RepoModel()
349 repo_model = RepoModel()
350 repo_model.delete_stats(repo_name)
350 repo_model.delete_stats(repo_name)
351 except Exception, e:
351 except Exception, e:
352 h.flash(_('An error occurred during deletion of repository stats'),
352 h.flash(_('An error occurred during deletion of repository stats'),
353 category='error')
353 category='error')
354 return redirect(url('edit_repo', repo_name=repo_name))
354 return redirect(url('edit_repo', repo_name=repo_name))
355
355
356 @HasPermissionAllDecorator('hg.admin')
356 @HasPermissionAllDecorator('hg.admin')
357 def repo_cache(self, repo_name):
357 def repo_cache(self, repo_name):
358 """
358 """
359 INVALIDATE existing repository cache
359 INVALIDATE existing repository cache
360
360
361 :param repo_name:
361 :param repo_name:
362 """
362 """
363
363
364 try:
364 try:
365 ScmModel().mark_for_invalidation(repo_name)
365 ScmModel().mark_for_invalidation(repo_name)
366 except Exception, e:
366 except Exception, e:
367 h.flash(_('An error occurred during cache invalidation'),
367 h.flash(_('An error occurred during cache invalidation'),
368 category='error')
368 category='error')
369 return redirect(url('edit_repo', repo_name=repo_name))
369 return redirect(url('edit_repo', repo_name=repo_name))
370
370
371 @HasPermissionAllDecorator('hg.admin')
371 @HasPermissionAllDecorator('hg.admin')
372 def repo_public_journal(self, repo_name):
372 def repo_public_journal(self, repo_name):
373 """
373 """
374 Set's this repository to be visible in public journal,
374 Set's this repository to be visible in public journal,
375 in other words assing default user to follow this repo
375 in other words assing default user to follow this repo
376
376
377 :param repo_name:
377 :param repo_name:
378 """
378 """
379
379
380 cur_token = request.POST.get('auth_token')
380 cur_token = request.POST.get('auth_token')
381 token = get_token()
381 token = get_token()
382 if cur_token == token:
382 if cur_token == token:
383 try:
383 try:
384 repo_id = Repository.get_by_repo_name(repo_name).repo_id
384 repo_id = Repository.get_by_repo_name(repo_name).repo_id
385 user_id = User.get_by_username('default').user_id
385 user_id = User.get_by_username('default').user_id
386 self.scm_model.toggle_following_repo(repo_id, user_id)
386 self.scm_model.toggle_following_repo(repo_id, user_id)
387 h.flash(_('Updated repository visibility in public journal'),
387 h.flash(_('Updated repository visibility in public journal'),
388 category='success')
388 category='success')
389 except:
389 except:
390 h.flash(_('An error occurred during setting this'
390 h.flash(_('An error occurred during setting this'
391 ' repository in public journal'),
391 ' repository in public journal'),
392 category='error')
392 category='error')
393
393
394 else:
394 else:
395 h.flash(_('Token mismatch'), category='error')
395 h.flash(_('Token mismatch'), category='error')
396 return redirect(url('edit_repo', repo_name=repo_name))
396 return redirect(url('edit_repo', repo_name=repo_name))
397
397
398 @HasPermissionAllDecorator('hg.admin')
398 @HasPermissionAllDecorator('hg.admin')
399 def repo_pull(self, repo_name):
399 def repo_pull(self, repo_name):
400 """
400 """
401 Runs task to update given repository with remote changes,
401 Runs task to update given repository with remote changes,
402 ie. make pull on remote location
402 ie. make pull on remote location
403
403
404 :param repo_name:
404 :param repo_name:
405 """
405 """
406 try:
406 try:
407 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
407 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
408 h.flash(_('Pulled from remote location'), category='success')
408 h.flash(_('Pulled from remote location'), category='success')
409 except Exception, e:
409 except Exception, e:
410 h.flash(_('An error occurred during pull from remote location'),
410 h.flash(_('An error occurred during pull from remote location'),
411 category='error')
411 category='error')
412
412
413 return redirect(url('edit_repo', repo_name=repo_name))
413 return redirect(url('edit_repo', repo_name=repo_name))
414
414
415 @HasPermissionAllDecorator('hg.admin')
415 @HasPermissionAllDecorator('hg.admin')
416 def show(self, repo_name, format='html'):
416 def show(self, repo_name, format='html'):
417 """GET /repos/repo_name: Show a specific item"""
417 """GET /repos/repo_name: Show a specific item"""
418 # url('repo', repo_name=ID)
418 # url('repo', repo_name=ID)
419
419
420 @HasPermissionAllDecorator('hg.admin')
420 @HasPermissionAllDecorator('hg.admin')
421 def edit(self, repo_name, format='html'):
421 def edit(self, repo_name, format='html'):
422 """GET /repos/repo_name/edit: Form to edit an existing item"""
422 """GET /repos/repo_name/edit: Form to edit an existing item"""
423 # url('edit_repo', repo_name=ID)
423 # url('edit_repo', repo_name=ID)
424 defaults = self.__load_data(repo_name)
424 defaults = self.__load_data(repo_name)
425
425
426 return htmlfill.render(
426 return htmlfill.render(
427 render('admin/repos/repo_edit.html'),
427 render('admin/repos/repo_edit.html'),
428 defaults=defaults,
428 defaults=defaults,
429 encoding="UTF-8",
429 encoding="UTF-8",
430 force_defaults=False
430 force_defaults=False
431 )
431 )
@@ -1,1002 +1,1011
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 modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 from datetime import date
30 from datetime import date
31
31
32 from sqlalchemy import *
32 from sqlalchemy import *
33 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.exc import DatabaseError
34 from sqlalchemy.orm import relationship, backref, joinedload, class_mapper
34 from sqlalchemy.orm import relationship, backref, joinedload, class_mapper
35 from sqlalchemy.orm.interfaces import MapperExtension
35 from sqlalchemy.orm.interfaces import MapperExtension
36
36
37 from beaker.cache import cache_region, region_invalidate
37 from beaker.cache import cache_region, region_invalidate
38
38
39 from vcs import get_backend
39 from vcs import get_backend
40 from vcs.utils.helpers import get_scm
40 from vcs.utils.helpers import get_scm
41 from vcs.exceptions import VCSError
41 from vcs.exceptions import VCSError
42 from vcs.utils.lazy import LazyProperty
42 from vcs.utils.lazy import LazyProperty
43
43
44 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
44 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
45 generate_api_key
45 generate_api_key
46 from rhodecode.lib.exceptions import UsersGroupsAssignedException
46 from rhodecode.lib.exceptions import UsersGroupsAssignedException
47 from rhodecode.lib.compat import json
47 from rhodecode.lib.compat import json
48
48
49 from rhodecode.model.meta import Base, Session
49 from rhodecode.model.meta import Base, Session
50 from rhodecode.model.caching_query import FromCache
50 from rhodecode.model.caching_query import FromCache
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54 #==============================================================================
54 #==============================================================================
55 # BASE CLASSES
55 # BASE CLASSES
56 #==============================================================================
56 #==============================================================================
57
57
58 class ModelSerializer(json.JSONEncoder):
58 class ModelSerializer(json.JSONEncoder):
59 """
59 """
60 Simple Serializer for JSON,
60 Simple Serializer for JSON,
61
61
62 usage::
62 usage::
63
63
64 to make object customized for serialization implement a __json__
64 to make object customized for serialization implement a __json__
65 method that will return a dict for serialization into json
65 method that will return a dict for serialization into json
66
66
67 example::
67 example::
68
68
69 class Task(object):
69 class Task(object):
70
70
71 def __init__(self, name, value):
71 def __init__(self, name, value):
72 self.name = name
72 self.name = name
73 self.value = value
73 self.value = value
74
74
75 def __json__(self):
75 def __json__(self):
76 return dict(name=self.name,
76 return dict(name=self.name,
77 value=self.value)
77 value=self.value)
78
78
79 """
79 """
80
80
81 def default(self, obj):
81 def default(self, obj):
82
82
83 if hasattr(obj, '__json__'):
83 if hasattr(obj, '__json__'):
84 return obj.__json__()
84 return obj.__json__()
85 else:
85 else:
86 return json.JSONEncoder.default(self, obj)
86 return json.JSONEncoder.default(self, obj)
87
87
88 class BaseModel(object):
88 class BaseModel(object):
89 """Base Model for all classess
89 """Base Model for all classess
90
90
91 """
91 """
92
92
93 @classmethod
93 @classmethod
94 def _get_keys(cls):
94 def _get_keys(cls):
95 """return column names for this model """
95 """return column names for this model """
96 return class_mapper(cls).c.keys()
96 return class_mapper(cls).c.keys()
97
97
98 def get_dict(self):
98 def get_dict(self):
99 """return dict with keys and values corresponding
99 """return dict with keys and values corresponding
100 to this model data """
100 to this model data """
101
101
102 d = {}
102 d = {}
103 for k in self._get_keys():
103 for k in self._get_keys():
104 d[k] = getattr(self, k)
104 d[k] = getattr(self, k)
105 return d
105 return d
106
106
107 def get_appstruct(self):
107 def get_appstruct(self):
108 """return list with keys and values tupples corresponding
108 """return list with keys and values tupples corresponding
109 to this model data """
109 to this model data """
110
110
111 l = []
111 l = []
112 for k in self._get_keys():
112 for k in self._get_keys():
113 l.append((k, getattr(self, k),))
113 l.append((k, getattr(self, k),))
114 return l
114 return l
115
115
116 def populate_obj(self, populate_dict):
116 def populate_obj(self, populate_dict):
117 """populate model with data from given populate_dict"""
117 """populate model with data from given populate_dict"""
118
118
119 for k in self._get_keys():
119 for k in self._get_keys():
120 if k in populate_dict:
120 if k in populate_dict:
121 setattr(self, k, populate_dict[k])
121 setattr(self, k, populate_dict[k])
122
122
123 @classmethod
123 @classmethod
124 def query(cls):
124 def query(cls):
125 return Session.query(cls)
125 return Session.query(cls)
126
126
127 @classmethod
127 @classmethod
128 def get(cls, id_):
128 def get(cls, id_):
129 if id_:
129 if id_:
130 return Session.query(cls).get(id_)
130 return Session.query(cls).get(id_)
131
131
132 @classmethod
132 @classmethod
133 def delete(cls, id_):
133 def delete(cls, id_):
134 obj = Session.query(cls).get(id_)
134 obj = Session.query(cls).get(id_)
135 Session.delete(obj)
135 Session.delete(obj)
136 Session.commit()
136 Session.commit()
137
137
138
138
139 class RhodeCodeSettings(Base, BaseModel):
139 class RhodeCodeSettings(Base, BaseModel):
140 __tablename__ = 'rhodecode_settings'
140 __tablename__ = 'rhodecode_settings'
141 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
141 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
142 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
142 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
143 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
143 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
144 app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
144 app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
145
145
146 def __init__(self, k='', v=''):
146 def __init__(self, k='', v=''):
147 self.app_settings_name = k
147 self.app_settings_name = k
148 self.app_settings_value = v
148 self.app_settings_value = v
149
149
150 def __repr__(self):
150 def __repr__(self):
151 return "<%s('%s:%s')>" % (self.__class__.__name__,
151 return "<%s('%s:%s')>" % (self.__class__.__name__,
152 self.app_settings_name, self.app_settings_value)
152 self.app_settings_name, self.app_settings_value)
153
153
154
154
155 @classmethod
155 @classmethod
156 def get_by_name(cls, ldap_key):
156 def get_by_name(cls, ldap_key):
157 return Session.query(cls)\
157 return Session.query(cls)\
158 .filter(cls.app_settings_name == ldap_key).scalar()
158 .filter(cls.app_settings_name == ldap_key).scalar()
159
159
160 @classmethod
160 @classmethod
161 def get_app_settings(cls, cache=False):
161 def get_app_settings(cls, cache=False):
162
162
163 ret = Session.query(cls)
163 ret = Session.query(cls)
164
164
165 if cache:
165 if cache:
166 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
166 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
167
167
168 if not ret:
168 if not ret:
169 raise Exception('Could not get application settings !')
169 raise Exception('Could not get application settings !')
170 settings = {}
170 settings = {}
171 for each in ret:
171 for each in ret:
172 settings['rhodecode_' + each.app_settings_name] = \
172 settings['rhodecode_' + each.app_settings_name] = \
173 each.app_settings_value
173 each.app_settings_value
174
174
175 return settings
175 return settings
176
176
177 @classmethod
177 @classmethod
178 def get_ldap_settings(cls, cache=False):
178 def get_ldap_settings(cls, cache=False):
179 ret = Session.query(cls)\
179 ret = Session.query(cls)\
180 .filter(cls.app_settings_name.startswith('ldap_'))\
180 .filter(cls.app_settings_name.startswith('ldap_'))\
181 .all()
181 .all()
182 fd = {}
182 fd = {}
183 for row in ret:
183 for row in ret:
184 fd.update({row.app_settings_name:row.app_settings_value})
184 fd.update({row.app_settings_name:row.app_settings_value})
185
185
186 fd.update({'ldap_active':str2bool(fd.get('ldap_active'))})
186 fd.update({'ldap_active':str2bool(fd.get('ldap_active'))})
187
187
188 return fd
188 return fd
189
189
190
190
191 class RhodeCodeUi(Base, BaseModel):
191 class RhodeCodeUi(Base, BaseModel):
192 __tablename__ = 'rhodecode_ui'
192 __tablename__ = 'rhodecode_ui'
193 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
193 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
194
194
195 HOOK_UPDATE = 'changegroup.update'
195 HOOK_UPDATE = 'changegroup.update'
196 HOOK_REPO_SIZE = 'changegroup.repo_size'
196 HOOK_REPO_SIZE = 'changegroup.repo_size'
197 HOOK_PUSH = 'pretxnchangegroup.push_logger'
197 HOOK_PUSH = 'pretxnchangegroup.push_logger'
198 HOOK_PULL = 'preoutgoing.pull_logger'
198 HOOK_PULL = 'preoutgoing.pull_logger'
199
199
200 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
200 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
201 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
201 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
202 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
202 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
203 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
203 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
204 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
204 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
205
205
206
206
207 @classmethod
207 @classmethod
208 def get_by_key(cls, key):
208 def get_by_key(cls, key):
209 return Session.query(cls).filter(cls.ui_key == key)
209 return Session.query(cls).filter(cls.ui_key == key)
210
210
211
211
212 @classmethod
212 @classmethod
213 def get_builtin_hooks(cls):
213 def get_builtin_hooks(cls):
214 q = cls.query()
214 q = cls.query()
215 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
215 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
216 cls.HOOK_REPO_SIZE,
216 cls.HOOK_REPO_SIZE,
217 cls.HOOK_PUSH, cls.HOOK_PULL]))
217 cls.HOOK_PUSH, cls.HOOK_PULL]))
218 return q.all()
218 return q.all()
219
219
220 @classmethod
220 @classmethod
221 def get_custom_hooks(cls):
221 def get_custom_hooks(cls):
222 q = cls.query()
222 q = cls.query()
223 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
223 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
224 cls.HOOK_REPO_SIZE,
224 cls.HOOK_REPO_SIZE,
225 cls.HOOK_PUSH, cls.HOOK_PULL]))
225 cls.HOOK_PUSH, cls.HOOK_PULL]))
226 q = q.filter(cls.ui_section == 'hooks')
226 q = q.filter(cls.ui_section == 'hooks')
227 return q.all()
227 return q.all()
228
228
229 @classmethod
229 @classmethod
230 def create_or_update_hook(cls, key, val):
230 def create_or_update_hook(cls, key, val):
231 new_ui = cls.get_by_key(key).scalar() or cls()
231 new_ui = cls.get_by_key(key).scalar() or cls()
232 new_ui.ui_section = 'hooks'
232 new_ui.ui_section = 'hooks'
233 new_ui.ui_active = True
233 new_ui.ui_active = True
234 new_ui.ui_key = key
234 new_ui.ui_key = key
235 new_ui.ui_value = val
235 new_ui.ui_value = val
236
236
237 Session.add(new_ui)
237 Session.add(new_ui)
238 Session.commit()
238 Session.commit()
239
239
240
240
241 class User(Base, BaseModel):
241 class User(Base, BaseModel):
242 __tablename__ = 'users'
242 __tablename__ = 'users'
243 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
243 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
244 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
244 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
245 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
245 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
246 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
246 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
247 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
247 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
248 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
248 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
249 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
249 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
250 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
250 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
251 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
251 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
252 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
252 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
253 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
253 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
254 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
254 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
255
255
256 user_log = relationship('UserLog', cascade='all')
256 user_log = relationship('UserLog', cascade='all')
257 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
257 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
258
258
259 repositories = relationship('Repository')
259 repositories = relationship('Repository')
260 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
260 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
261 repo_to_perm = relationship('RepoToPerm', primaryjoin='RepoToPerm.user_id==User.user_id', cascade='all')
261 repo_to_perm = relationship('RepoToPerm', primaryjoin='RepoToPerm.user_id==User.user_id', cascade='all')
262
262
263 group_member = relationship('UsersGroupMember', cascade='all')
263 group_member = relationship('UsersGroupMember', cascade='all')
264
264
265 @property
265 @property
266 def full_contact(self):
266 def full_contact(self):
267 return '%s %s <%s>' % (self.name, self.lastname, self.email)
267 return '%s %s <%s>' % (self.name, self.lastname, self.email)
268
268
269 @property
269 @property
270 def short_contact(self):
270 def short_contact(self):
271 return '%s %s' % (self.name, self.lastname)
271 return '%s %s' % (self.name, self.lastname)
272
272
273 @property
273 @property
274 def is_admin(self):
274 def is_admin(self):
275 return self.admin
275 return self.admin
276
276
277 def __repr__(self):
277 def __repr__(self):
278 try:
278 try:
279 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
279 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
280 self.user_id, self.username)
280 self.user_id, self.username)
281 except:
281 except:
282 return self.__class__.__name__
282 return self.__class__.__name__
283
283
284 @classmethod
284 @classmethod
285 def get_by_username(cls, username, case_insensitive=False):
285 def get_by_username(cls, username, case_insensitive=False):
286 if case_insensitive:
286 if case_insensitive:
287 return Session.query(cls).filter(cls.username.like(username)).scalar()
287 return Session.query(cls).filter(cls.username.ilike(username)).scalar()
288 else:
288 else:
289 return Session.query(cls).filter(cls.username == username).scalar()
289 return Session.query(cls).filter(cls.username == username).scalar()
290
290
291 @classmethod
291 @classmethod
292 def get_by_api_key(cls, api_key):
292 def get_by_api_key(cls, api_key):
293 return Session.query(cls).filter(cls.api_key == api_key).one()
293 return Session.query(cls).filter(cls.api_key == api_key).one()
294
294
295 def update_lastlogin(self):
295 def update_lastlogin(self):
296 """Update user lastlogin"""
296 """Update user lastlogin"""
297
297
298 self.last_login = datetime.datetime.now()
298 self.last_login = datetime.datetime.now()
299 Session.add(self)
299 Session.add(self)
300 Session.commit()
300 Session.commit()
301 log.debug('updated user %s lastlogin', self.username)
301 log.debug('updated user %s lastlogin', self.username)
302
302
303 @classmethod
303 @classmethod
304 def create(cls, form_data):
304 def create(cls, form_data):
305 from rhodecode.lib.auth import get_crypt_password
305 from rhodecode.lib.auth import get_crypt_password
306
306
307 try:
307 try:
308 new_user = cls()
308 new_user = cls()
309 for k, v in form_data.items():
309 for k, v in form_data.items():
310 if k == 'password':
310 if k == 'password':
311 v = get_crypt_password(v)
311 v = get_crypt_password(v)
312 setattr(new_user, k, v)
312 setattr(new_user, k, v)
313
313
314 new_user.api_key = generate_api_key(form_data['username'])
314 new_user.api_key = generate_api_key(form_data['username'])
315 Session.add(new_user)
315 Session.add(new_user)
316 Session.commit()
316 Session.commit()
317 return new_user
317 return new_user
318 except:
318 except:
319 log.error(traceback.format_exc())
319 log.error(traceback.format_exc())
320 Session.rollback()
320 Session.rollback()
321 raise
321 raise
322
322
323 class UserLog(Base, BaseModel):
323 class UserLog(Base, BaseModel):
324 __tablename__ = 'user_logs'
324 __tablename__ = 'user_logs'
325 __table_args__ = {'extend_existing':True}
325 __table_args__ = {'extend_existing':True}
326 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
326 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
327 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
327 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
328 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
328 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
329 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
332 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
333
333
334 @property
334 @property
335 def action_as_day(self):
335 def action_as_day(self):
336 return date(*self.action_date.timetuple()[:3])
336 return date(*self.action_date.timetuple()[:3])
337
337
338 user = relationship('User')
338 user = relationship('User')
339 repository = relationship('Repository')
339 repository = relationship('Repository')
340
340
341
341
342 class UsersGroup(Base, BaseModel):
342 class UsersGroup(Base, BaseModel):
343 __tablename__ = 'users_groups'
343 __tablename__ = 'users_groups'
344 __table_args__ = {'extend_existing':True}
344 __table_args__ = {'extend_existing':True}
345
345
346 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
346 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
347 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
347 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
348 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
348 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
349
349
350 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
350 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
351
351
352 def __repr__(self):
352 def __repr__(self):
353 return '<userGroup(%s)>' % (self.users_group_name)
353 return '<userGroup(%s)>' % (self.users_group_name)
354
354
355 @classmethod
355 @classmethod
356 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
356 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
357 if case_insensitive:
357 if case_insensitive:
358 gr = Session.query(cls)\
358 gr = Session.query(cls)\
359 .filter(cls.users_group_name.ilike(group_name))
359 .filter(cls.users_group_name.ilike(group_name))
360 else:
360 else:
361 gr = Session.query(UsersGroup)\
361 gr = Session.query(UsersGroup)\
362 .filter(UsersGroup.users_group_name == group_name)
362 .filter(UsersGroup.users_group_name == group_name)
363 if cache:
363 if cache:
364 gr = gr.options(FromCache("sql_cache_short",
364 gr = gr.options(FromCache("sql_cache_short",
365 "get_user_%s" % group_name))
365 "get_user_%s" % group_name))
366 return gr.scalar()
366 return gr.scalar()
367
367
368
368
369 @classmethod
369 @classmethod
370 def get(cls, users_group_id, cache=False):
370 def get(cls, users_group_id, cache=False):
371 users_group = Session.query(cls)
371 users_group = Session.query(cls)
372 if cache:
372 if cache:
373 users_group = users_group.options(FromCache("sql_cache_short",
373 users_group = users_group.options(FromCache("sql_cache_short",
374 "get_users_group_%s" % users_group_id))
374 "get_users_group_%s" % users_group_id))
375 return users_group.get(users_group_id)
375 return users_group.get(users_group_id)
376
376
377 @classmethod
377 @classmethod
378 def create(cls, form_data):
378 def create(cls, form_data):
379 try:
379 try:
380 new_users_group = cls()
380 new_users_group = cls()
381 for k, v in form_data.items():
381 for k, v in form_data.items():
382 setattr(new_users_group, k, v)
382 setattr(new_users_group, k, v)
383
383
384 Session.add(new_users_group)
384 Session.add(new_users_group)
385 Session.commit()
385 Session.commit()
386 return new_users_group
386 return new_users_group
387 except:
387 except:
388 log.error(traceback.format_exc())
388 log.error(traceback.format_exc())
389 Session.rollback()
389 Session.rollback()
390 raise
390 raise
391
391
392 @classmethod
392 @classmethod
393 def update(cls, users_group_id, form_data):
393 def update(cls, users_group_id, form_data):
394
394
395 try:
395 try:
396 users_group = cls.get(users_group_id, cache=False)
396 users_group = cls.get(users_group_id, cache=False)
397
397
398 for k, v in form_data.items():
398 for k, v in form_data.items():
399 if k == 'users_group_members':
399 if k == 'users_group_members':
400 users_group.members = []
400 users_group.members = []
401 Session.flush()
401 Session.flush()
402 members_list = []
402 members_list = []
403 if v:
403 if v:
404 for u_id in set(v):
404 for u_id in set(v):
405 members_list.append(UsersGroupMember(
405 members_list.append(UsersGroupMember(
406 users_group_id,
406 users_group_id,
407 u_id))
407 u_id))
408 setattr(users_group, 'members', members_list)
408 setattr(users_group, 'members', members_list)
409 setattr(users_group, k, v)
409 setattr(users_group, k, v)
410
410
411 Session.add(users_group)
411 Session.add(users_group)
412 Session.commit()
412 Session.commit()
413 except:
413 except:
414 log.error(traceback.format_exc())
414 log.error(traceback.format_exc())
415 Session.rollback()
415 Session.rollback()
416 raise
416 raise
417
417
418 @classmethod
418 @classmethod
419 def delete(cls, users_group_id):
419 def delete(cls, users_group_id):
420 try:
420 try:
421
421
422 # check if this group is not assigned to repo
422 # check if this group is not assigned to repo
423 assigned_groups = UsersGroupRepoToPerm.query()\
423 assigned_groups = UsersGroupRepoToPerm.query()\
424 .filter(UsersGroupRepoToPerm.users_group_id ==
424 .filter(UsersGroupRepoToPerm.users_group_id ==
425 users_group_id).all()
425 users_group_id).all()
426
426
427 if assigned_groups:
427 if assigned_groups:
428 raise UsersGroupsAssignedException('Group assigned to %s' %
428 raise UsersGroupsAssignedException('Group assigned to %s' %
429 assigned_groups)
429 assigned_groups)
430
430
431 users_group = cls.get(users_group_id, cache=False)
431 users_group = cls.get(users_group_id, cache=False)
432 Session.delete(users_group)
432 Session.delete(users_group)
433 Session.commit()
433 Session.commit()
434 except:
434 except:
435 log.error(traceback.format_exc())
435 log.error(traceback.format_exc())
436 Session.rollback()
436 Session.rollback()
437 raise
437 raise
438
438
439
439
440 class UsersGroupMember(Base, BaseModel):
440 class UsersGroupMember(Base, BaseModel):
441 __tablename__ = 'users_groups_members'
441 __tablename__ = 'users_groups_members'
442 __table_args__ = {'extend_existing':True}
442 __table_args__ = {'extend_existing':True}
443
443
444 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
444 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
445 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
445 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
446 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
446 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
447
447
448 user = relationship('User', lazy='joined')
448 user = relationship('User', lazy='joined')
449 users_group = relationship('UsersGroup')
449 users_group = relationship('UsersGroup')
450
450
451 def __init__(self, gr_id='', u_id=''):
451 def __init__(self, gr_id='', u_id=''):
452 self.users_group_id = gr_id
452 self.users_group_id = gr_id
453 self.user_id = u_id
453 self.user_id = u_id
454
454
455 class Repository(Base, BaseModel):
455 class Repository(Base, BaseModel):
456 __tablename__ = 'repositories'
456 __tablename__ = 'repositories'
457 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
457 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
458
458
459 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
459 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
460 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
460 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
461 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
461 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
462 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
462 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
463 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
463 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
464 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
464 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
465 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
465 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
466 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
466 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
467 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
467 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
468 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
468 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
469
469
470 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
470 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
471 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
471 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
472
472
473
473
474 user = relationship('User')
474 user = relationship('User')
475 fork = relationship('Repository', remote_side=repo_id)
475 fork = relationship('Repository', remote_side=repo_id)
476 group = relationship('Group')
476 group = relationship('Group')
477 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
477 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
478 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
478 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
479 stats = relationship('Statistics', cascade='all', uselist=False)
479 stats = relationship('Statistics', cascade='all', uselist=False)
480
480
481 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
481 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
482
482
483 logs = relationship('UserLog', cascade='all')
483 logs = relationship('UserLog', cascade='all')
484
484
485 def __repr__(self):
485 def __repr__(self):
486 return "<%s('%s:%s')>" % (self.__class__.__name__,
486 return "<%s('%s:%s')>" % (self.__class__.__name__,
487 self.repo_id, self.repo_name)
487 self.repo_id, self.repo_name)
488
488
489 @classmethod
489 @classmethod
490 def get_by_repo_name(cls, repo_name):
490 def get_by_repo_name(cls, repo_name):
491 q = Session.query(cls).filter(cls.repo_name == repo_name)
491 q = Session.query(cls).filter(cls.repo_name == repo_name)
492
492
493 q = q.options(joinedload(Repository.fork))\
493 q = q.options(joinedload(Repository.fork))\
494 .options(joinedload(Repository.user))\
494 .options(joinedload(Repository.user))\
495 .options(joinedload(Repository.group))\
495 .options(joinedload(Repository.group))\
496
496
497 return q.one()
497 return q.one()
498
498
499 @classmethod
499 @classmethod
500 def get_repo_forks(cls, repo_id):
500 def get_repo_forks(cls, repo_id):
501 return Session.query(cls).filter(Repository.fork_id == repo_id)
501 return Session.query(cls).filter(Repository.fork_id == repo_id)
502
502
503 @classmethod
503 @classmethod
504 def base_path(cls):
504 def base_path(cls):
505 """
505 """
506 Returns base path when all repos are stored
506 Returns base path when all repos are stored
507
507
508 :param cls:
508 :param cls:
509 """
509 """
510 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/')
510 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/')
511 q.options(FromCache("sql_cache_short", "repository_repo_path"))
511 q.options(FromCache("sql_cache_short", "repository_repo_path"))
512 return q.one().ui_value
512 return q.one().ui_value
513
513
514 @property
514 @property
515 def just_name(self):
515 def just_name(self):
516 return self.repo_name.split(os.sep)[-1]
516 return self.repo_name.split(os.sep)[-1]
517
517
518 @property
518 @property
519 def groups_with_parents(self):
519 def groups_with_parents(self):
520 groups = []
520 groups = []
521 if self.group is None:
521 if self.group is None:
522 return groups
522 return groups
523
523
524 cur_gr = self.group
524 cur_gr = self.group
525 groups.insert(0, cur_gr)
525 groups.insert(0, cur_gr)
526 while 1:
526 while 1:
527 gr = getattr(cur_gr, 'parent_group', None)
527 gr = getattr(cur_gr, 'parent_group', None)
528 cur_gr = cur_gr.parent_group
528 cur_gr = cur_gr.parent_group
529 if gr is None:
529 if gr is None:
530 break
530 break
531 groups.insert(0, gr)
531 groups.insert(0, gr)
532
532
533 return groups
533 return groups
534
534
535 @property
535 @property
536 def groups_and_repo(self):
536 def groups_and_repo(self):
537 return self.groups_with_parents, self.just_name
537 return self.groups_with_parents, self.just_name
538
538
539 @LazyProperty
539 @LazyProperty
540 def repo_path(self):
540 def repo_path(self):
541 """
541 """
542 Returns base full path for that repository means where it actually
542 Returns base full path for that repository means where it actually
543 exists on a filesystem
543 exists on a filesystem
544 """
544 """
545 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/')
545 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/')
546 q.options(FromCache("sql_cache_short", "repository_repo_path"))
546 q.options(FromCache("sql_cache_short", "repository_repo_path"))
547 return q.one().ui_value
547 return q.one().ui_value
548
548
549 @property
549 @property
550 def repo_full_path(self):
550 def repo_full_path(self):
551 p = [self.repo_path]
551 p = [self.repo_path]
552 # we need to split the name by / since this is how we store the
552 # we need to split the name by / since this is how we store the
553 # names in the database, but that eventually needs to be converted
553 # names in the database, but that eventually needs to be converted
554 # into a valid system path
554 # into a valid system path
555 p += self.repo_name.split('/')
555 p += self.repo_name.split('/')
556 return os.path.join(*p)
556 return os.path.join(*p)
557
557
558 def get_new_name(self, repo_name):
559 """
560 returns new full repository name based on assigned group and new new
561
562 :param group_name:
563 """
564 path_prefix = self.group.full_path_splitted if self.group else []
565 return '/'.join(path_prefix + [repo_name])
566
558 @property
567 @property
559 def _ui(self):
568 def _ui(self):
560 """
569 """
561 Creates an db based ui object for this repository
570 Creates an db based ui object for this repository
562 """
571 """
563 from mercurial import ui
572 from mercurial import ui
564 from mercurial import config
573 from mercurial import config
565 baseui = ui.ui()
574 baseui = ui.ui()
566
575
567 #clean the baseui object
576 #clean the baseui object
568 baseui._ocfg = config.config()
577 baseui._ocfg = config.config()
569 baseui._ucfg = config.config()
578 baseui._ucfg = config.config()
570 baseui._tcfg = config.config()
579 baseui._tcfg = config.config()
571
580
572
581
573 ret = Session.query(RhodeCodeUi)\
582 ret = Session.query(RhodeCodeUi)\
574 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
583 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
575
584
576 hg_ui = ret
585 hg_ui = ret
577 for ui_ in hg_ui:
586 for ui_ in hg_ui:
578 if ui_.ui_active:
587 if ui_.ui_active:
579 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
588 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
580 ui_.ui_key, ui_.ui_value)
589 ui_.ui_key, ui_.ui_value)
581 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
590 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
582
591
583 return baseui
592 return baseui
584
593
585 @classmethod
594 @classmethod
586 def is_valid(cls, repo_name):
595 def is_valid(cls, repo_name):
587 """
596 """
588 returns True if given repo name is a valid filesystem repository
597 returns True if given repo name is a valid filesystem repository
589
598
590 @param cls:
599 @param cls:
591 @param repo_name:
600 @param repo_name:
592 """
601 """
593 from rhodecode.lib.utils import is_valid_repo
602 from rhodecode.lib.utils import is_valid_repo
594
603
595 return is_valid_repo(repo_name, cls.base_path())
604 return is_valid_repo(repo_name, cls.base_path())
596
605
597
606
598 #==========================================================================
607 #==========================================================================
599 # SCM PROPERTIES
608 # SCM PROPERTIES
600 #==========================================================================
609 #==========================================================================
601
610
602 def get_changeset(self, rev):
611 def get_changeset(self, rev):
603 return get_changeset_safe(self.scm_instance, rev)
612 return get_changeset_safe(self.scm_instance, rev)
604
613
605 @property
614 @property
606 def tip(self):
615 def tip(self):
607 return self.get_changeset('tip')
616 return self.get_changeset('tip')
608
617
609 @property
618 @property
610 def author(self):
619 def author(self):
611 return self.tip.author
620 return self.tip.author
612
621
613 @property
622 @property
614 def last_change(self):
623 def last_change(self):
615 return self.scm_instance.last_change
624 return self.scm_instance.last_change
616
625
617 #==========================================================================
626 #==========================================================================
618 # SCM CACHE INSTANCE
627 # SCM CACHE INSTANCE
619 #==========================================================================
628 #==========================================================================
620
629
621 @property
630 @property
622 def invalidate(self):
631 def invalidate(self):
623 """
632 """
624 Returns Invalidation object if this repo should be invalidated
633 Returns Invalidation object if this repo should be invalidated
625 None otherwise. `cache_active = False` means that this cache
634 None otherwise. `cache_active = False` means that this cache
626 state is not valid and needs to be invalidated
635 state is not valid and needs to be invalidated
627 """
636 """
628 return Session.query(CacheInvalidation)\
637 return Session.query(CacheInvalidation)\
629 .filter(CacheInvalidation.cache_key == self.repo_name)\
638 .filter(CacheInvalidation.cache_key == self.repo_name)\
630 .filter(CacheInvalidation.cache_active == False)\
639 .filter(CacheInvalidation.cache_active == False)\
631 .scalar()
640 .scalar()
632
641
633 def set_invalidate(self):
642 def set_invalidate(self):
634 """
643 """
635 set a cache for invalidation for this instance
644 set a cache for invalidation for this instance
636 """
645 """
637 inv = Session.query(CacheInvalidation)\
646 inv = Session.query(CacheInvalidation)\
638 .filter(CacheInvalidation.cache_key == self.repo_name)\
647 .filter(CacheInvalidation.cache_key == self.repo_name)\
639 .scalar()
648 .scalar()
640
649
641 if inv is None:
650 if inv is None:
642 inv = CacheInvalidation(self.repo_name)
651 inv = CacheInvalidation(self.repo_name)
643 inv.cache_active = True
652 inv.cache_active = True
644 Session.add(inv)
653 Session.add(inv)
645 Session.commit()
654 Session.commit()
646
655
647 @LazyProperty
656 @LazyProperty
648 def scm_instance(self):
657 def scm_instance(self):
649 return self.__get_instance()
658 return self.__get_instance()
650
659
651 @property
660 @property
652 def scm_instance_cached(self):
661 def scm_instance_cached(self):
653 @cache_region('long_term')
662 @cache_region('long_term')
654 def _c(repo_name):
663 def _c(repo_name):
655 return self.__get_instance()
664 return self.__get_instance()
656
665
657 # TODO: remove this trick when beaker 1.6 is released
666 # TODO: remove this trick when beaker 1.6 is released
658 # and have fixed this issue with not supporting unicode keys
667 # and have fixed this issue with not supporting unicode keys
659 rn = safe_str(self.repo_name)
668 rn = safe_str(self.repo_name)
660
669
661 inv = self.invalidate
670 inv = self.invalidate
662 if inv is not None:
671 if inv is not None:
663 region_invalidate(_c, None, rn)
672 region_invalidate(_c, None, rn)
664 # update our cache
673 # update our cache
665 inv.cache_active = True
674 inv.cache_active = True
666 Session.add(inv)
675 Session.add(inv)
667 Session.commit()
676 Session.commit()
668
677
669 return _c(rn)
678 return _c(rn)
670
679
671 def __get_instance(self):
680 def __get_instance(self):
672
681
673 repo_full_path = self.repo_full_path
682 repo_full_path = self.repo_full_path
674
683
675 try:
684 try:
676 alias = get_scm(repo_full_path)[0]
685 alias = get_scm(repo_full_path)[0]
677 log.debug('Creating instance of %s repository', alias)
686 log.debug('Creating instance of %s repository', alias)
678 backend = get_backend(alias)
687 backend = get_backend(alias)
679 except VCSError:
688 except VCSError:
680 log.error(traceback.format_exc())
689 log.error(traceback.format_exc())
681 log.error('Perhaps this repository is in db and not in '
690 log.error('Perhaps this repository is in db and not in '
682 'filesystem run rescan repositories with '
691 'filesystem run rescan repositories with '
683 '"destroy old data " option from admin panel')
692 '"destroy old data " option from admin panel')
684 return
693 return
685
694
686 if alias == 'hg':
695 if alias == 'hg':
687
696
688 repo = backend(safe_str(repo_full_path), create=False,
697 repo = backend(safe_str(repo_full_path), create=False,
689 baseui=self._ui)
698 baseui=self._ui)
690 #skip hidden web repository
699 #skip hidden web repository
691 if repo._get_hidden():
700 if repo._get_hidden():
692 return
701 return
693 else:
702 else:
694 repo = backend(repo_full_path, create=False)
703 repo = backend(repo_full_path, create=False)
695
704
696 return repo
705 return repo
697
706
698
707
699 class Group(Base, BaseModel):
708 class Group(Base, BaseModel):
700 __tablename__ = 'groups'
709 __tablename__ = 'groups'
701 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
710 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
702 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
711 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
703 __mapper_args__ = {'order_by':'group_name'}
712 __mapper_args__ = {'order_by':'group_name'}
704
713
705 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
714 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
706 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
715 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
707 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
716 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
708 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
717 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
709
718
710 parent_group = relationship('Group', remote_side=group_id)
719 parent_group = relationship('Group', remote_side=group_id)
711
720
712
721
713 def __init__(self, group_name='', parent_group=None):
722 def __init__(self, group_name='', parent_group=None):
714 self.group_name = group_name
723 self.group_name = group_name
715 self.parent_group = parent_group
724 self.parent_group = parent_group
716
725
717 def __repr__(self):
726 def __repr__(self):
718 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
727 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
719 self.group_name)
728 self.group_name)
720
729
721 @classmethod
730 @classmethod
722 def url_sep(cls):
731 def url_sep(cls):
723 return '/'
732 return '/'
724
733
725 @classmethod
734 @classmethod
726 def get_by_group_name(cls, group_name):
735 def get_by_group_name(cls, group_name):
727 return cls.query().filter(cls.group_name == group_name).scalar()
736 return cls.query().filter(cls.group_name == group_name).scalar()
728
737
729 @property
738 @property
730 def parents(self):
739 def parents(self):
731 parents_recursion_limit = 5
740 parents_recursion_limit = 5
732 groups = []
741 groups = []
733 if self.parent_group is None:
742 if self.parent_group is None:
734 return groups
743 return groups
735 cur_gr = self.parent_group
744 cur_gr = self.parent_group
736 groups.insert(0, cur_gr)
745 groups.insert(0, cur_gr)
737 cnt = 0
746 cnt = 0
738 while 1:
747 while 1:
739 cnt += 1
748 cnt += 1
740 gr = getattr(cur_gr, 'parent_group', None)
749 gr = getattr(cur_gr, 'parent_group', None)
741 cur_gr = cur_gr.parent_group
750 cur_gr = cur_gr.parent_group
742 if gr is None:
751 if gr is None:
743 break
752 break
744 if cnt == parents_recursion_limit:
753 if cnt == parents_recursion_limit:
745 # this will prevent accidental infinit loops
754 # this will prevent accidental infinit loops
746 log.error('group nested more than %s' %
755 log.error('group nested more than %s' %
747 parents_recursion_limit)
756 parents_recursion_limit)
748 break
757 break
749
758
750 groups.insert(0, gr)
759 groups.insert(0, gr)
751 return groups
760 return groups
752
761
753 @property
762 @property
754 def children(self):
763 def children(self):
755 return Session.query(Group).filter(Group.parent_group == self)
764 return Session.query(Group).filter(Group.parent_group == self)
756
765
757 @property
766 @property
758 def name(self):
767 def name(self):
759 return self.group_name.split(Group.url_sep())[-1]
768 return self.group_name.split(Group.url_sep())[-1]
760
769
761 @property
770 @property
762 def full_path(self):
771 def full_path(self):
763 return self.group_name
772 return self.group_name
764
773
765 @property
774 @property
766 def full_path_splitted(self):
775 def full_path_splitted(self):
767 return self.group_name.split(Group.url_sep())
776 return self.group_name.split(Group.url_sep())
768
777
769 @property
778 @property
770 def repositories(self):
779 def repositories(self):
771 return Session.query(Repository).filter(Repository.group == self)
780 return Session.query(Repository).filter(Repository.group == self)
772
781
773 @property
782 @property
774 def repositories_recursive_count(self):
783 def repositories_recursive_count(self):
775 cnt = self.repositories.count()
784 cnt = self.repositories.count()
776
785
777 def children_count(group):
786 def children_count(group):
778 cnt = 0
787 cnt = 0
779 for child in group.children:
788 for child in group.children:
780 cnt += child.repositories.count()
789 cnt += child.repositories.count()
781 cnt += children_count(child)
790 cnt += children_count(child)
782 return cnt
791 return cnt
783
792
784 return cnt + children_count(self)
793 return cnt + children_count(self)
785
794
786
795
787 def get_new_name(self, group_name):
796 def get_new_name(self, group_name):
788 """
797 """
789 returns new full group name based on parent and new name
798 returns new full group name based on parent and new name
790
799
791 :param group_name:
800 :param group_name:
792 """
801 """
793 path_prefix = self.parent_group.full_path_splitted if self.parent_group else []
802 path_prefix = self.parent_group.full_path_splitted if self.parent_group else []
794 return Group.url_sep().join(path_prefix + [group_name])
803 return Group.url_sep().join(path_prefix + [group_name])
795
804
796
805
797 class Permission(Base, BaseModel):
806 class Permission(Base, BaseModel):
798 __tablename__ = 'permissions'
807 __tablename__ = 'permissions'
799 __table_args__ = {'extend_existing':True}
808 __table_args__ = {'extend_existing':True}
800 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
809 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
801 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
810 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
802 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
811 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
803
812
804 def __repr__(self):
813 def __repr__(self):
805 return "<%s('%s:%s')>" % (self.__class__.__name__,
814 return "<%s('%s:%s')>" % (self.__class__.__name__,
806 self.permission_id, self.permission_name)
815 self.permission_id, self.permission_name)
807
816
808 @classmethod
817 @classmethod
809 def get_by_key(cls, key):
818 def get_by_key(cls, key):
810 return Session.query(cls).filter(cls.permission_name == key).scalar()
819 return Session.query(cls).filter(cls.permission_name == key).scalar()
811
820
812 class RepoToPerm(Base, BaseModel):
821 class RepoToPerm(Base, BaseModel):
813 __tablename__ = 'repo_to_perm'
822 __tablename__ = 'repo_to_perm'
814 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
823 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
815 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
824 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
816 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
825 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
817 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
826 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
818 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
827 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
819
828
820 user = relationship('User')
829 user = relationship('User')
821 permission = relationship('Permission')
830 permission = relationship('Permission')
822 repository = relationship('Repository')
831 repository = relationship('Repository')
823
832
824 class UserToPerm(Base, BaseModel):
833 class UserToPerm(Base, BaseModel):
825 __tablename__ = 'user_to_perm'
834 __tablename__ = 'user_to_perm'
826 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
835 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
827 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
836 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
828 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
837 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
829 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
838 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
830
839
831 user = relationship('User')
840 user = relationship('User')
832 permission = relationship('Permission')
841 permission = relationship('Permission')
833
842
834 @classmethod
843 @classmethod
835 def has_perm(cls, user_id, perm):
844 def has_perm(cls, user_id, perm):
836 if not isinstance(perm, Permission):
845 if not isinstance(perm, Permission):
837 raise Exception('perm needs to be an instance of Permission class')
846 raise Exception('perm needs to be an instance of Permission class')
838
847
839 return Session.query(cls).filter(cls.user_id == user_id)\
848 return Session.query(cls).filter(cls.user_id == user_id)\
840 .filter(cls.permission == perm).scalar() is not None
849 .filter(cls.permission == perm).scalar() is not None
841
850
842 @classmethod
851 @classmethod
843 def grant_perm(cls, user_id, perm):
852 def grant_perm(cls, user_id, perm):
844 if not isinstance(perm, Permission):
853 if not isinstance(perm, Permission):
845 raise Exception('perm needs to be an instance of Permission class')
854 raise Exception('perm needs to be an instance of Permission class')
846
855
847 new = cls()
856 new = cls()
848 new.user_id = user_id
857 new.user_id = user_id
849 new.permission = perm
858 new.permission = perm
850 try:
859 try:
851 Session.add(new)
860 Session.add(new)
852 Session.commit()
861 Session.commit()
853 except:
862 except:
854 Session.rollback()
863 Session.rollback()
855
864
856
865
857 @classmethod
866 @classmethod
858 def revoke_perm(cls, user_id, perm):
867 def revoke_perm(cls, user_id, perm):
859 if not isinstance(perm, Permission):
868 if not isinstance(perm, Permission):
860 raise Exception('perm needs to be an instance of Permission class')
869 raise Exception('perm needs to be an instance of Permission class')
861
870
862 try:
871 try:
863 Session.query(cls).filter(cls.user_id == user_id)\
872 Session.query(cls).filter(cls.user_id == user_id)\
864 .filter(cls.permission == perm).delete()
873 .filter(cls.permission == perm).delete()
865 Session.commit()
874 Session.commit()
866 except:
875 except:
867 Session.rollback()
876 Session.rollback()
868
877
869 class UsersGroupRepoToPerm(Base, BaseModel):
878 class UsersGroupRepoToPerm(Base, BaseModel):
870 __tablename__ = 'users_group_repo_to_perm'
879 __tablename__ = 'users_group_repo_to_perm'
871 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
880 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
872 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
881 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
873 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
882 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
874 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
883 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
875 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
884 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
876
885
877 users_group = relationship('UsersGroup')
886 users_group = relationship('UsersGroup')
878 permission = relationship('Permission')
887 permission = relationship('Permission')
879 repository = relationship('Repository')
888 repository = relationship('Repository')
880
889
881 def __repr__(self):
890 def __repr__(self):
882 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
891 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
883
892
884 class UsersGroupToPerm(Base, BaseModel):
893 class UsersGroupToPerm(Base, BaseModel):
885 __tablename__ = 'users_group_to_perm'
894 __tablename__ = 'users_group_to_perm'
886 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
895 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
887 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
896 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
888 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
897 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
889
898
890 users_group = relationship('UsersGroup')
899 users_group = relationship('UsersGroup')
891 permission = relationship('Permission')
900 permission = relationship('Permission')
892
901
893
902
894 @classmethod
903 @classmethod
895 def has_perm(cls, users_group_id, perm):
904 def has_perm(cls, users_group_id, perm):
896 if not isinstance(perm, Permission):
905 if not isinstance(perm, Permission):
897 raise Exception('perm needs to be an instance of Permission class')
906 raise Exception('perm needs to be an instance of Permission class')
898
907
899 return Session.query(cls).filter(cls.users_group_id ==
908 return Session.query(cls).filter(cls.users_group_id ==
900 users_group_id)\
909 users_group_id)\
901 .filter(cls.permission == perm)\
910 .filter(cls.permission == perm)\
902 .scalar() is not None
911 .scalar() is not None
903
912
904 @classmethod
913 @classmethod
905 def grant_perm(cls, users_group_id, perm):
914 def grant_perm(cls, users_group_id, perm):
906 if not isinstance(perm, Permission):
915 if not isinstance(perm, Permission):
907 raise Exception('perm needs to be an instance of Permission class')
916 raise Exception('perm needs to be an instance of Permission class')
908
917
909 new = cls()
918 new = cls()
910 new.users_group_id = users_group_id
919 new.users_group_id = users_group_id
911 new.permission = perm
920 new.permission = perm
912 try:
921 try:
913 Session.add(new)
922 Session.add(new)
914 Session.commit()
923 Session.commit()
915 except:
924 except:
916 Session.rollback()
925 Session.rollback()
917
926
918
927
919 @classmethod
928 @classmethod
920 def revoke_perm(cls, users_group_id, perm):
929 def revoke_perm(cls, users_group_id, perm):
921 if not isinstance(perm, Permission):
930 if not isinstance(perm, Permission):
922 raise Exception('perm needs to be an instance of Permission class')
931 raise Exception('perm needs to be an instance of Permission class')
923
932
924 try:
933 try:
925 Session.query(cls).filter(cls.users_group_id == users_group_id)\
934 Session.query(cls).filter(cls.users_group_id == users_group_id)\
926 .filter(cls.permission == perm).delete()
935 .filter(cls.permission == perm).delete()
927 Session.commit()
936 Session.commit()
928 except:
937 except:
929 Session.rollback()
938 Session.rollback()
930
939
931
940
932 class GroupToPerm(Base, BaseModel):
941 class GroupToPerm(Base, BaseModel):
933 __tablename__ = 'group_to_perm'
942 __tablename__ = 'group_to_perm'
934 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
943 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
935
944
936 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
945 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
937 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
946 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
938 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
947 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
939 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
948 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
940
949
941 user = relationship('User')
950 user = relationship('User')
942 permission = relationship('Permission')
951 permission = relationship('Permission')
943 group = relationship('Group')
952 group = relationship('Group')
944
953
945 class Statistics(Base, BaseModel):
954 class Statistics(Base, BaseModel):
946 __tablename__ = 'statistics'
955 __tablename__ = 'statistics'
947 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
956 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
948 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
957 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
949 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
958 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
950 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
959 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
951 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
960 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
952 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
961 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
953 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
962 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
954
963
955 repository = relationship('Repository', single_parent=True)
964 repository = relationship('Repository', single_parent=True)
956
965
957 class UserFollowing(Base, BaseModel):
966 class UserFollowing(Base, BaseModel):
958 __tablename__ = 'user_followings'
967 __tablename__ = 'user_followings'
959 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
968 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
960 UniqueConstraint('user_id', 'follows_user_id')
969 UniqueConstraint('user_id', 'follows_user_id')
961 , {'extend_existing':True})
970 , {'extend_existing':True})
962
971
963 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
972 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
964 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
973 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
965 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
974 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
966 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
975 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
967 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
976 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
968
977
969 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
978 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
970
979
971 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
980 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
972 follows_repository = relationship('Repository', order_by='Repository.repo_name')
981 follows_repository = relationship('Repository', order_by='Repository.repo_name')
973
982
974
983
975 @classmethod
984 @classmethod
976 def get_repo_followers(cls, repo_id):
985 def get_repo_followers(cls, repo_id):
977 return Session.query(cls).filter(cls.follows_repo_id == repo_id)
986 return Session.query(cls).filter(cls.follows_repo_id == repo_id)
978
987
979 class CacheInvalidation(Base, BaseModel):
988 class CacheInvalidation(Base, BaseModel):
980 __tablename__ = 'cache_invalidation'
989 __tablename__ = 'cache_invalidation'
981 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
990 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
982 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
991 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
983 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
992 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
984 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
993 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
985 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
994 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
986
995
987
996
988 def __init__(self, cache_key, cache_args=''):
997 def __init__(self, cache_key, cache_args=''):
989 self.cache_key = cache_key
998 self.cache_key = cache_key
990 self.cache_args = cache_args
999 self.cache_args = cache_args
991 self.cache_active = False
1000 self.cache_active = False
992
1001
993 def __repr__(self):
1002 def __repr__(self):
994 return "<%s('%s:%s')>" % (self.__class__.__name__,
1003 return "<%s('%s:%s')>" % (self.__class__.__name__,
995 self.cache_id, self.cache_key)
1004 self.cache_id, self.cache_key)
996
1005
997 class DbMigrateVersion(Base, BaseModel):
1006 class DbMigrateVersion(Base, BaseModel):
998 __tablename__ = 'db_migrate_version'
1007 __tablename__ = 'db_migrate_version'
999 __table_args__ = {'extend_existing':True}
1008 __table_args__ = {'extend_existing':True}
1000 repository_id = Column('repository_id', String(250), primary_key=True)
1009 repository_id = Column('repository_id', String(250), primary_key=True)
1001 repository_path = Column('repository_path', Text)
1010 repository_path = Column('repository_path', Text)
1002 version = Column('version', Integer)
1011 version = Column('version', Integer)
@@ -1,681 +1,681
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 import traceback
25 import traceback
26
26
27 import formencode
27 import formencode
28 from formencode import All
28 from formencode import All
29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
30 Email, Bool, StringBoolean, Set
30 Email, Bool, StringBoolean, Set
31
31
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from webhelpers.pylonslib.secure_form import authentication_token
33 from webhelpers.pylonslib.secure_form import authentication_token
34
34
35 from rhodecode.config.routing import ADMIN_PREFIX
35 from rhodecode.config.routing import ADMIN_PREFIX
36 from rhodecode.lib.utils import repo_name_slug
36 from rhodecode.lib.utils import repo_name_slug
37 from rhodecode.lib.auth import authenticate, get_crypt_password
37 from rhodecode.lib.auth import authenticate, get_crypt_password
38 from rhodecode.lib.exceptions import LdapImportError
38 from rhodecode.lib.exceptions import LdapImportError
39 from rhodecode.model.user import UserModel
39 from rhodecode.model.user import UserModel
40 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.repo import RepoModel
41 from rhodecode.model.db import User, UsersGroup, Group
41 from rhodecode.model.db import User, UsersGroup, Group
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 User.get_by_username(value, case_insensitive=True):
74 if User.get_by_username(value, case_insensitive=True):
75 raise formencode.Invalid(_('This username already '
75 raise formencode.Invalid(_('This username already '
76 'exists') , value, state)
76 'exists') , value, state)
77
77
78 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
78 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
79 raise formencode.Invalid(_('Username may only contain '
79 raise formencode.Invalid(_('Username may only contain '
80 'alphanumeric characters '
80 'alphanumeric characters '
81 'underscores, periods or dashes '
81 'underscores, periods or dashes '
82 'and must begin with alphanumeric '
82 'and must begin with alphanumeric '
83 'character'), value, state)
83 'character'), value, state)
84
84
85 return _ValidUsername
85 return _ValidUsername
86
86
87
87
88 def ValidUsersGroup(edit, old_data):
88 def ValidUsersGroup(edit, old_data):
89
89
90 class _ValidUsersGroup(formencode.validators.FancyValidator):
90 class _ValidUsersGroup(formencode.validators.FancyValidator):
91
91
92 def validate_python(self, value, state):
92 def validate_python(self, value, state):
93 if value in ['default']:
93 if value in ['default']:
94 raise formencode.Invalid(_('Invalid group name'), value, state)
94 raise formencode.Invalid(_('Invalid group name'), value, state)
95 #check if group is unique
95 #check if group is unique
96 old_ugname = None
96 old_ugname = None
97 if edit:
97 if edit:
98 old_ugname = UsersGroup.get(
98 old_ugname = UsersGroup.get(
99 old_data.get('users_group_id')).users_group_name
99 old_data.get('users_group_id')).users_group_name
100
100
101 if old_ugname != value or not edit:
101 if old_ugname != value or not edit:
102 if UsersGroup.get_by_group_name(value, cache=False,
102 if UsersGroup.get_by_group_name(value, cache=False,
103 case_insensitive=True):
103 case_insensitive=True):
104 raise formencode.Invalid(_('This users group '
104 raise formencode.Invalid(_('This users group '
105 'already exists') , value,
105 'already exists') , value,
106 state)
106 state)
107
107
108
108
109 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
109 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
110 raise formencode.Invalid(_('Group name may only contain '
110 raise formencode.Invalid(_('Group name may only contain '
111 'alphanumeric characters '
111 'alphanumeric characters '
112 'underscores, periods or dashes '
112 'underscores, periods or dashes '
113 'and must begin with alphanumeric '
113 'and must begin with alphanumeric '
114 'character'), value, state)
114 'character'), value, state)
115
115
116 return _ValidUsersGroup
116 return _ValidUsersGroup
117
117
118
118
119 def ValidReposGroup(edit, old_data):
119 def ValidReposGroup(edit, old_data):
120 class _ValidReposGroup(formencode.validators.FancyValidator):
120 class _ValidReposGroup(formencode.validators.FancyValidator):
121
121
122 def validate_python(self, value, state):
122 def validate_python(self, value, state):
123 #TODO WRITE VALIDATIONS
123 #TODO WRITE VALIDATIONS
124 group_name = value.get('group_name')
124 group_name = value.get('group_name')
125 group_parent_id = int(value.get('group_parent_id') or - 1)
125 group_parent_id = int(value.get('group_parent_id') or -1)
126
126
127 # slugify repo group just in case :)
127 # slugify repo group just in case :)
128 slug = repo_name_slug(group_name)
128 slug = repo_name_slug(group_name)
129
129
130 # check for parent of self
130 # check for parent of self
131 if edit and old_data['group_id'] == group_parent_id:
131 if edit and old_data['group_id'] == group_parent_id:
132 e_dict = {'group_parent_id':_('Cannot assign this group '
132 e_dict = {'group_parent_id':_('Cannot assign this group '
133 'as parent')}
133 'as parent')}
134 raise formencode.Invalid('', value, state,
134 raise formencode.Invalid('', value, state,
135 error_dict=e_dict)
135 error_dict=e_dict)
136
136
137 old_gname = None
137 old_gname = None
138 if edit:
138 if edit:
139 old_gname = Group.get(
139 old_gname = Group.get(
140 old_data.get('group_id')).group_name
140 old_data.get('group_id')).group_name
141
141
142 if old_gname != group_name or not edit:
142 if old_gname != group_name or not edit:
143 # check filesystem
143 # check filesystem
144 gr = Group.query().filter(Group.group_name == slug)\
144 gr = Group.query().filter(Group.group_name == slug)\
145 .filter(Group.group_parent_id == group_parent_id).scalar()
145 .filter(Group.group_parent_id == group_parent_id).scalar()
146
146
147 if gr:
147 if gr:
148 e_dict = {'group_name':_('This group already exists')}
148 e_dict = {'group_name':_('This group already exists')}
149 raise formencode.Invalid('', value, state,
149 raise formencode.Invalid('', value, state,
150 error_dict=e_dict)
150 error_dict=e_dict)
151
151
152 return _ValidReposGroup
152 return _ValidReposGroup
153
153
154 class ValidPassword(formencode.validators.FancyValidator):
154 class ValidPassword(formencode.validators.FancyValidator):
155
155
156 def to_python(self, value, state):
156 def to_python(self, value, state):
157
157
158 if value:
158 if value:
159
159
160 if value.get('password'):
160 if value.get('password'):
161 try:
161 try:
162 value['password'] = get_crypt_password(value['password'])
162 value['password'] = get_crypt_password(value['password'])
163 except UnicodeEncodeError:
163 except UnicodeEncodeError:
164 e_dict = {'password':_('Invalid characters in password')}
164 e_dict = {'password':_('Invalid characters in password')}
165 raise formencode.Invalid('', value, state, error_dict=e_dict)
165 raise formencode.Invalid('', value, state, error_dict=e_dict)
166
166
167 if value.get('password_confirmation'):
167 if value.get('password_confirmation'):
168 try:
168 try:
169 value['password_confirmation'] = \
169 value['password_confirmation'] = \
170 get_crypt_password(value['password_confirmation'])
170 get_crypt_password(value['password_confirmation'])
171 except UnicodeEncodeError:
171 except UnicodeEncodeError:
172 e_dict = {'password_confirmation':_('Invalid characters in password')}
172 e_dict = {'password_confirmation':_('Invalid characters in password')}
173 raise formencode.Invalid('', value, state, error_dict=e_dict)
173 raise formencode.Invalid('', value, state, error_dict=e_dict)
174
174
175 if value.get('new_password'):
175 if value.get('new_password'):
176 try:
176 try:
177 value['new_password'] = \
177 value['new_password'] = \
178 get_crypt_password(value['new_password'])
178 get_crypt_password(value['new_password'])
179 except UnicodeEncodeError:
179 except UnicodeEncodeError:
180 e_dict = {'new_password':_('Invalid characters in password')}
180 e_dict = {'new_password':_('Invalid characters in password')}
181 raise formencode.Invalid('', value, state, error_dict=e_dict)
181 raise formencode.Invalid('', value, state, error_dict=e_dict)
182
182
183 return value
183 return value
184
184
185 class ValidPasswordsMatch(formencode.validators.FancyValidator):
185 class ValidPasswordsMatch(formencode.validators.FancyValidator):
186
186
187 def validate_python(self, value, state):
187 def validate_python(self, value, state):
188
188
189 if value['password'] != value['password_confirmation']:
189 if value['password'] != value['password_confirmation']:
190 e_dict = {'password_confirmation':
190 e_dict = {'password_confirmation':
191 _('Passwords do not match')}
191 _('Passwords do not match')}
192 raise formencode.Invalid('', value, state, error_dict=e_dict)
192 raise formencode.Invalid('', value, state, error_dict=e_dict)
193
193
194 class ValidAuth(formencode.validators.FancyValidator):
194 class ValidAuth(formencode.validators.FancyValidator):
195 messages = {
195 messages = {
196 'invalid_password':_('invalid password'),
196 'invalid_password':_('invalid password'),
197 'invalid_login':_('invalid user name'),
197 'invalid_login':_('invalid user name'),
198 'disabled_account':_('Your account is disabled')
198 'disabled_account':_('Your account is disabled')
199
199
200 }
200 }
201 #error mapping
201 #error mapping
202 e_dict = {'username':messages['invalid_login'],
202 e_dict = {'username':messages['invalid_login'],
203 'password':messages['invalid_password']}
203 'password':messages['invalid_password']}
204 e_dict_disable = {'username':messages['disabled_account']}
204 e_dict_disable = {'username':messages['disabled_account']}
205
205
206 def validate_python(self, value, state):
206 def validate_python(self, value, state):
207 password = value['password']
207 password = value['password']
208 username = value['username']
208 username = value['username']
209 user = User.get_by_username(username)
209 user = User.get_by_username(username)
210
210
211 if authenticate(username, password):
211 if authenticate(username, password):
212 return value
212 return value
213 else:
213 else:
214 if user and user.active is False:
214 if user and user.active is False:
215 log.warning('user %s is disabled', username)
215 log.warning('user %s is disabled', username)
216 raise formencode.Invalid(self.message('disabled_account',
216 raise formencode.Invalid(self.message('disabled_account',
217 state=State_obj),
217 state=State_obj),
218 value, state,
218 value, state,
219 error_dict=self.e_dict_disable)
219 error_dict=self.e_dict_disable)
220 else:
220 else:
221 log.warning('user %s not authenticated', username)
221 log.warning('user %s not authenticated', username)
222 raise formencode.Invalid(self.message('invalid_password',
222 raise formencode.Invalid(self.message('invalid_password',
223 state=State_obj), value, state,
223 state=State_obj), value, state,
224 error_dict=self.e_dict)
224 error_dict=self.e_dict)
225
225
226 class ValidRepoUser(formencode.validators.FancyValidator):
226 class ValidRepoUser(formencode.validators.FancyValidator):
227
227
228 def to_python(self, value, state):
228 def to_python(self, value, state):
229 try:
229 try:
230 User.query().filter(User.active == True)\
230 User.query().filter(User.active == True)\
231 .filter(User.username == value).one()
231 .filter(User.username == value).one()
232 except Exception:
232 except Exception:
233 raise formencode.Invalid(_('This username is not valid'),
233 raise formencode.Invalid(_('This username is not valid'),
234 value, state)
234 value, state)
235 return value
235 return value
236
236
237 def ValidRepoName(edit, old_data):
237 def ValidRepoName(edit, old_data):
238 class _ValidRepoName(formencode.validators.FancyValidator):
238 class _ValidRepoName(formencode.validators.FancyValidator):
239 def to_python(self, value, state):
239 def to_python(self, value, state):
240
240
241 repo_name = value.get('repo_name')
241 repo_name = value.get('repo_name')
242
242
243 slug = repo_name_slug(repo_name)
243 slug = repo_name_slug(repo_name)
244 if slug in [ADMIN_PREFIX, '']:
244 if slug in [ADMIN_PREFIX, '']:
245 e_dict = {'repo_name': _('This repository name is disallowed')}
245 e_dict = {'repo_name': _('This repository name is disallowed')}
246 raise formencode.Invalid('', value, state, error_dict=e_dict)
246 raise formencode.Invalid('', value, state, error_dict=e_dict)
247
247
248
248
249 if value.get('repo_group'):
249 if value.get('repo_group'):
250 gr = Group.get(value.get('repo_group'))
250 gr = Group.get(value.get('repo_group'))
251 group_path = gr.full_path
251 group_path = gr.full_path
252 # value needs to be aware of group name in order to check
252 # value needs to be aware of group name in order to check
253 # db key This is an actuall just the name to store in the
253 # db key This is an actuall just the name to store in the
254 # database
254 # database
255 repo_name_full = group_path + Group.url_sep() + repo_name
255 repo_name_full = group_path + Group.url_sep() + repo_name
256 else:
256 else:
257 group_path = ''
257 group_path = ''
258 repo_name_full = repo_name
258 repo_name_full = repo_name
259
259
260
260
261 value['repo_name_full'] = repo_name_full
261 value['repo_name_full'] = repo_name_full
262 if old_data.get('repo_name') != repo_name_full or not edit:
262 if old_data.get('repo_name') != repo_name_full or not edit:
263
263
264 if group_path != '':
264 if group_path != '':
265 if RepoModel().get_by_repo_name(repo_name_full,):
265 if RepoModel().get_by_repo_name(repo_name_full,):
266 e_dict = {'repo_name':_('This repository already '
266 e_dict = {'repo_name':_('This repository already '
267 'exists in group "%s"') %
267 'exists in group "%s"') %
268 gr.group_name}
268 gr.group_name}
269 raise formencode.Invalid('', value, state,
269 raise formencode.Invalid('', value, state,
270 error_dict=e_dict)
270 error_dict=e_dict)
271
271
272 else:
272 else:
273 if RepoModel().get_by_repo_name(repo_name_full):
273 if RepoModel().get_by_repo_name(repo_name_full):
274 e_dict = {'repo_name':_('This repository '
274 e_dict = {'repo_name':_('This repository '
275 'already exists')}
275 'already exists')}
276 raise formencode.Invalid('', value, state,
276 raise formencode.Invalid('', value, state,
277 error_dict=e_dict)
277 error_dict=e_dict)
278 return value
278 return value
279
279
280
280
281 return _ValidRepoName
281 return _ValidRepoName
282
282
283 def ValidForkName():
283 def ValidForkName():
284 class _ValidForkName(formencode.validators.FancyValidator):
284 class _ValidForkName(formencode.validators.FancyValidator):
285 def to_python(self, value, state):
285 def to_python(self, value, state):
286
286
287 repo_name = value.get('fork_name')
287 repo_name = value.get('fork_name')
288
288
289 slug = repo_name_slug(repo_name)
289 slug = repo_name_slug(repo_name)
290 if slug in [ADMIN_PREFIX, '']:
290 if slug in [ADMIN_PREFIX, '']:
291 e_dict = {'repo_name': _('This repository name is disallowed')}
291 e_dict = {'repo_name': _('This repository name is disallowed')}
292 raise formencode.Invalid('', value, state, error_dict=e_dict)
292 raise formencode.Invalid('', value, state, error_dict=e_dict)
293
293
294 if RepoModel().get_by_repo_name(repo_name):
294 if RepoModel().get_by_repo_name(repo_name):
295 e_dict = {'fork_name':_('This repository '
295 e_dict = {'fork_name':_('This repository '
296 'already exists')}
296 'already exists')}
297 raise formencode.Invalid('', value, state,
297 raise formencode.Invalid('', value, state,
298 error_dict=e_dict)
298 error_dict=e_dict)
299 return value
299 return value
300 return _ValidForkName
300 return _ValidForkName
301
301
302
302
303 def SlugifyName():
303 def SlugifyName():
304 class _SlugifyName(formencode.validators.FancyValidator):
304 class _SlugifyName(formencode.validators.FancyValidator):
305
305
306 def to_python(self, value, state):
306 def to_python(self, value, state):
307 return repo_name_slug(value)
307 return repo_name_slug(value)
308
308
309 return _SlugifyName
309 return _SlugifyName
310
310
311 def ValidCloneUri():
311 def ValidCloneUri():
312 from mercurial.httprepo import httprepository, httpsrepository
312 from mercurial.httprepo import httprepository, httpsrepository
313 from rhodecode.lib.utils import make_ui
313 from rhodecode.lib.utils import make_ui
314
314
315 class _ValidCloneUri(formencode.validators.FancyValidator):
315 class _ValidCloneUri(formencode.validators.FancyValidator):
316
316
317 def to_python(self, value, state):
317 def to_python(self, value, state):
318 if not value:
318 if not value:
319 pass
319 pass
320 elif value.startswith('https'):
320 elif value.startswith('https'):
321 try:
321 try:
322 httpsrepository(make_ui('db'), value).capabilities
322 httpsrepository(make_ui('db'), value).capabilities
323 except Exception, e:
323 except Exception, e:
324 log.error(traceback.format_exc())
324 log.error(traceback.format_exc())
325 raise formencode.Invalid(_('invalid clone url'), value,
325 raise formencode.Invalid(_('invalid clone url'), value,
326 state)
326 state)
327 elif value.startswith('http'):
327 elif value.startswith('http'):
328 try:
328 try:
329 httprepository(make_ui('db'), value).capabilities
329 httprepository(make_ui('db'), value).capabilities
330 except Exception, e:
330 except Exception, e:
331 log.error(traceback.format_exc())
331 log.error(traceback.format_exc())
332 raise formencode.Invalid(_('invalid clone url'), value,
332 raise formencode.Invalid(_('invalid clone url'), value,
333 state)
333 state)
334 else:
334 else:
335 raise formencode.Invalid(_('Invalid clone url, provide a '
335 raise formencode.Invalid(_('Invalid clone url, provide a '
336 'valid clone http\s url'), value,
336 'valid clone http\s url'), value,
337 state)
337 state)
338 return value
338 return value
339
339
340 return _ValidCloneUri
340 return _ValidCloneUri
341
341
342 def ValidForkType(old_data):
342 def ValidForkType(old_data):
343 class _ValidForkType(formencode.validators.FancyValidator):
343 class _ValidForkType(formencode.validators.FancyValidator):
344
344
345 def to_python(self, value, state):
345 def to_python(self, value, state):
346 if old_data['repo_type'] != value:
346 if old_data['repo_type'] != value:
347 raise formencode.Invalid(_('Fork have to be the same '
347 raise formencode.Invalid(_('Fork have to be the same '
348 'type as original'), value, state)
348 'type as original'), value, state)
349
349
350 return value
350 return value
351 return _ValidForkType
351 return _ValidForkType
352
352
353 class ValidPerms(formencode.validators.FancyValidator):
353 class ValidPerms(formencode.validators.FancyValidator):
354 messages = {'perm_new_member_name':_('This username or users group name'
354 messages = {'perm_new_member_name':_('This username or users group name'
355 ' is not valid')}
355 ' is not valid')}
356
356
357 def to_python(self, value, state):
357 def to_python(self, value, state):
358 perms_update = []
358 perms_update = []
359 perms_new = []
359 perms_new = []
360 #build a list of permission to update and new permission to create
360 #build a list of permission to update and new permission to create
361 for k, v in value.items():
361 for k, v in value.items():
362 #means new added member to permissions
362 #means new added member to permissions
363 if k.startswith('perm_new_member'):
363 if k.startswith('perm_new_member'):
364 new_perm = value.get('perm_new_member', False)
364 new_perm = value.get('perm_new_member', False)
365 new_member = value.get('perm_new_member_name', False)
365 new_member = value.get('perm_new_member_name', False)
366 new_type = value.get('perm_new_member_type')
366 new_type = value.get('perm_new_member_type')
367
367
368 if new_member and new_perm:
368 if new_member and new_perm:
369 if (new_member, new_perm, new_type) not in perms_new:
369 if (new_member, new_perm, new_type) not in perms_new:
370 perms_new.append((new_member, new_perm, new_type))
370 perms_new.append((new_member, new_perm, new_type))
371 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
371 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
372 member = k[7:]
372 member = k[7:]
373 t = {'u':'user',
373 t = {'u':'user',
374 'g':'users_group'}[k[0]]
374 'g':'users_group'}[k[0]]
375 if member == 'default':
375 if member == 'default':
376 if value['private']:
376 if value['private']:
377 #set none for default when updating to private repo
377 #set none for default when updating to private repo
378 v = 'repository.none'
378 v = 'repository.none'
379 perms_update.append((member, v, t))
379 perms_update.append((member, v, t))
380
380
381 value['perms_updates'] = perms_update
381 value['perms_updates'] = perms_update
382 value['perms_new'] = perms_new
382 value['perms_new'] = perms_new
383
383
384 #update permissions
384 #update permissions
385 for k, v, t in perms_new:
385 for k, v, t in perms_new:
386 try:
386 try:
387 if t is 'user':
387 if t is 'user':
388 self.user_db = User.query()\
388 self.user_db = User.query()\
389 .filter(User.active == True)\
389 .filter(User.active == True)\
390 .filter(User.username == k).one()
390 .filter(User.username == k).one()
391 if t is 'users_group':
391 if t is 'users_group':
392 self.user_db = UsersGroup.query()\
392 self.user_db = UsersGroup.query()\
393 .filter(UsersGroup.users_group_active == True)\
393 .filter(UsersGroup.users_group_active == True)\
394 .filter(UsersGroup.users_group_name == k).one()
394 .filter(UsersGroup.users_group_name == k).one()
395
395
396 except Exception:
396 except Exception:
397 msg = self.message('perm_new_member_name',
397 msg = self.message('perm_new_member_name',
398 state=State_obj)
398 state=State_obj)
399 raise formencode.Invalid(msg, value, state,
399 raise formencode.Invalid(msg, value, state,
400 error_dict={'perm_new_member_name':msg})
400 error_dict={'perm_new_member_name':msg})
401 return value
401 return value
402
402
403 class ValidSettings(formencode.validators.FancyValidator):
403 class ValidSettings(formencode.validators.FancyValidator):
404
404
405 def to_python(self, value, state):
405 def to_python(self, value, state):
406 #settings form can't edit user
406 #settings form can't edit user
407 if value.has_key('user'):
407 if value.has_key('user'):
408 del['value']['user']
408 del['value']['user']
409
409
410 return value
410 return value
411
411
412 class ValidPath(formencode.validators.FancyValidator):
412 class ValidPath(formencode.validators.FancyValidator):
413 def to_python(self, value, state):
413 def to_python(self, value, state):
414
414
415 if not os.path.isdir(value):
415 if not os.path.isdir(value):
416 msg = _('This is not a valid path')
416 msg = _('This is not a valid path')
417 raise formencode.Invalid(msg, value, state,
417 raise formencode.Invalid(msg, value, state,
418 error_dict={'paths_root_path':msg})
418 error_dict={'paths_root_path':msg})
419 return value
419 return value
420
420
421 def UniqSystemEmail(old_data):
421 def UniqSystemEmail(old_data):
422 class _UniqSystemEmail(formencode.validators.FancyValidator):
422 class _UniqSystemEmail(formencode.validators.FancyValidator):
423 def to_python(self, value, state):
423 def to_python(self, value, state):
424 value = value.lower()
424 value = value.lower()
425 if old_data.get('email') != value:
425 if old_data.get('email') != value:
426 user = User.query().filter(User.email == value).scalar()
426 user = User.query().filter(User.email == value).scalar()
427 if user:
427 if user:
428 raise formencode.Invalid(
428 raise formencode.Invalid(
429 _("This e-mail address is already taken"),
429 _("This e-mail address is already taken"),
430 value, state)
430 value, state)
431 return value
431 return value
432
432
433 return _UniqSystemEmail
433 return _UniqSystemEmail
434
434
435 class ValidSystemEmail(formencode.validators.FancyValidator):
435 class ValidSystemEmail(formencode.validators.FancyValidator):
436 def to_python(self, value, state):
436 def to_python(self, value, state):
437 value = value.lower()
437 value = value.lower()
438 user = User.query().filter(User.email == value).scalar()
438 user = User.query().filter(User.email == value).scalar()
439 if user is None:
439 if user is None:
440 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
440 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
441 value, state)
441 value, state)
442
442
443 return value
443 return value
444
444
445 class LdapLibValidator(formencode.validators.FancyValidator):
445 class LdapLibValidator(formencode.validators.FancyValidator):
446
446
447 def to_python(self, value, state):
447 def to_python(self, value, state):
448
448
449 try:
449 try:
450 import ldap
450 import ldap
451 except ImportError:
451 except ImportError:
452 raise LdapImportError
452 raise LdapImportError
453 return value
453 return value
454
454
455 class AttrLoginValidator(formencode.validators.FancyValidator):
455 class AttrLoginValidator(formencode.validators.FancyValidator):
456
456
457 def to_python(self, value, state):
457 def to_python(self, value, state):
458
458
459 if not value or not isinstance(value, (str, unicode)):
459 if not value or not isinstance(value, (str, unicode)):
460 raise formencode.Invalid(_("The LDAP Login attribute of the CN "
460 raise formencode.Invalid(_("The LDAP Login attribute of the CN "
461 "must be specified - this is the name "
461 "must be specified - this is the name "
462 "of the attribute that is equivalent "
462 "of the attribute that is equivalent "
463 "to 'username'"),
463 "to 'username'"),
464 value, state)
464 value, state)
465
465
466 return value
466 return value
467
467
468 #===============================================================================
468 #===============================================================================
469 # FORMS
469 # FORMS
470 #===============================================================================
470 #===============================================================================
471 class LoginForm(formencode.Schema):
471 class LoginForm(formencode.Schema):
472 allow_extra_fields = True
472 allow_extra_fields = True
473 filter_extra_fields = True
473 filter_extra_fields = True
474 username = UnicodeString(
474 username = UnicodeString(
475 strip=True,
475 strip=True,
476 min=1,
476 min=1,
477 not_empty=True,
477 not_empty=True,
478 messages={
478 messages={
479 'empty':_('Please enter a login'),
479 'empty':_('Please enter a login'),
480 'tooShort':_('Enter a value %(min)i characters long or more')}
480 'tooShort':_('Enter a value %(min)i characters long or more')}
481 )
481 )
482
482
483 password = UnicodeString(
483 password = UnicodeString(
484 strip=True,
484 strip=True,
485 min=3,
485 min=3,
486 not_empty=True,
486 not_empty=True,
487 messages={
487 messages={
488 'empty':_('Please enter a password'),
488 'empty':_('Please enter a password'),
489 'tooShort':_('Enter %(min)i characters or more')}
489 'tooShort':_('Enter %(min)i characters or more')}
490 )
490 )
491
491
492
492
493 #chained validators have access to all data
493 #chained validators have access to all data
494 chained_validators = [ValidAuth]
494 chained_validators = [ValidAuth]
495
495
496 def UserForm(edit=False, old_data={}):
496 def UserForm(edit=False, old_data={}):
497 class _UserForm(formencode.Schema):
497 class _UserForm(formencode.Schema):
498 allow_extra_fields = True
498 allow_extra_fields = True
499 filter_extra_fields = True
499 filter_extra_fields = True
500 username = All(UnicodeString(strip=True, min=1, not_empty=True),
500 username = All(UnicodeString(strip=True, min=1, not_empty=True),
501 ValidUsername(edit, old_data))
501 ValidUsername(edit, old_data))
502 if edit:
502 if edit:
503 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
503 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
504 admin = StringBoolean(if_missing=False)
504 admin = StringBoolean(if_missing=False)
505 else:
505 else:
506 password = All(UnicodeString(strip=True, min=6, not_empty=True))
506 password = All(UnicodeString(strip=True, min=6, not_empty=True))
507 active = StringBoolean(if_missing=False)
507 active = StringBoolean(if_missing=False)
508 name = UnicodeString(strip=True, min=1, not_empty=True)
508 name = UnicodeString(strip=True, min=1, not_empty=True)
509 lastname = UnicodeString(strip=True, min=1, not_empty=True)
509 lastname = UnicodeString(strip=True, min=1, not_empty=True)
510 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
510 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
511
511
512 chained_validators = [ValidPassword]
512 chained_validators = [ValidPassword]
513
513
514 return _UserForm
514 return _UserForm
515
515
516
516
517 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
517 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
518 class _UsersGroupForm(formencode.Schema):
518 class _UsersGroupForm(formencode.Schema):
519 allow_extra_fields = True
519 allow_extra_fields = True
520 filter_extra_fields = True
520 filter_extra_fields = True
521
521
522 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
522 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
523 ValidUsersGroup(edit, old_data))
523 ValidUsersGroup(edit, old_data))
524
524
525 users_group_active = StringBoolean(if_missing=False)
525 users_group_active = StringBoolean(if_missing=False)
526
526
527 if edit:
527 if edit:
528 users_group_members = OneOf(available_members, hideList=False,
528 users_group_members = OneOf(available_members, hideList=False,
529 testValueList=True,
529 testValueList=True,
530 if_missing=None, not_empty=False)
530 if_missing=None, not_empty=False)
531
531
532 return _UsersGroupForm
532 return _UsersGroupForm
533
533
534 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
534 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
535 class _ReposGroupForm(formencode.Schema):
535 class _ReposGroupForm(formencode.Schema):
536 allow_extra_fields = True
536 allow_extra_fields = True
537 filter_extra_fields = True
537 filter_extra_fields = True
538
538
539 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
539 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
540 SlugifyName())
540 SlugifyName())
541 group_description = UnicodeString(strip=True, min=1,
541 group_description = UnicodeString(strip=True, min=1,
542 not_empty=True)
542 not_empty=True)
543 group_parent_id = OneOf(available_groups, hideList=False,
543 group_parent_id = OneOf(available_groups, hideList=False,
544 testValueList=True,
544 testValueList=True,
545 if_missing=None, not_empty=False)
545 if_missing=None, not_empty=False)
546
546
547 chained_validators = [ValidReposGroup(edit, old_data)]
547 chained_validators = [ValidReposGroup(edit, old_data)]
548
548
549 return _ReposGroupForm
549 return _ReposGroupForm
550
550
551 def RegisterForm(edit=False, old_data={}):
551 def RegisterForm(edit=False, old_data={}):
552 class _RegisterForm(formencode.Schema):
552 class _RegisterForm(formencode.Schema):
553 allow_extra_fields = True
553 allow_extra_fields = True
554 filter_extra_fields = True
554 filter_extra_fields = True
555 username = All(ValidUsername(edit, old_data),
555 username = All(ValidUsername(edit, old_data),
556 UnicodeString(strip=True, min=1, not_empty=True))
556 UnicodeString(strip=True, min=1, not_empty=True))
557 password = All(UnicodeString(strip=True, min=6, not_empty=True))
557 password = All(UnicodeString(strip=True, min=6, not_empty=True))
558 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
558 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
559 active = StringBoolean(if_missing=False)
559 active = StringBoolean(if_missing=False)
560 name = UnicodeString(strip=True, min=1, not_empty=True)
560 name = UnicodeString(strip=True, min=1, not_empty=True)
561 lastname = UnicodeString(strip=True, min=1, not_empty=True)
561 lastname = UnicodeString(strip=True, min=1, not_empty=True)
562 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
562 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
563
563
564 chained_validators = [ValidPasswordsMatch, ValidPassword]
564 chained_validators = [ValidPasswordsMatch, ValidPassword]
565
565
566 return _RegisterForm
566 return _RegisterForm
567
567
568 def PasswordResetForm():
568 def PasswordResetForm():
569 class _PasswordResetForm(formencode.Schema):
569 class _PasswordResetForm(formencode.Schema):
570 allow_extra_fields = True
570 allow_extra_fields = True
571 filter_extra_fields = True
571 filter_extra_fields = True
572 email = All(ValidSystemEmail(), Email(not_empty=True))
572 email = All(ValidSystemEmail(), Email(not_empty=True))
573 return _PasswordResetForm
573 return _PasswordResetForm
574
574
575 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
575 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
576 repo_groups=[]):
576 repo_groups=[]):
577 class _RepoForm(formencode.Schema):
577 class _RepoForm(formencode.Schema):
578 allow_extra_fields = True
578 allow_extra_fields = True
579 filter_extra_fields = False
579 filter_extra_fields = False
580 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
580 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
581 SlugifyName())
581 SlugifyName())
582 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
582 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
583 ValidCloneUri()())
583 ValidCloneUri()())
584 repo_group = OneOf(repo_groups, hideList=True)
584 repo_group = OneOf(repo_groups, hideList=True)
585 repo_type = OneOf(supported_backends)
585 repo_type = OneOf(supported_backends)
586 description = UnicodeString(strip=True, min=1, not_empty=True)
586 description = UnicodeString(strip=True, min=1, not_empty=True)
587 private = StringBoolean(if_missing=False)
587 private = StringBoolean(if_missing=False)
588 enable_statistics = StringBoolean(if_missing=False)
588 enable_statistics = StringBoolean(if_missing=False)
589 enable_downloads = StringBoolean(if_missing=False)
589 enable_downloads = StringBoolean(if_missing=False)
590
590
591 if edit:
591 if edit:
592 #this is repo owner
592 #this is repo owner
593 user = All(UnicodeString(not_empty=True), ValidRepoUser)
593 user = All(UnicodeString(not_empty=True), ValidRepoUser)
594
594
595 chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
595 chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
596 return _RepoForm
596 return _RepoForm
597
597
598 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
598 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
599 class _RepoForkForm(formencode.Schema):
599 class _RepoForkForm(formencode.Schema):
600 allow_extra_fields = True
600 allow_extra_fields = True
601 filter_extra_fields = False
601 filter_extra_fields = False
602 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
602 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
603 SlugifyName())
603 SlugifyName())
604 description = UnicodeString(strip=True, min=1, not_empty=True)
604 description = UnicodeString(strip=True, min=1, not_empty=True)
605 private = StringBoolean(if_missing=False)
605 private = StringBoolean(if_missing=False)
606 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
606 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
607
607
608 chained_validators = [ValidForkName()]
608 chained_validators = [ValidForkName()]
609
609
610 return _RepoForkForm
610 return _RepoForkForm
611
611
612 def RepoSettingsForm(edit=False, old_data={}):
612 def RepoSettingsForm(edit=False, old_data={}):
613 class _RepoForm(formencode.Schema):
613 class _RepoForm(formencode.Schema):
614 allow_extra_fields = True
614 allow_extra_fields = True
615 filter_extra_fields = False
615 filter_extra_fields = False
616 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
616 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
617 SlugifyName())
617 SlugifyName())
618 description = UnicodeString(strip=True, min=1, not_empty=True)
618 description = UnicodeString(strip=True, min=1, not_empty=True)
619 private = StringBoolean(if_missing=False)
619 private = StringBoolean(if_missing=False)
620
620
621 chained_validators = [ValidRepoName(edit, old_data), ValidPerms, ValidSettings]
621 chained_validators = [ValidRepoName(edit, old_data), ValidPerms, ValidSettings]
622 return _RepoForm
622 return _RepoForm
623
623
624
624
625 def ApplicationSettingsForm():
625 def ApplicationSettingsForm():
626 class _ApplicationSettingsForm(formencode.Schema):
626 class _ApplicationSettingsForm(formencode.Schema):
627 allow_extra_fields = True
627 allow_extra_fields = True
628 filter_extra_fields = False
628 filter_extra_fields = False
629 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
629 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
630 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
630 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
631 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
631 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
632
632
633 return _ApplicationSettingsForm
633 return _ApplicationSettingsForm
634
634
635 def ApplicationUiSettingsForm():
635 def ApplicationUiSettingsForm():
636 class _ApplicationUiSettingsForm(formencode.Schema):
636 class _ApplicationUiSettingsForm(formencode.Schema):
637 allow_extra_fields = True
637 allow_extra_fields = True
638 filter_extra_fields = False
638 filter_extra_fields = False
639 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
639 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
640 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
640 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
641 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
641 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
642 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
642 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
643 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
643 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
644 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
644 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
645
645
646 return _ApplicationUiSettingsForm
646 return _ApplicationUiSettingsForm
647
647
648 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
648 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
649 class _DefaultPermissionsForm(formencode.Schema):
649 class _DefaultPermissionsForm(formencode.Schema):
650 allow_extra_fields = True
650 allow_extra_fields = True
651 filter_extra_fields = True
651 filter_extra_fields = True
652 overwrite_default = StringBoolean(if_missing=False)
652 overwrite_default = StringBoolean(if_missing=False)
653 anonymous = OneOf(['True', 'False'], if_missing=False)
653 anonymous = OneOf(['True', 'False'], if_missing=False)
654 default_perm = OneOf(perms_choices)
654 default_perm = OneOf(perms_choices)
655 default_register = OneOf(register_choices)
655 default_register = OneOf(register_choices)
656 default_create = OneOf(create_choices)
656 default_create = OneOf(create_choices)
657
657
658 return _DefaultPermissionsForm
658 return _DefaultPermissionsForm
659
659
660
660
661 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
661 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
662 class _LdapSettingsForm(formencode.Schema):
662 class _LdapSettingsForm(formencode.Schema):
663 allow_extra_fields = True
663 allow_extra_fields = True
664 filter_extra_fields = True
664 filter_extra_fields = True
665 pre_validators = [LdapLibValidator]
665 pre_validators = [LdapLibValidator]
666 ldap_active = StringBoolean(if_missing=False)
666 ldap_active = StringBoolean(if_missing=False)
667 ldap_host = UnicodeString(strip=True,)
667 ldap_host = UnicodeString(strip=True,)
668 ldap_port = Number(strip=True,)
668 ldap_port = Number(strip=True,)
669 ldap_tls_kind = OneOf(tls_kind_choices)
669 ldap_tls_kind = OneOf(tls_kind_choices)
670 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
670 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
671 ldap_dn_user = UnicodeString(strip=True,)
671 ldap_dn_user = UnicodeString(strip=True,)
672 ldap_dn_pass = UnicodeString(strip=True,)
672 ldap_dn_pass = UnicodeString(strip=True,)
673 ldap_base_dn = UnicodeString(strip=True,)
673 ldap_base_dn = UnicodeString(strip=True,)
674 ldap_filter = UnicodeString(strip=True,)
674 ldap_filter = UnicodeString(strip=True,)
675 ldap_search_scope = OneOf(search_scope_choices)
675 ldap_search_scope = OneOf(search_scope_choices)
676 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
676 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
677 ldap_attr_firstname = UnicodeString(strip=True,)
677 ldap_attr_firstname = UnicodeString(strip=True,)
678 ldap_attr_lastname = UnicodeString(strip=True,)
678 ldap_attr_lastname = UnicodeString(strip=True,)
679 ldap_attr_email = UnicodeString(strip=True,)
679 ldap_attr_email = UnicodeString(strip=True,)
680
680
681 return _LdapSettingsForm
681 return _LdapSettingsForm
@@ -1,359 +1,362
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.repo
3 rhodecode.model.repo
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 Repository model for rhodecode
6 Repository model for rhodecode
7
7
8 :created_on: Jun 5, 2010
8 :created_on: Jun 5, 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 modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import os
26 import shutil
26 import shutil
27 import logging
27 import logging
28 import traceback
28 import traceback
29 from datetime import datetime
29 from datetime import datetime
30
30
31 from sqlalchemy.orm import joinedload, make_transient
31 from sqlalchemy.orm import joinedload, make_transient
32
32
33 from vcs.utils.lazy import LazyProperty
33 from vcs.utils.lazy import LazyProperty
34 from vcs.backends import get_backend
34 from vcs.backends import get_backend
35
35
36 from rhodecode.lib import safe_str
36 from rhodecode.lib import safe_str
37
37
38 from rhodecode.model import BaseModel
38 from rhodecode.model import BaseModel
39 from rhodecode.model.caching_query import FromCache
39 from rhodecode.model.caching_query import FromCache
40 from rhodecode.model.db import Repository, RepoToPerm, User, Permission, \
40 from rhodecode.model.db import Repository, RepoToPerm, User, Permission, \
41 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, Group
41 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, Group
42 from rhodecode.model.user import UserModel
42 from rhodecode.model.user import UserModel
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class RepoModel(BaseModel):
47 class RepoModel(BaseModel):
48
48
49 @LazyProperty
49 @LazyProperty
50 def repos_path(self):
50 def repos_path(self):
51 """Get's the repositories root path from database
51 """Get's the repositories root path from database
52 """
52 """
53
53
54 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
54 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
55 return q.ui_value
55 return q.ui_value
56
56
57 def get(self, repo_id, cache=False):
57 def get(self, repo_id, cache=False):
58 repo = self.sa.query(Repository)\
58 repo = self.sa.query(Repository)\
59 .filter(Repository.repo_id == repo_id)
59 .filter(Repository.repo_id == repo_id)
60
60
61 if cache:
61 if cache:
62 repo = repo.options(FromCache("sql_cache_short",
62 repo = repo.options(FromCache("sql_cache_short",
63 "get_repo_%s" % repo_id))
63 "get_repo_%s" % repo_id))
64 return repo.scalar()
64 return repo.scalar()
65
65
66 def get_by_repo_name(self, repo_name, cache=False):
66 def get_by_repo_name(self, repo_name, cache=False):
67 repo = self.sa.query(Repository)\
67 repo = self.sa.query(Repository)\
68 .filter(Repository.repo_name == repo_name)
68 .filter(Repository.repo_name == repo_name)
69
69
70 if cache:
70 if cache:
71 repo = repo.options(FromCache("sql_cache_short",
71 repo = repo.options(FromCache("sql_cache_short",
72 "get_repo_%s" % repo_name))
72 "get_repo_%s" % repo_name))
73 return repo.scalar()
73 return repo.scalar()
74
74
75
75
76 def get_users_js(self):
76 def get_users_js(self):
77
77
78 users = self.sa.query(User).filter(User.active == True).all()
78 users = self.sa.query(User).filter(User.active == True).all()
79 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
79 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
80 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
80 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
81 u.lastname, u.username)
81 u.lastname, u.username)
82 for u in users])
82 for u in users])
83 return users_array
83 return users_array
84
84
85 def get_users_groups_js(self):
85 def get_users_groups_js(self):
86 users_groups = self.sa.query(UsersGroup)\
86 users_groups = self.sa.query(UsersGroup)\
87 .filter(UsersGroup.users_group_active == True).all()
87 .filter(UsersGroup.users_group_active == True).all()
88
88
89 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
89 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
90
90
91 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
91 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
92 (gr.users_group_id, gr.users_group_name,
92 (gr.users_group_id, gr.users_group_name,
93 len(gr.members))
93 len(gr.members))
94 for gr in users_groups])
94 for gr in users_groups])
95 return users_groups_array
95 return users_groups_array
96
96
97 def update(self, repo_name, form_data):
97 def update(self, repo_name, form_data):
98 try:
98 try:
99 cur_repo = self.get_by_repo_name(repo_name, cache=False)
99 cur_repo = self.get_by_repo_name(repo_name, cache=False)
100
100
101 #update permissions
101 # update permissions
102 for member, perm, member_type in form_data['perms_updates']:
102 for member, perm, member_type in form_data['perms_updates']:
103 if member_type == 'user':
103 if member_type == 'user':
104 r2p = self.sa.query(RepoToPerm)\
104 r2p = self.sa.query(RepoToPerm)\
105 .filter(RepoToPerm.user == User.get_by_username(member))\
105 .filter(RepoToPerm.user == User.get_by_username(member))\
106 .filter(RepoToPerm.repository == cur_repo)\
106 .filter(RepoToPerm.repository == cur_repo)\
107 .one()
107 .one()
108
108
109 r2p.permission = self.sa.query(Permission)\
109 r2p.permission = self.sa.query(Permission)\
110 .filter(Permission.permission_name ==
110 .filter(Permission.permission_name ==
111 perm).scalar()
111 perm).scalar()
112 self.sa.add(r2p)
112 self.sa.add(r2p)
113 else:
113 else:
114 g2p = self.sa.query(UsersGroupRepoToPerm)\
114 g2p = self.sa.query(UsersGroupRepoToPerm)\
115 .filter(UsersGroupRepoToPerm.users_group ==
115 .filter(UsersGroupRepoToPerm.users_group ==
116 UsersGroup.get_by_group_name(member))\
116 UsersGroup.get_by_group_name(member))\
117 .filter(UsersGroupRepoToPerm.repository ==
117 .filter(UsersGroupRepoToPerm.repository ==
118 cur_repo).one()
118 cur_repo).one()
119
119
120 g2p.permission = self.sa.query(Permission)\
120 g2p.permission = self.sa.query(Permission)\
121 .filter(Permission.permission_name ==
121 .filter(Permission.permission_name ==
122 perm).scalar()
122 perm).scalar()
123 self.sa.add(g2p)
123 self.sa.add(g2p)
124
124
125 #set new permissions
125 # set new permissions
126 for member, perm, member_type in form_data['perms_new']:
126 for member, perm, member_type in form_data['perms_new']:
127 if member_type == 'user':
127 if member_type == 'user':
128 r2p = RepoToPerm()
128 r2p = RepoToPerm()
129 r2p.repository = cur_repo
129 r2p.repository = cur_repo
130 r2p.user = User.get_by_username(member)
130 r2p.user = User.get_by_username(member)
131
131
132 r2p.permission = self.sa.query(Permission)\
132 r2p.permission = self.sa.query(Permission)\
133 .filter(Permission.
133 .filter(Permission.
134 permission_name == perm)\
134 permission_name == perm)\
135 .scalar()
135 .scalar()
136 self.sa.add(r2p)
136 self.sa.add(r2p)
137 else:
137 else:
138 g2p = UsersGroupRepoToPerm()
138 g2p = UsersGroupRepoToPerm()
139 g2p.repository = cur_repo
139 g2p.repository = cur_repo
140 g2p.users_group = UsersGroup.get_by_group_name(member)
140 g2p.users_group = UsersGroup.get_by_group_name(member)
141 g2p.permission = self.sa.query(Permission)\
141 g2p.permission = self.sa.query(Permission)\
142 .filter(Permission.
142 .filter(Permission.
143 permission_name == perm)\
143 permission_name == perm)\
144 .scalar()
144 .scalar()
145 self.sa.add(g2p)
145 self.sa.add(g2p)
146
146
147 #update current repo
147 # update current repo
148 for k, v in form_data.items():
148 for k, v in form_data.items():
149 if k == 'user':
149 if k == 'user':
150 cur_repo.user = User.get_by_username(v)
150 cur_repo.user = User.get_by_username(v)
151 elif k == 'repo_name':
151 elif k == 'repo_name':
152 cur_repo.repo_name = form_data['repo_name_full']
152 pass
153 elif k == 'repo_group':
153 elif k == 'repo_group':
154 cur_repo.group_id = v
154 cur_repo.group_id = v
155
155
156 else:
156 else:
157 setattr(cur_repo, k, v)
157 setattr(cur_repo, k, v)
158
158
159 new_name = cur_repo.get_new_name(form_data['repo_name'])
160 cur_repo.repo_name = new_name
161
159 self.sa.add(cur_repo)
162 self.sa.add(cur_repo)
160
163
161 if repo_name != form_data['repo_name_full']:
164 if repo_name != new_name:
162 # rename repository
165 # rename repository
163 self.__rename_repo(old=repo_name,
166 self.__rename_repo(old=repo_name, new=new_name)
164 new=form_data['repo_name_full'])
165
167
166 self.sa.commit()
168 self.sa.commit()
169 return cur_repo
167 except:
170 except:
168 log.error(traceback.format_exc())
171 log.error(traceback.format_exc())
169 self.sa.rollback()
172 self.sa.rollback()
170 raise
173 raise
171
174
172 def create(self, form_data, cur_user, just_db=False, fork=False):
175 def create(self, form_data, cur_user, just_db=False, fork=False):
173
176
174 try:
177 try:
175 if fork:
178 if fork:
176 repo_name = form_data['fork_name']
179 repo_name = form_data['fork_name']
177 org_name = form_data['repo_name']
180 org_name = form_data['repo_name']
178 org_full_name = org_name
181 org_full_name = org_name
179
182
180 else:
183 else:
181 org_name = repo_name = form_data['repo_name']
184 org_name = repo_name = form_data['repo_name']
182 repo_name_full = form_data['repo_name_full']
185 repo_name_full = form_data['repo_name_full']
183
186
184 new_repo = Repository()
187 new_repo = Repository()
185 new_repo.enable_statistics = False
188 new_repo.enable_statistics = False
186 for k, v in form_data.items():
189 for k, v in form_data.items():
187 if k == 'repo_name':
190 if k == 'repo_name':
188 if fork:
191 if fork:
189 v = repo_name
192 v = repo_name
190 else:
193 else:
191 v = repo_name_full
194 v = repo_name_full
192 if k == 'repo_group':
195 if k == 'repo_group':
193 k = 'group_id'
196 k = 'group_id'
194
197
195 if k == 'description':
198 if k == 'description':
196 v = v or repo_name
199 v = v or repo_name
197
200
198 setattr(new_repo, k, v)
201 setattr(new_repo, k, v)
199
202
200 if fork:
203 if fork:
201 parent_repo = self.sa.query(Repository)\
204 parent_repo = self.sa.query(Repository)\
202 .filter(Repository.repo_name == org_full_name).one()
205 .filter(Repository.repo_name == org_full_name).one()
203 new_repo.fork = parent_repo
206 new_repo.fork = parent_repo
204
207
205 new_repo.user_id = cur_user.user_id
208 new_repo.user_id = cur_user.user_id
206 self.sa.add(new_repo)
209 self.sa.add(new_repo)
207
210
208 #create default permission
211 #create default permission
209 repo_to_perm = RepoToPerm()
212 repo_to_perm = RepoToPerm()
210 default = 'repository.read'
213 default = 'repository.read'
211 for p in User.get_by_username('default').user_perms:
214 for p in User.get_by_username('default').user_perms:
212 if p.permission.permission_name.startswith('repository.'):
215 if p.permission.permission_name.startswith('repository.'):
213 default = p.permission.permission_name
216 default = p.permission.permission_name
214 break
217 break
215
218
216 default_perm = 'repository.none' if form_data['private'] else default
219 default_perm = 'repository.none' if form_data['private'] else default
217
220
218 repo_to_perm.permission_id = self.sa.query(Permission)\
221 repo_to_perm.permission_id = self.sa.query(Permission)\
219 .filter(Permission.permission_name == default_perm)\
222 .filter(Permission.permission_name == default_perm)\
220 .one().permission_id
223 .one().permission_id
221
224
222 repo_to_perm.repository = new_repo
225 repo_to_perm.repository = new_repo
223 repo_to_perm.user_id = User.get_by_username('default').user_id
226 repo_to_perm.user_id = User.get_by_username('default').user_id
224
227
225 self.sa.add(repo_to_perm)
228 self.sa.add(repo_to_perm)
226
229
227 if not just_db:
230 if not just_db:
228 self.__create_repo(repo_name, form_data['repo_type'],
231 self.__create_repo(repo_name, form_data['repo_type'],
229 form_data['repo_group'],
232 form_data['repo_group'],
230 form_data['clone_uri'])
233 form_data['clone_uri'])
231
234
232 self.sa.commit()
235 self.sa.commit()
233
236
234 #now automatically start following this repository as owner
237 #now automatically start following this repository as owner
235 from rhodecode.model.scm import ScmModel
238 from rhodecode.model.scm import ScmModel
236 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
239 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
237 cur_user.user_id)
240 cur_user.user_id)
238
241 return new_repo
239 except:
242 except:
240 log.error(traceback.format_exc())
243 log.error(traceback.format_exc())
241 self.sa.rollback()
244 self.sa.rollback()
242 raise
245 raise
243
246
244 def create_fork(self, form_data, cur_user):
247 def create_fork(self, form_data, cur_user):
245 from rhodecode.lib.celerylib import tasks, run_task
248 from rhodecode.lib.celerylib import tasks, run_task
246 run_task(tasks.create_repo_fork, form_data, cur_user)
249 run_task(tasks.create_repo_fork, form_data, cur_user)
247
250
248 def delete(self, repo):
251 def delete(self, repo):
249 try:
252 try:
250 self.sa.delete(repo)
253 self.sa.delete(repo)
251 self.__delete_repo(repo)
254 self.__delete_repo(repo)
252 self.sa.commit()
255 self.sa.commit()
253 except:
256 except:
254 log.error(traceback.format_exc())
257 log.error(traceback.format_exc())
255 self.sa.rollback()
258 self.sa.rollback()
256 raise
259 raise
257
260
258 def delete_perm_user(self, form_data, repo_name):
261 def delete_perm_user(self, form_data, repo_name):
259 try:
262 try:
260 self.sa.query(RepoToPerm)\
263 self.sa.query(RepoToPerm)\
261 .filter(RepoToPerm.repository \
264 .filter(RepoToPerm.repository \
262 == self.get_by_repo_name(repo_name))\
265 == self.get_by_repo_name(repo_name))\
263 .filter(RepoToPerm.user_id == form_data['user_id']).delete()
266 .filter(RepoToPerm.user_id == form_data['user_id']).delete()
264 self.sa.commit()
267 self.sa.commit()
265 except:
268 except:
266 log.error(traceback.format_exc())
269 log.error(traceback.format_exc())
267 self.sa.rollback()
270 self.sa.rollback()
268 raise
271 raise
269
272
270 def delete_perm_users_group(self, form_data, repo_name):
273 def delete_perm_users_group(self, form_data, repo_name):
271 try:
274 try:
272 self.sa.query(UsersGroupRepoToPerm)\
275 self.sa.query(UsersGroupRepoToPerm)\
273 .filter(UsersGroupRepoToPerm.repository \
276 .filter(UsersGroupRepoToPerm.repository \
274 == self.get_by_repo_name(repo_name))\
277 == self.get_by_repo_name(repo_name))\
275 .filter(UsersGroupRepoToPerm.users_group_id \
278 .filter(UsersGroupRepoToPerm.users_group_id \
276 == form_data['users_group_id']).delete()
279 == form_data['users_group_id']).delete()
277 self.sa.commit()
280 self.sa.commit()
278 except:
281 except:
279 log.error(traceback.format_exc())
282 log.error(traceback.format_exc())
280 self.sa.rollback()
283 self.sa.rollback()
281 raise
284 raise
282
285
283 def delete_stats(self, repo_name):
286 def delete_stats(self, repo_name):
284 try:
287 try:
285 self.sa.query(Statistics)\
288 self.sa.query(Statistics)\
286 .filter(Statistics.repository == \
289 .filter(Statistics.repository == \
287 self.get_by_repo_name(repo_name)).delete()
290 self.get_by_repo_name(repo_name)).delete()
288 self.sa.commit()
291 self.sa.commit()
289 except:
292 except:
290 log.error(traceback.format_exc())
293 log.error(traceback.format_exc())
291 self.sa.rollback()
294 self.sa.rollback()
292 raise
295 raise
293
296
294 def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
297 def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
295 """
298 """
296 makes repository on filesystem. It's group aware means it'll create
299 makes repository on filesystem. It's group aware means it'll create
297 a repository within a group, and alter the paths accordingly of
300 a repository within a group, and alter the paths accordingly of
298 group location
301 group location
299
302
300 :param repo_name:
303 :param repo_name:
301 :param alias:
304 :param alias:
302 :param parent_id:
305 :param parent_id:
303 :param clone_uri:
306 :param clone_uri:
304 """
307 """
305 from rhodecode.lib.utils import is_valid_repo
308 from rhodecode.lib.utils import is_valid_repo
306
309
307 if new_parent_id:
310 if new_parent_id:
308 paths = Group.get(new_parent_id).full_path.split(Group.url_sep())
311 paths = Group.get(new_parent_id).full_path.split(Group.url_sep())
309 new_parent_path = os.sep.join(paths)
312 new_parent_path = os.sep.join(paths)
310 else:
313 else:
311 new_parent_path = ''
314 new_parent_path = ''
312
315
313 repo_path = os.path.join(*map(lambda x:safe_str(x),
316 repo_path = os.path.join(*map(lambda x:safe_str(x),
314 [self.repos_path, new_parent_path, repo_name]))
317 [self.repos_path, new_parent_path, repo_name]))
315
318
316 if is_valid_repo(repo_path, self.repos_path) is False:
319 if is_valid_repo(repo_path, self.repos_path) is False:
317 log.info('creating repo %s in %s @ %s', repo_name, repo_path,
320 log.info('creating repo %s in %s @ %s', repo_name, repo_path,
318 clone_uri)
321 clone_uri)
319 backend = get_backend(alias)
322 backend = get_backend(alias)
320
323
321 backend(repo_path, create=True, src_url=clone_uri)
324 backend(repo_path, create=True, src_url=clone_uri)
322
325
323
326
324 def __rename_repo(self, old, new):
327 def __rename_repo(self, old, new):
325 """
328 """
326 renames repository on filesystem
329 renames repository on filesystem
327
330
328 :param old: old name
331 :param old: old name
329 :param new: new name
332 :param new: new name
330 """
333 """
331 log.info('renaming repo from %s to %s', old, new)
334 log.info('renaming repo from %s to %s', old, new)
332
335
333 old_path = os.path.join(self.repos_path, old)
336 old_path = os.path.join(self.repos_path, old)
334 new_path = os.path.join(self.repos_path, new)
337 new_path = os.path.join(self.repos_path, new)
335 if os.path.isdir(new_path):
338 if os.path.isdir(new_path):
336 raise Exception('Was trying to rename to already existing dir %s' \
339 raise Exception('Was trying to rename to already existing dir %s' \
337 % new_path)
340 % new_path)
338 shutil.move(old_path, new_path)
341 shutil.move(old_path, new_path)
339
342
340 def __delete_repo(self, repo):
343 def __delete_repo(self, repo):
341 """
344 """
342 removes repo from filesystem, the removal is acctually made by
345 removes repo from filesystem, the removal is acctually made by
343 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
346 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
344 repository is no longer valid for rhodecode, can be undeleted later on
347 repository is no longer valid for rhodecode, can be undeleted later on
345 by reverting the renames on this repository
348 by reverting the renames on this repository
346
349
347 :param repo: repo object
350 :param repo: repo object
348 """
351 """
349 rm_path = os.path.join(self.repos_path, repo.repo_name)
352 rm_path = os.path.join(self.repos_path, repo.repo_name)
350 log.info("Removing %s", rm_path)
353 log.info("Removing %s", rm_path)
351 #disable hg/git
354 #disable hg/git
352 alias = repo.repo_type
355 alias = repo.repo_type
353 shutil.move(os.path.join(rm_path, '.%s' % alias),
356 shutil.move(os.path.join(rm_path, '.%s' % alias),
354 os.path.join(rm_path, 'rm__.%s' % alias))
357 os.path.join(rm_path, 'rm__.%s' % alias))
355 #disable repo
358 #disable repo
356 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
359 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
357 % (datetime.today()\
360 % (datetime.today()\
358 .strftime('%Y%m%d_%H%M%S_%f'),
361 .strftime('%Y%m%d_%H%M%S_%f'),
359 repo.repo_name)))
362 repo.repo_name)))
@@ -1,156 +1,162
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.user_group
3 rhodecode.model.user_group
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 users groups model for RhodeCode
6 users groups model for RhodeCode
7
7
8 :created_on: Jan 25, 2011
8 :created_on: Jan 25, 2011
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 modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import shutil
29 import shutil
30
30
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32
32
33 from vcs.utils.lazy import LazyProperty
33 from vcs.utils.lazy import LazyProperty
34
34
35 from rhodecode.model import BaseModel
35 from rhodecode.model import BaseModel
36 from rhodecode.model.caching_query import FromCache
36 from rhodecode.model.caching_query import FromCache
37 from rhodecode.model.db import Group, RhodeCodeUi
37 from rhodecode.model.db import Group, RhodeCodeUi
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 class ReposGroupModel(BaseModel):
42 class ReposGroupModel(BaseModel):
43
43
44 @LazyProperty
44 @LazyProperty
45 def repos_path(self):
45 def repos_path(self):
46 """
46 """
47 Get's the repositories root path from database
47 Get's the repositories root path from database
48 """
48 """
49
49
50 q = RhodeCodeUi.get_by_key('/').one()
50 q = RhodeCodeUi.get_by_key('/').one()
51 return q.ui_value
51 return q.ui_value
52
52
53 def __create_group(self, group_name):
53 def __create_group(self, group_name):
54 """
54 """
55 makes repositories group on filesystem
55 makes repositories group on filesystem
56
56
57 :param repo_name:
57 :param repo_name:
58 :param parent_id:
58 :param parent_id:
59 """
59 """
60
60
61 create_path = os.path.join(self.repos_path, group_name)
61 create_path = os.path.join(self.repos_path, group_name)
62 log.debug('creating new group in %s', create_path)
62 log.debug('creating new group in %s', create_path)
63
63
64 if os.path.isdir(create_path):
64 if os.path.isdir(create_path):
65 raise Exception('That directory already exists !')
65 raise Exception('That directory already exists !')
66
66
67 os.makedirs(create_path)
67 os.makedirs(create_path)
68
68
69 def __rename_group(self, old, new):
69 def __rename_group(self, old, new):
70 """
70 """
71 Renames a group on filesystem
71 Renames a group on filesystem
72
72
73 :param group_name:
73 :param group_name:
74 """
74 """
75
75
76 if old == new:
76 if old == new:
77 log.debug('skipping group rename')
77 log.debug('skipping group rename')
78 return
78 return
79
79
80 log.debug('renaming repos group from %s to %s', old, new)
80 log.debug('renaming repos group from %s to %s', old, new)
81
81
82
82
83 old_path = os.path.join(self.repos_path, old)
83 old_path = os.path.join(self.repos_path, old)
84 new_path = os.path.join(self.repos_path, new)
84 new_path = os.path.join(self.repos_path, new)
85
85
86 log.debug('renaming repos paths from %s to %s', old_path, new_path)
86 log.debug('renaming repos paths from %s to %s', old_path, new_path)
87
87
88 if os.path.isdir(new_path):
88 if os.path.isdir(new_path):
89 raise Exception('Was trying to rename to already '
89 raise Exception('Was trying to rename to already '
90 'existing dir %s' % new_path)
90 'existing dir %s' % new_path)
91 shutil.move(old_path, new_path)
91 shutil.move(old_path, new_path)
92
92
93 def __delete_group(self, group):
93 def __delete_group(self, group):
94 """
94 """
95 Deletes a group from a filesystem
95 Deletes a group from a filesystem
96
96
97 :param group: instance of group from database
97 :param group: instance of group from database
98 """
98 """
99 paths = group.full_path.split(Group.url_sep())
99 paths = group.full_path.split(Group.url_sep())
100 paths = os.sep.join(paths)
100 paths = os.sep.join(paths)
101
101
102 rm_path = os.path.join(self.repos_path, paths)
102 rm_path = os.path.join(self.repos_path, paths)
103 os.rmdir(rm_path)
103 os.rmdir(rm_path)
104
104
105 def create(self, form_data):
105 def create(self, form_data):
106 try:
106 try:
107 new_repos_group = Group()
107 new_repos_group = Group()
108 new_repos_group.group_description = form_data['group_description']
108 new_repos_group.group_description = form_data['group_description']
109 new_repos_group.parent_group = Group.get(form_data['group_parent_id'])
109 new_repos_group.parent_group = Group.get(form_data['group_parent_id'])
110 new_repos_group.group_name = new_repos_group.get_new_name(form_data['group_name'])
110 new_repos_group.group_name = new_repos_group.get_new_name(form_data['group_name'])
111
111
112 self.sa.add(new_repos_group)
112 self.sa.add(new_repos_group)
113
113
114 self.__create_group(new_repos_group.group_name)
114 self.__create_group(new_repos_group.group_name)
115
115
116 self.sa.commit()
116 self.sa.commit()
117 return new_repos_group
117 return new_repos_group
118 except:
118 except:
119 log.error(traceback.format_exc())
119 log.error(traceback.format_exc())
120 self.sa.rollback()
120 self.sa.rollback()
121 raise
121 raise
122
122
123 def update(self, repos_group_id, form_data):
123 def update(self, repos_group_id, form_data):
124
124
125 try:
125 try:
126 repos_group = Group.get(repos_group_id)
126 repos_group = Group.get(repos_group_id)
127 old_path = repos_group.full_path
127 old_path = repos_group.full_path
128
128
129 #change properties
129 #change properties
130 repos_group.group_description = form_data['group_description']
130 repos_group.group_description = form_data['group_description']
131 repos_group.parent_group = Group.get(form_data['group_parent_id'])
131 repos_group.parent_group = Group.get(form_data['group_parent_id'])
132 repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
132 repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
133
133
134 new_path = repos_group.full_path
134 new_path = repos_group.full_path
135
135
136 self.sa.add(repos_group)
136 self.sa.add(repos_group)
137
137
138 self.__rename_group(old_path, new_path)
138 self.__rename_group(old_path, new_path)
139
139
140 # we need to get all repositories from this new group and
141 # rename them accordingly to new group path
142 for r in repos_group.repositories:
143 r.repo_name = r.get_new_name(r.just_name)
144 self.sa.add(r)
145
140 self.sa.commit()
146 self.sa.commit()
141 return repos_group
147 return repos_group
142 except:
148 except:
143 log.error(traceback.format_exc())
149 log.error(traceback.format_exc())
144 self.sa.rollback()
150 self.sa.rollback()
145 raise
151 raise
146
152
147 def delete(self, users_group_id):
153 def delete(self, users_group_id):
148 try:
154 try:
149 users_group = Group.get(users_group_id)
155 users_group = Group.get(users_group_id)
150 self.sa.delete(users_group)
156 self.sa.delete(users_group)
151 self.__delete_group(users_group)
157 self.__delete_group(users_group)
152 self.sa.commit()
158 self.sa.commit()
153 except:
159 except:
154 log.error(traceback.format_exc())
160 log.error(traceback.format_exc())
155 self.sa.rollback()
161 self.sa.rollback()
156 raise
162 raise
@@ -1,261 +1,261
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 from rhodecode.tests import *
2 from rhodecode.tests import *
3 from rhodecode.model.db import User
3 from rhodecode.model.db import User
4 from rhodecode.lib import generate_api_key
4 from rhodecode.lib import generate_api_key
5 from rhodecode.lib.auth import check_password
5 from rhodecode.lib.auth import check_password
6
6
7
7
8 class TestLoginController(TestController):
8 class TestLoginController(TestController):
9
9
10 def test_index(self):
10 def test_index(self):
11 response = self.app.get(url(controller='login', action='index'))
11 response = self.app.get(url(controller='login', action='index'))
12 self.assertEqual(response.status, '200 OK')
12 self.assertEqual(response.status, '200 OK')
13 # Test response...
13 # Test response...
14
14
15 def test_login_admin_ok(self):
15 def test_login_admin_ok(self):
16 response = self.app.post(url(controller='login', action='index'),
16 response = self.app.post(url(controller='login', action='index'),
17 {'username':'test_admin',
17 {'username':'test_admin',
18 'password':'test12'})
18 'password':'test12'})
19 self.assertEqual(response.status, '302 Found')
19 self.assertEqual(response.status, '302 Found')
20 self.assertEqual(response.session['rhodecode_user'].username ,
20 self.assertEqual(response.session['rhodecode_user'].username ,
21 'test_admin')
21 'test_admin')
22 response = response.follow()
22 response = response.follow()
23 self.assertTrue('%s repository' % HG_REPO in response.body)
23 self.assertTrue('%s repository' % HG_REPO in response.body)
24
24
25 def test_login_regular_ok(self):
25 def test_login_regular_ok(self):
26 response = self.app.post(url(controller='login', action='index'),
26 response = self.app.post(url(controller='login', action='index'),
27 {'username':'test_regular',
27 {'username':'test_regular',
28 'password':'test12'})
28 'password':'test12'})
29
29
30 self.assertEqual(response.status, '302 Found')
30 self.assertEqual(response.status, '302 Found')
31 self.assertEqual(response.session['rhodecode_user'].username ,
31 self.assertEqual(response.session['rhodecode_user'].username ,
32 'test_regular')
32 'test_regular')
33 response = response.follow()
33 response = response.follow()
34 self.assertTrue('%s repository' % HG_REPO in response.body)
34 self.assertTrue('%s repository' % HG_REPO in response.body)
35 self.assertTrue('<a title="Admin" href="/_admin">' not in response.body)
35 self.assertTrue('<a title="Admin" href="/_admin">' not in response.body)
36
36
37 def test_login_ok_came_from(self):
37 def test_login_ok_came_from(self):
38 test_came_from = '/_admin/users'
38 test_came_from = '/_admin/users'
39 response = self.app.post(url(controller='login', action='index',
39 response = self.app.post(url(controller='login', action='index',
40 came_from=test_came_from),
40 came_from=test_came_from),
41 {'username':'test_admin',
41 {'username':'test_admin',
42 'password':'test12'})
42 'password':'test12'})
43 self.assertEqual(response.status, '302 Found')
43 self.assertEqual(response.status, '302 Found')
44 response = response.follow()
44 response = response.follow()
45
45
46 self.assertEqual(response.status, '200 OK')
46 self.assertEqual(response.status, '200 OK')
47 self.assertTrue('Users administration' in response.body)
47 self.assertTrue('Users administration' in response.body)
48
48
49
49
50 def test_login_short_password(self):
50 def test_login_short_password(self):
51 response = self.app.post(url(controller='login', action='index'),
51 response = self.app.post(url(controller='login', action='index'),
52 {'username':'test_admin',
52 {'username':'test_admin',
53 'password':'as'})
53 'password':'as'})
54 self.assertEqual(response.status, '200 OK')
54 self.assertEqual(response.status, '200 OK')
55
55
56 self.assertTrue('Enter 3 characters or more' in response.body)
56 self.assertTrue('Enter 3 characters or more' in response.body)
57
57
58 def test_login_wrong_username_password(self):
58 def test_login_wrong_username_password(self):
59 response = self.app.post(url(controller='login', action='index'),
59 response = self.app.post(url(controller='login', action='index'),
60 {'username':'error',
60 {'username':'error',
61 'password':'test12'})
61 'password':'test12'})
62 self.assertEqual(response.status , '200 OK')
62 self.assertEqual(response.status , '200 OK')
63
63
64 self.assertTrue('invalid user name' in response.body)
64 self.assertTrue('invalid user name' in response.body)
65 self.assertTrue('invalid password' in response.body)
65 self.assertTrue('invalid password' in response.body)
66
66
67 #==========================================================================
67 #==========================================================================
68 # REGISTRATIONS
68 # REGISTRATIONS
69 #==========================================================================
69 #==========================================================================
70 def test_register(self):
70 def test_register(self):
71 response = self.app.get(url(controller='login', action='register'))
71 response = self.app.get(url(controller='login', action='register'))
72 self.assertTrue('Sign Up to RhodeCode' in response.body)
72 self.assertTrue('Sign Up to RhodeCode' in response.body)
73
73
74 def test_register_err_same_username(self):
74 def test_register_err_same_username(self):
75 response = self.app.post(url(controller='login', action='register'),
75 response = self.app.post(url(controller='login', action='register'),
76 {'username':'test_admin',
76 {'username':'test_admin',
77 'password':'test12',
77 'password':'test12',
78 'password_confirmation':'test12',
78 'password_confirmation':'test12',
79 'email':'goodmail@domain.com',
79 'email':'goodmail@domain.com',
80 'name':'test',
80 'name':'test',
81 'lastname':'test'})
81 'lastname':'test'})
82
82
83 self.assertEqual(response.status , '200 OK')
83 self.assertEqual(response.status , '200 OK')
84 self.assertTrue('This username already exists' in response.body)
84 self.assertTrue('This username already exists' in response.body)
85
85
86 def test_register_err_same_email(self):
86 def test_register_err_same_email(self):
87 response = self.app.post(url(controller='login', action='register'),
87 response = self.app.post(url(controller='login', action='register'),
88 {'username':'test_admin_0',
88 {'username':'test_admin_0',
89 'password':'test12',
89 'password':'test12',
90 'password_confirmation':'test12',
90 'password_confirmation':'test12',
91 'email':'test_admin@mail.com',
91 'email':'test_admin@mail.com',
92 'name':'test',
92 'name':'test',
93 'lastname':'test'})
93 'lastname':'test'})
94
94
95 self.assertEqual(response.status , '200 OK')
95 self.assertEqual(response.status , '200 OK')
96 assert 'This e-mail address is already taken' in response.body
96 assert 'This e-mail address is already taken' in response.body
97
97
98 def test_register_err_same_email_case_sensitive(self):
98 def test_register_err_same_email_case_sensitive(self):
99 response = self.app.post(url(controller='login', action='register'),
99 response = self.app.post(url(controller='login', action='register'),
100 {'username':'test_admin_1',
100 {'username':'test_admin_1',
101 'password':'test12',
101 'password':'test12',
102 'password_confirmation':'test12',
102 'password_confirmation':'test12',
103 'email':'TesT_Admin@mail.COM',
103 'email':'TesT_Admin@mail.COM',
104 'name':'test',
104 'name':'test',
105 'lastname':'test'})
105 'lastname':'test'})
106 self.assertEqual(response.status , '200 OK')
106 self.assertEqual(response.status , '200 OK')
107 assert 'This e-mail address is already taken' in response.body
107 assert 'This e-mail address is already taken' in response.body
108
108
109 def test_register_err_wrong_data(self):
109 def test_register_err_wrong_data(self):
110 response = self.app.post(url(controller='login', action='register'),
110 response = self.app.post(url(controller='login', action='register'),
111 {'username':'xs',
111 {'username':'xs',
112 'password':'test',
112 'password':'test',
113 'password_confirmation':'test',
113 'password_confirmation':'test',
114 'email':'goodmailm',
114 'email':'goodmailm',
115 'name':'test',
115 'name':'test',
116 'lastname':'test'})
116 'lastname':'test'})
117 self.assertEqual(response.status , '200 OK')
117 self.assertEqual(response.status , '200 OK')
118 assert 'An email address must contain a single @' in response.body
118 assert 'An email address must contain a single @' in response.body
119 assert 'Enter a value 6 characters long or more' in response.body
119 assert 'Enter a value 6 characters long or more' in response.body
120
120
121
121
122 def test_register_err_username(self):
122 def test_register_err_username(self):
123 response = self.app.post(url(controller='login', action='register'),
123 response = self.app.post(url(controller='login', action='register'),
124 {'username':'error user',
124 {'username':'error user',
125 'password':'test12',
125 'password':'test12',
126 'password_confirmation':'test12',
126 'password_confirmation':'test12',
127 'email':'goodmailm',
127 'email':'goodmailm',
128 'name':'test',
128 'name':'test',
129 'lastname':'test'})
129 'lastname':'test'})
130
130
131 self.assertEqual(response.status , '200 OK')
131 self.assertEqual(response.status , '200 OK')
132 assert 'An email address must contain a single @' in response.body
132 assert 'An email address must contain a single @' in response.body
133 assert ('Username may only contain '
133 assert ('Username may only contain '
134 'alphanumeric characters underscores, '
134 'alphanumeric characters underscores, '
135 'periods or dashes and must begin with '
135 'periods or dashes and must begin with '
136 'alphanumeric character') in response.body
136 'alphanumeric character') in response.body
137
137
138 def test_register_err_case_sensitive(self):
138 def test_register_err_case_sensitive(self):
139 response = self.app.post(url(controller='login', action='register'),
139 response = self.app.post(url(controller='login', action='register'),
140 {'username':'Test_Admin',
140 {'username':'Test_Admin',
141 'password':'test12',
141 'password':'test12',
142 'password_confirmation':'test12',
142 'password_confirmation':'test12',
143 'email':'goodmailm',
143 'email':'goodmailm',
144 'name':'test',
144 'name':'test',
145 'lastname':'test'})
145 'lastname':'test'})
146
146
147 self.assertEqual(response.status , '200 OK')
147 self.assertEqual(response.status , '200 OK')
148 assert 'An email address must contain a single @' in response.body
148 self.assertTrue('An email address must contain a single @' in response.body)
149 assert 'This username already exists' in response.body
149 self.assertTrue('This username already exists' in response.body)
150
150
151
151
152
152
153 def test_register_special_chars(self):
153 def test_register_special_chars(self):
154 response = self.app.post(url(controller='login', action='register'),
154 response = self.app.post(url(controller='login', action='register'),
155 {'username':'xxxaxn',
155 {'username':'xxxaxn',
156 'password':'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
156 'password':'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
157 'password_confirmation':'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
157 'password_confirmation':'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
158 'email':'goodmailm@test.plx',
158 'email':'goodmailm@test.plx',
159 'name':'test',
159 'name':'test',
160 'lastname':'test'})
160 'lastname':'test'})
161
161
162 self.assertEqual(response.status , '200 OK')
162 self.assertEqual(response.status , '200 OK')
163 assert 'Invalid characters in password' in response.body
163 self.assertTrue('Invalid characters in password' in response.body)
164
164
165
165
166 def test_register_password_mismatch(self):
166 def test_register_password_mismatch(self):
167 response = self.app.post(url(controller='login', action='register'),
167 response = self.app.post(url(controller='login', action='register'),
168 {'username':'xs',
168 {'username':'xs',
169 'password':'123qwe',
169 'password':'123qwe',
170 'password_confirmation':'qwe123',
170 'password_confirmation':'qwe123',
171 'email':'goodmailm@test.plxa',
171 'email':'goodmailm@test.plxa',
172 'name':'test',
172 'name':'test',
173 'lastname':'test'})
173 'lastname':'test'})
174
174
175 self.assertEqual(response.status , '200 OK')
175 self.assertEqual(response.status , '200 OK')
176 assert 'Passwords do not match' in response.body
176 assert 'Passwords do not match' in response.body
177
177
178 def test_register_ok(self):
178 def test_register_ok(self):
179 username = 'test_regular4'
179 username = 'test_regular4'
180 password = 'qweqwe'
180 password = 'qweqwe'
181 email = 'marcin@test.com'
181 email = 'marcin@test.com'
182 name = 'testname'
182 name = 'testname'
183 lastname = 'testlastname'
183 lastname = 'testlastname'
184
184
185 response = self.app.post(url(controller='login', action='register'),
185 response = self.app.post(url(controller='login', action='register'),
186 {'username':username,
186 {'username':username,
187 'password':password,
187 'password':password,
188 'password_confirmation':password,
188 'password_confirmation':password,
189 'email':email,
189 'email':email,
190 'name':name,
190 'name':name,
191 'lastname':lastname})
191 'lastname':lastname})
192 self.assertEqual(response.status , '302 Found')
192 self.assertEqual(response.status , '302 Found')
193 assert 'You have successfully registered into rhodecode' in response.session['flash'][0], 'No flash message about user registration'
193 assert 'You have successfully registered into rhodecode' in response.session['flash'][0], 'No flash message about user registration'
194
194
195 ret = self.sa.query(User).filter(User.username == 'test_regular4').one()
195 ret = self.sa.query(User).filter(User.username == 'test_regular4').one()
196 assert ret.username == username , 'field mismatch %s %s' % (ret.username, username)
196 assert ret.username == username , 'field mismatch %s %s' % (ret.username, username)
197 assert check_password(password, ret.password) == True , 'password mismatch'
197 assert check_password(password, ret.password) == True , 'password mismatch'
198 assert ret.email == email , 'field mismatch %s %s' % (ret.email, email)
198 assert ret.email == email , 'field mismatch %s %s' % (ret.email, email)
199 assert ret.name == name , 'field mismatch %s %s' % (ret.name, name)
199 assert ret.name == name , 'field mismatch %s %s' % (ret.name, name)
200 assert ret.lastname == lastname , 'field mismatch %s %s' % (ret.lastname, lastname)
200 assert ret.lastname == lastname , 'field mismatch %s %s' % (ret.lastname, lastname)
201
201
202
202
203 def test_forgot_password_wrong_mail(self):
203 def test_forgot_password_wrong_mail(self):
204 response = self.app.post(url(controller='login', action='password_reset'),
204 response = self.app.post(url(controller='login', action='password_reset'),
205 {'email':'marcin@wrongmail.org', })
205 {'email':'marcin@wrongmail.org', })
206
206
207 assert "This e-mail address doesn't exist" in response.body, 'Missing error message about wrong email'
207 assert "This e-mail address doesn't exist" in response.body, 'Missing error message about wrong email'
208
208
209 def test_forgot_password(self):
209 def test_forgot_password(self):
210 response = self.app.get(url(controller='login',
210 response = self.app.get(url(controller='login',
211 action='password_reset'))
211 action='password_reset'))
212 self.assertEqual(response.status , '200 OK')
212 self.assertEqual(response.status , '200 OK')
213
213
214 username = 'test_password_reset_1'
214 username = 'test_password_reset_1'
215 password = 'qweqwe'
215 password = 'qweqwe'
216 email = 'marcin@python-works.com'
216 email = 'marcin@python-works.com'
217 name = 'passwd'
217 name = 'passwd'
218 lastname = 'reset'
218 lastname = 'reset'
219
219
220 new = User()
220 new = User()
221 new.username = username
221 new.username = username
222 new.password = password
222 new.password = password
223 new.email = email
223 new.email = email
224 new.name = name
224 new.name = name
225 new.lastname = lastname
225 new.lastname = lastname
226 new.api_key = generate_api_key(username)
226 new.api_key = generate_api_key(username)
227 self.sa.add(new)
227 self.sa.add(new)
228 self.sa.commit()
228 self.sa.commit()
229
229
230 response = self.app.post(url(controller='login',
230 response = self.app.post(url(controller='login',
231 action='password_reset'),
231 action='password_reset'),
232 {'email':email, })
232 {'email':email, })
233
233
234 self.checkSessionFlash(response, 'Your password reset link was sent')
234 self.checkSessionFlash(response, 'Your password reset link was sent')
235
235
236 response = response.follow()
236 response = response.follow()
237
237
238 # BAD KEY
238 # BAD KEY
239
239
240 key = "bad"
240 key = "bad"
241 response = self.app.get(url(controller='login',
241 response = self.app.get(url(controller='login',
242 action='password_reset_confirmation',
242 action='password_reset_confirmation',
243 key=key))
243 key=key))
244 self.assertEqual(response.status, '302 Found')
244 self.assertEqual(response.status, '302 Found')
245 self.assertTrue(response.location.endswith(url('reset_password')))
245 self.assertTrue(response.location.endswith(url('reset_password')))
246
246
247 # GOOD KEY
247 # GOOD KEY
248
248
249 key = User.get_by_username(username).api_key
249 key = User.get_by_username(username).api_key
250
250
251 response = self.app.get(url(controller='login',
251 response = self.app.get(url(controller='login',
252 action='password_reset_confirmation',
252 action='password_reset_confirmation',
253 key=key))
253 key=key))
254 self.assertEqual(response.status, '302 Found')
254 self.assertEqual(response.status, '302 Found')
255 self.assertTrue(response.location.endswith(url('login_home')))
255 self.assertTrue(response.location.endswith(url('login_home')))
256
256
257 self.checkSessionFlash(response,
257 self.checkSessionFlash(response,
258 ('Your password reset was successful, '
258 ('Your password reset was successful, '
259 'new password has been sent to your email'))
259 'new password has been sent to your email'))
260
260
261 response = response.follow()
261 response = response.follow()
@@ -1,115 +1,153
1 import os
1 import os
2 import unittest
2 import unittest
3 from rhodecode.tests import *
3 from rhodecode.tests import *
4
4
5 from rhodecode.model.repos_group import ReposGroupModel
5 from rhodecode.model.repos_group import ReposGroupModel
6 from rhodecode.model.db import Group
6 from rhodecode.model.repo import RepoModel
7 from rhodecode.model.db import Group, User
7 from sqlalchemy.exc import IntegrityError
8 from sqlalchemy.exc import IntegrityError
8
9
9 class TestReposGroups(unittest.TestCase):
10 class TestReposGroups(unittest.TestCase):
10
11
11 def setUp(self):
12 def setUp(self):
12 self.g1 = self.__make_group('test1', skip_if_exists=True)
13 self.g1 = self.__make_group('test1', skip_if_exists=True)
13 self.g2 = self.__make_group('test2', skip_if_exists=True)
14 self.g2 = self.__make_group('test2', skip_if_exists=True)
14 self.g3 = self.__make_group('test3', skip_if_exists=True)
15 self.g3 = self.__make_group('test3', skip_if_exists=True)
15
16
16 def tearDown(self):
17 def tearDown(self):
17 print 'out'
18 print 'out'
18
19
19 def __check_path(self, *path):
20 def __check_path(self, *path):
20 path = [TESTS_TMP_PATH] + list(path)
21 path = [TESTS_TMP_PATH] + list(path)
21 path = os.path.join(*path)
22 path = os.path.join(*path)
22 return os.path.isdir(path)
23 return os.path.isdir(path)
23
24
24 def _check_folders(self):
25 def _check_folders(self):
25 print os.listdir(TESTS_TMP_PATH)
26 print os.listdir(TESTS_TMP_PATH)
26
27
27 def __make_group(self, path, desc='desc', parent_id=None,
28 def __make_group(self, path, desc='desc', parent_id=None,
28 skip_if_exists=False):
29 skip_if_exists=False):
29
30
30 gr = Group.get_by_group_name(path)
31 gr = Group.get_by_group_name(path)
31 if gr and skip_if_exists:
32 if gr and skip_if_exists:
32 return gr
33 return gr
33
34
34 form_data = dict(group_name=path,
35 form_data = dict(group_name=path,
35 group_description=desc,
36 group_description=desc,
36 group_parent_id=parent_id)
37 group_parent_id=parent_id)
37 gr = ReposGroupModel().create(form_data)
38 gr = ReposGroupModel().create(form_data)
38 return gr
39 return gr
39
40
40 def __delete_group(self, id_):
41 def __delete_group(self, id_):
41 ReposGroupModel().delete(id_)
42 ReposGroupModel().delete(id_)
42
43
43
44
44 def __update_group(self, id_, path, desc='desc', parent_id=None):
45 def __update_group(self, id_, path, desc='desc', parent_id=None):
45 form_data = dict(group_name=path,
46 form_data = dict(group_name=path,
46 group_description=desc,
47 group_description=desc,
47 group_parent_id=parent_id)
48 group_parent_id=parent_id)
48
49
49 gr = ReposGroupModel().update(id_, form_data)
50 gr = ReposGroupModel().update(id_, form_data)
50 return gr
51 return gr
51
52
52 def test_create_group(self):
53 def test_create_group(self):
53 g = self.__make_group('newGroup')
54 g = self.__make_group('newGroup')
54 self.assertEqual(g.full_path, 'newGroup')
55 self.assertEqual(g.full_path, 'newGroup')
55
56
56 self.assertTrue(self.__check_path('newGroup'))
57 self.assertTrue(self.__check_path('newGroup'))
57
58
58
59
59 def test_create_same_name_group(self):
60 def test_create_same_name_group(self):
60 self.assertRaises(IntegrityError, lambda:self.__make_group('newGroup'))
61 self.assertRaises(IntegrityError, lambda:self.__make_group('newGroup'))
61
62
62
63
63 def test_same_subgroup(self):
64 def test_same_subgroup(self):
64 sg1 = self.__make_group('sub1', parent_id=self.g1.group_id)
65 sg1 = self.__make_group('sub1', parent_id=self.g1.group_id)
65 self.assertEqual(sg1.parent_group, self.g1)
66 self.assertEqual(sg1.parent_group, self.g1)
66 self.assertEqual(sg1.full_path, 'test1/sub1')
67 self.assertEqual(sg1.full_path, 'test1/sub1')
67 self.assertTrue(self.__check_path('test1', 'sub1'))
68 self.assertTrue(self.__check_path('test1', 'sub1'))
68
69
69 ssg1 = self.__make_group('subsub1', parent_id=sg1.group_id)
70 ssg1 = self.__make_group('subsub1', parent_id=sg1.group_id)
70 self.assertEqual(ssg1.parent_group, sg1)
71 self.assertEqual(ssg1.parent_group, sg1)
71 self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1')
72 self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1')
72 self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1'))
73 self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1'))
73
74
74
75
75 def test_remove_group(self):
76 def test_remove_group(self):
76 sg1 = self.__make_group('deleteme')
77 sg1 = self.__make_group('deleteme')
77 self.__delete_group(sg1.group_id)
78 self.__delete_group(sg1.group_id)
78
79
79 self.assertEqual(Group.get(sg1.group_id), None)
80 self.assertEqual(Group.get(sg1.group_id), None)
80 self.assertFalse(self.__check_path('deteteme'))
81 self.assertFalse(self.__check_path('deteteme'))
81
82
82 sg1 = self.__make_group('deleteme', parent_id=self.g1.group_id)
83 sg1 = self.__make_group('deleteme', parent_id=self.g1.group_id)
83 self.__delete_group(sg1.group_id)
84 self.__delete_group(sg1.group_id)
84
85
85 self.assertEqual(Group.get(sg1.group_id), None)
86 self.assertEqual(Group.get(sg1.group_id), None)
86 self.assertFalse(self.__check_path('test1', 'deteteme'))
87 self.assertFalse(self.__check_path('test1', 'deteteme'))
87
88
88
89
89 def test_rename_single_group(self):
90 def test_rename_single_group(self):
90 sg1 = self.__make_group('initial')
91 sg1 = self.__make_group('initial')
91
92
92 new_sg1 = self.__update_group(sg1.group_id, 'after')
93 new_sg1 = self.__update_group(sg1.group_id, 'after')
93 self.assertTrue(self.__check_path('after'))
94 self.assertTrue(self.__check_path('after'))
94 self.assertEqual(Group.get_by_group_name('initial'), None)
95 self.assertEqual(Group.get_by_group_name('initial'), None)
95
96
96
97
97 def test_update_group_parent(self):
98 def test_update_group_parent(self):
98
99
99 sg1 = self.__make_group('initial', parent_id=self.g1.group_id)
100 sg1 = self.__make_group('initial', parent_id=self.g1.group_id)
100
101
101 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
102 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
102 self.assertTrue(self.__check_path('test1', 'after'))
103 self.assertTrue(self.__check_path('test1', 'after'))
103 self.assertEqual(Group.get_by_group_name('test1/initial'), None)
104 self.assertEqual(Group.get_by_group_name('test1/initial'), None)
104
105
105
106
106 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
107 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
107 self.assertTrue(self.__check_path('test3', 'after'))
108 self.assertTrue(self.__check_path('test3', 'after'))
108 self.assertEqual(Group.get_by_group_name('test3/initial'), None)
109 self.assertEqual(Group.get_by_group_name('test3/initial'), None)
109
110
110
111
111 new_sg1 = self.__update_group(sg1.group_id, 'hello')
112 new_sg1 = self.__update_group(sg1.group_id, 'hello')
112 self.assertTrue(self.__check_path('hello'))
113 self.assertTrue(self.__check_path('hello'))
113
114
114 self.assertEqual(Group.get_by_group_name('hello'), new_sg1)
115 self.assertEqual(Group.get_by_group_name('hello'), new_sg1)
115
116
117
118
119 def test_subgrouping_with_repo(self):
120
121 g1 = self.__make_group('g1')
122 g2 = self.__make_group('g2')
123
124 # create new repo
125 form_data = dict(repo_name='john',
126 repo_name_full='john',
127 fork_name=None,
128 description=None,
129 repo_group=None,
130 private=False,
131 repo_type='hg',
132 clone_uri=None)
133 cur_user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
134 r = RepoModel().create(form_data, cur_user)
135
136 self.assertEqual(r.repo_name, 'john')
137
138 # put repo into group
139 form_data = form_data
140 form_data['repo_group'] = g1.group_id
141 form_data['perms_new'] = []
142 form_data['perms_updates'] = []
143 RepoModel().update(r.repo_name, form_data)
144 self.assertEqual(r.repo_name, 'g1/john')
145
146
147 self.__update_group(g1.group_id, 'g1', parent_id=g2.group_id)
148 self.assertTrue(self.__check_path('g2', 'g1'))
149
150 # test repo
151 self.assertEqual(r.repo_name, os.path.join('g2', 'g1', r.just_name))
152
153
General Comments 0
You need to be logged in to leave comments. Login now