##// END OF EJS Templates
fixes #200, rewrote the whole caching mechanism to get rid of such problems. Now cached instances are attached...
marcink -
r1366:9c0f5d55 beta
parent child Browse files
Show More
@@ -1,434 +1,431 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.repos
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Admin controller for RhodeCode
7 7
8 8 :created_on: Apr 7, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29 from operator import itemgetter
30 30 from formencode import htmlfill
31 31
32 32 from paste.httpexceptions import HTTPInternalServerError
33 33 from pylons import request, response, session, tmpl_context as c, url
34 34 from pylons.controllers.util import abort, redirect
35 35 from pylons.i18n.translation import _
36 36
37 37 from rhodecode.lib import helpers as h
38 38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
39 39 HasPermissionAnyDecorator
40 40 from rhodecode.lib.base import BaseController, render
41 41 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
42 42 from rhodecode.lib.helpers import get_token
43 43 from rhodecode.model.db import User, Repository, UserFollowing, Group
44 44 from rhodecode.model.forms import RepoForm
45 45 from rhodecode.model.scm import ScmModel
46 46 from rhodecode.model.repo import RepoModel
47 47 from sqlalchemy.exc import IntegrityError
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 class ReposController(BaseController):
53 53 """
54 54 REST Controller styled on the Atom Publishing Protocol"""
55 55 # To properly map this controller, ensure your config/routing.py
56 56 # file has a resource setup:
57 57 # map.resource('repo', 'repos')
58 58
59 59 @LoginRequired()
60 60 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
61 61 def __before__(self):
62 62 c.admin_user = session.get('admin_user')
63 63 c.admin_username = session.get('admin_username')
64 64 super(ReposController, self).__before__()
65 65
66 66 def __load_defaults(self):
67 67 repo_model = RepoModel()
68 68
69 69 c.repo_groups = [('', '')]
70 70 parents_link = lambda k: h.literal('&raquo;'.join(
71 71 map(lambda k: k.group_name,
72 72 k.parents + [k])
73 73 )
74 74 )
75 75
76 76 c.repo_groups.extend([(x.group_id, parents_link(x)) for \
77 77 x in self.sa.query(Group).all()])
78 78 c.repo_groups = sorted(c.repo_groups,
79 79 key=lambda t: t[1].split('&raquo;')[0])
80 80 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
81 81 c.users_array = repo_model.get_users_js()
82 82 c.users_groups_array = repo_model.get_users_groups_js()
83 83
84 84 def __load_data(self, repo_name=None):
85 85 """
86 86 Load defaults settings for edit, and update
87 87
88 88 :param repo_name:
89 89 """
90 90 self.__load_defaults()
91 91
92 repo, dbrepo = ScmModel().get(repo_name, retval='repo')
93
94 repo_model = RepoModel()
95 c.repo_info = repo_model.get_by_repo_name(repo_name)
92 c.repo_info = db_repo = Repository.by_repo_name(repo_name)
93 repo = scm_repo = db_repo.scm_instance
96 94
97 95 if c.repo_info is None:
98 96 h.flash(_('%s repository is not mapped to db perhaps'
99 97 ' it was created or renamed from the filesystem'
100 98 ' please run the application again'
101 99 ' in order to rescan repositories') % repo_name,
102 100 category='error')
103 101
104 102 return redirect(url('repos'))
105 103
106 104 c.default_user_id = User.by_username('default').user_id
107 105 c.in_public_journal = self.sa.query(UserFollowing)\
108 106 .filter(UserFollowing.user_id == c.default_user_id)\
109 107 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
110 108
111 109 if c.repo_info.stats:
112 110 last_rev = c.repo_info.stats.stat_on_revision
113 111 else:
114 112 last_rev = 0
115 113 c.stats_revision = last_rev
116 114
117 115 c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
118 116
119 117 if last_rev == 0 or c.repo_last_rev == 0:
120 118 c.stats_percentage = 0
121 119 else:
122 120 c.stats_percentage = '%.2f' % ((float((last_rev)) /
123 121 c.repo_last_rev) * 100)
124 122
125 123 defaults = c.repo_info.get_dict()
126 124 group, repo_name = c.repo_info.groups_and_repo
127 125 defaults['repo_name'] = repo_name
128 126 defaults['repo_group'] = getattr(group[-1] if group else None,
129 127 'group_id', None)
130 128
131 129 #fill owner
132 130 if c.repo_info.user:
133 131 defaults.update({'user': c.repo_info.user.username})
134 132 else:
135 133 replacement_user = self.sa.query(User)\
136 134 .filter(User.admin == True).first().username
137 135 defaults.update({'user': replacement_user})
138 136
139 137 #fill repository users
140 138 for p in c.repo_info.repo_to_perm:
141 139 defaults.update({'u_perm_%s' % p.user.username:
142 140 p.permission.permission_name})
143 141
144 142 #fill repository groups
145 143 for p in c.repo_info.users_group_to_perm:
146 144 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
147 145 p.permission.permission_name})
148 146
149 147 return defaults
150 148
151 149 @HasPermissionAllDecorator('hg.admin')
152 150 def index(self, format='html'):
153 151 """GET /repos: All items in the collection"""
154 152 # url('repos')
155 153
156 all_repos = [r.repo_name for r in Repository.query().all()]
157
158 cached_repo_list = ScmModel().get_repos(all_repos)
159 c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort'))
154 c.repos_list = ScmModel().get_repos(Repository.query()
155 .order_by(Repository.repo_name)
156 .all(), sort_key='name_sort')
160 157 return render('admin/repos/repos.html')
161 158
162 159 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
163 160 def create(self):
164 161 """
165 162 POST /repos: Create a new item"""
166 163 # url('repos')
167 164 repo_model = RepoModel()
168 165 self.__load_defaults()
169 166 form_result = {}
170 167 try:
171 168 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
172 169 .to_python(dict(request.POST))
173 170 repo_model.create(form_result, self.rhodecode_user)
174 171 if form_result['clone_uri']:
175 172 h.flash(_('created repository %s from %s') \
176 173 % (form_result['repo_name'], form_result['clone_uri']),
177 174 category='success')
178 175 else:
179 176 h.flash(_('created repository %s') % form_result['repo_name'],
180 177 category='success')
181 178
182 179 if request.POST.get('user_created'):
183 180 #created by regular non admin user
184 181 action_logger(self.rhodecode_user, 'user_created_repo',
185 182 form_result['repo_name_full'], '', self.sa)
186 183 else:
187 184 action_logger(self.rhodecode_user, 'admin_created_repo',
188 185 form_result['repo_name_full'], '', self.sa)
189 186
190 187 except formencode.Invalid, errors:
191 188
192 189 c.new_repo = errors.value['repo_name']
193 190
194 191 if request.POST.get('user_created'):
195 192 r = render('admin/repos/repo_add_create_repository.html')
196 193 else:
197 194 r = render('admin/repos/repo_add.html')
198 195
199 196 return htmlfill.render(
200 197 r,
201 198 defaults=errors.value,
202 199 errors=errors.error_dict or {},
203 200 prefix_error=False,
204 201 encoding="UTF-8")
205 202
206 203 except Exception:
207 204 log.error(traceback.format_exc())
208 205 msg = _('error occurred during creation of repository %s') \
209 206 % form_result.get('repo_name')
210 207 h.flash(msg, category='error')
211 208 if request.POST.get('user_created'):
212 209 return redirect(url('home'))
213 210 return redirect(url('repos'))
214 211
215 212 @HasPermissionAllDecorator('hg.admin')
216 213 def new(self, format='html'):
217 214 """GET /repos/new: Form to create a new item"""
218 215 new_repo = request.GET.get('repo', '')
219 216 c.new_repo = repo_name_slug(new_repo)
220 217 self.__load_defaults()
221 218 return render('admin/repos/repo_add.html')
222 219
223 220 @HasPermissionAllDecorator('hg.admin')
224 221 def update(self, repo_name):
225 222 """
226 223 PUT /repos/repo_name: Update an existing item"""
227 224 # Forms posted to this method should contain a hidden field:
228 225 # <input type="hidden" name="_method" value="PUT" />
229 226 # Or using helpers:
230 227 # h.form(url('repo', repo_name=ID),
231 228 # method='put')
232 229 # url('repo', repo_name=ID)
233 230 self.__load_defaults()
234 231 repo_model = RepoModel()
235 232 changed_name = repo_name
236 233 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
237 234 repo_groups=c.repo_groups_choices)()
238 235 try:
239 236 form_result = _form.to_python(dict(request.POST))
240 237 repo_model.update(repo_name, form_result)
241 238 invalidate_cache('get_repo_cached_%s' % repo_name)
242 239 h.flash(_('Repository %s updated successfully' % repo_name),
243 240 category='success')
244 241 changed_name = form_result['repo_name_full']
245 242 action_logger(self.rhodecode_user, 'admin_updated_repo',
246 243 changed_name, '', self.sa)
247 244
248 245 except formencode.Invalid, errors:
249 246 defaults = self.__load_data(repo_name)
250 247 defaults.update(errors.value)
251 248 return htmlfill.render(
252 249 render('admin/repos/repo_edit.html'),
253 250 defaults=defaults,
254 251 errors=errors.error_dict or {},
255 252 prefix_error=False,
256 253 encoding="UTF-8")
257 254
258 255 except Exception:
259 256 log.error(traceback.format_exc())
260 257 h.flash(_('error occurred during update of repository %s') \
261 258 % repo_name, category='error')
262 259 return redirect(url('edit_repo', repo_name=changed_name))
263 260
264 261 @HasPermissionAllDecorator('hg.admin')
265 262 def delete(self, repo_name):
266 263 """
267 264 DELETE /repos/repo_name: Delete an existing item"""
268 265 # Forms posted to this method should contain a hidden field:
269 266 # <input type="hidden" name="_method" value="DELETE" />
270 267 # Or using helpers:
271 268 # h.form(url('repo', repo_name=ID),
272 269 # method='delete')
273 270 # url('repo', repo_name=ID)
274 271
275 272 repo_model = RepoModel()
276 273 repo = repo_model.get_by_repo_name(repo_name)
277 274 if not repo:
278 275 h.flash(_('%s repository is not mapped to db perhaps'
279 276 ' it was moved or renamed from the filesystem'
280 277 ' please run the application again'
281 278 ' in order to rescan repositories') % repo_name,
282 279 category='error')
283 280
284 281 return redirect(url('repos'))
285 282 try:
286 283 action_logger(self.rhodecode_user, 'admin_deleted_repo',
287 284 repo_name, '', self.sa)
288 285 repo_model.delete(repo)
289 286 invalidate_cache('get_repo_cached_%s' % repo_name)
290 287 h.flash(_('deleted repository %s') % repo_name, category='success')
291 288
292 289 except IntegrityError, e:
293 290 if e.message.find('repositories_fork_id_fkey'):
294 291 log.error(traceback.format_exc())
295 292 h.flash(_('Cannot delete %s it still contains attached '
296 293 'forks') % repo_name,
297 294 category='warning')
298 295 else:
299 296 log.error(traceback.format_exc())
300 297 h.flash(_('An error occurred during '
301 298 'deletion of %s') % repo_name,
302 299 category='error')
303 300
304 301 except Exception, e:
305 302 log.error(traceback.format_exc())
306 303 h.flash(_('An error occurred during deletion of %s') % repo_name,
307 304 category='error')
308 305
309 306 return redirect(url('repos'))
310 307
311 308 @HasPermissionAllDecorator('hg.admin')
312 309 def delete_perm_user(self, repo_name):
313 310 """
314 311 DELETE an existing repository permission user
315 312
316 313 :param repo_name:
317 314 """
318 315
319 316 try:
320 317 repo_model = RepoModel()
321 318 repo_model.delete_perm_user(request.POST, repo_name)
322 319 except Exception, e:
323 320 h.flash(_('An error occurred during deletion of repository user'),
324 321 category='error')
325 322 raise HTTPInternalServerError()
326 323
327 324 @HasPermissionAllDecorator('hg.admin')
328 325 def delete_perm_users_group(self, repo_name):
329 326 """
330 327 DELETE an existing repository permission users group
331 328
332 329 :param repo_name:
333 330 """
334 331 try:
335 332 repo_model = RepoModel()
336 333 repo_model.delete_perm_users_group(request.POST, repo_name)
337 334 except Exception, e:
338 335 h.flash(_('An error occurred during deletion of repository'
339 336 ' users groups'),
340 337 category='error')
341 338 raise HTTPInternalServerError()
342 339
343 340 @HasPermissionAllDecorator('hg.admin')
344 341 def repo_stats(self, repo_name):
345 342 """
346 343 DELETE an existing repository statistics
347 344
348 345 :param repo_name:
349 346 """
350 347
351 348 try:
352 349 repo_model = RepoModel()
353 350 repo_model.delete_stats(repo_name)
354 351 except Exception, e:
355 352 h.flash(_('An error occurred during deletion of repository stats'),
356 353 category='error')
357 354 return redirect(url('edit_repo', repo_name=repo_name))
358 355
359 356 @HasPermissionAllDecorator('hg.admin')
360 357 def repo_cache(self, repo_name):
361 358 """
362 359 INVALIDATE existing repository cache
363 360
364 361 :param repo_name:
365 362 """
366 363
367 364 try:
368 365 ScmModel().mark_for_invalidation(repo_name)
369 366 except Exception, e:
370 367 h.flash(_('An error occurred during cache invalidation'),
371 368 category='error')
372 369 return redirect(url('edit_repo', repo_name=repo_name))
373 370
374 371 @HasPermissionAllDecorator('hg.admin')
375 372 def repo_public_journal(self, repo_name):
376 373 """
377 374 Set's this repository to be visible in public journal,
378 375 in other words assing default user to follow this repo
379 376
380 377 :param repo_name:
381 378 """
382 379
383 380 cur_token = request.POST.get('auth_token')
384 381 token = get_token()
385 382 if cur_token == token:
386 383 try:
387 384 repo_id = Repository.by_repo_name(repo_name).repo_id
388 385 user_id = User.by_username('default').user_id
389 386 self.scm_model.toggle_following_repo(repo_id, user_id)
390 387 h.flash(_('Updated repository visibility in public journal'),
391 388 category='success')
392 389 except:
393 390 h.flash(_('An error occurred during setting this'
394 391 ' repository in public journal'),
395 392 category='error')
396 393
397 394 else:
398 395 h.flash(_('Token mismatch'), category='error')
399 396 return redirect(url('edit_repo', repo_name=repo_name))
400 397
401 398 @HasPermissionAllDecorator('hg.admin')
402 399 def repo_pull(self, repo_name):
403 400 """
404 401 Runs task to update given repository with remote changes,
405 402 ie. make pull on remote location
406 403
407 404 :param repo_name:
408 405 """
409 406 try:
410 407 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
411 408 h.flash(_('Pulled from remote location'), category='success')
412 409 except Exception, e:
413 410 h.flash(_('An error occurred during pull from remote location'),
414 411 category='error')
415 412
416 413 return redirect(url('edit_repo', repo_name=repo_name))
417 414
418 415 @HasPermissionAllDecorator('hg.admin')
419 416 def show(self, repo_name, format='html'):
420 417 """GET /repos/repo_name: Show a specific item"""
421 418 # url('repo', repo_name=ID)
422 419
423 420 @HasPermissionAllDecorator('hg.admin')
424 421 def edit(self, repo_name, format='html'):
425 422 """GET /repos/repo_name/edit: Form to edit an existing item"""
426 423 # url('edit_repo', repo_name=ID)
427 424 defaults = self.__load_data(repo_name)
428 425
429 426 return htmlfill.render(
430 427 render('admin/repos/repo_edit.html'),
431 428 defaults=defaults,
432 429 encoding="UTF-8",
433 430 force_defaults=False
434 431 )
@@ -1,242 +1,238 b''
1 1 import logging
2 2 import traceback
3 3 import formencode
4 4
5 5 from formencode import htmlfill
6 6 from operator import itemgetter
7 7
8 8 from pylons import request, response, session, tmpl_context as c, url
9 9 from pylons.controllers.util import abort, redirect
10 10 from pylons.i18n.translation import _
11 11
12 12 from rhodecode.lib import helpers as h
13 13 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
14 14 HasPermissionAnyDecorator
15 15 from rhodecode.lib.base import BaseController, render
16 16 from rhodecode.model.db import Group
17 17 from rhodecode.model.repos_group import ReposGroupModel
18 18 from rhodecode.model.forms import ReposGroupForm
19 19
20 20 log = logging.getLogger(__name__)
21 21
22 22
23 23 class ReposGroupsController(BaseController):
24 24 """REST Controller styled on the Atom Publishing Protocol"""
25 25 # To properly map this controller, ensure your config/routing.py
26 26 # file has a resource setup:
27 27 # map.resource('repos_group', 'repos_groups')
28 28
29 29 @LoginRequired()
30 30 def __before__(self):
31 31 super(ReposGroupsController, self).__before__()
32 32
33 33 def __load_defaults(self):
34 34
35 35 c.repo_groups = [('', '')]
36 36 parents_link = lambda k: h.literal('&raquo;'.join(
37 37 map(lambda k: k.group_name,
38 38 k.parents + [k])
39 39 )
40 40 )
41 41
42 42 c.repo_groups.extend([(x.group_id, parents_link(x)) for \
43 43 x in self.sa.query(Group).all()])
44 44
45 45 c.repo_groups = sorted(c.repo_groups,
46 46 key=lambda t: t[1].split('&raquo;')[0])
47 47 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
48 48
49 49 def __load_data(self, group_id):
50 50 """
51 51 Load defaults settings for edit, and update
52 52
53 53 :param group_id:
54 54 """
55 55 self.__load_defaults()
56 56
57 57 repo_group = Group.get(group_id)
58 58
59 59 data = repo_group.get_dict()
60 60
61 61 return data
62 62
63 63 @HasPermissionAnyDecorator('hg.admin')
64 64 def index(self, format='html'):
65 65 """GET /repos_groups: All items in the collection"""
66 66 # url('repos_groups')
67 67
68 68 sk = lambda g:g.parents[0].group_name if g.parents else g.group_name
69 69 c.groups = sorted(Group.query().all(), key=sk)
70 70 return render('admin/repos_groups/repos_groups_show.html')
71 71
72 72 @HasPermissionAnyDecorator('hg.admin')
73 73 def create(self):
74 74 """POST /repos_groups: Create a new item"""
75 75 # url('repos_groups')
76 76 self.__load_defaults()
77 77 repos_group_model = ReposGroupModel()
78 78 repos_group_form = ReposGroupForm(available_groups=
79 79 c.repo_groups_choices)()
80 80 try:
81 81 form_result = repos_group_form.to_python(dict(request.POST))
82 82 repos_group_model.create(form_result)
83 83 h.flash(_('created repos group %s') \
84 84 % form_result['group_name'], category='success')
85 85 #TODO: in futureaction_logger(, '', '', '', self.sa)
86 86 except formencode.Invalid, errors:
87 87
88 88 return htmlfill.render(
89 89 render('admin/repos_groups/repos_groups_add.html'),
90 90 defaults=errors.value,
91 91 errors=errors.error_dict or {},
92 92 prefix_error=False,
93 93 encoding="UTF-8")
94 94 except Exception:
95 95 log.error(traceback.format_exc())
96 96 h.flash(_('error occurred during creation of repos group %s') \
97 97 % request.POST.get('group_name'), category='error')
98 98
99 99 return redirect(url('repos_groups'))
100 100
101 101
102 102 @HasPermissionAnyDecorator('hg.admin')
103 103 def new(self, format='html'):
104 104 """GET /repos_groups/new: Form to create a new item"""
105 105 # url('new_repos_group')
106 106 self.__load_defaults()
107 107 return render('admin/repos_groups/repos_groups_add.html')
108 108
109 109 @HasPermissionAnyDecorator('hg.admin')
110 110 def update(self, id):
111 111 """PUT /repos_groups/id: Update an existing item"""
112 112 # Forms posted to this method should contain a hidden field:
113 113 # <input type="hidden" name="_method" value="PUT" />
114 114 # Or using helpers:
115 115 # h.form(url('repos_group', id=ID),
116 116 # method='put')
117 117 # url('repos_group', id=ID)
118 118
119 119 self.__load_defaults()
120 120 c.repos_group = Group.get(id)
121 121
122 122 repos_group_model = ReposGroupModel()
123 123 repos_group_form = ReposGroupForm(edit=True,
124 124 old_data=c.repos_group.get_dict(),
125 125 available_groups=
126 126 c.repo_groups_choices)()
127 127 try:
128 128 form_result = repos_group_form.to_python(dict(request.POST))
129 129 repos_group_model.update(id, form_result)
130 130 h.flash(_('updated repos group %s') \
131 131 % form_result['group_name'], category='success')
132 132 #TODO: in futureaction_logger(, '', '', '', self.sa)
133 133 except formencode.Invalid, errors:
134 134
135 135 return htmlfill.render(
136 136 render('admin/repos_groups/repos_groups_edit.html'),
137 137 defaults=errors.value,
138 138 errors=errors.error_dict or {},
139 139 prefix_error=False,
140 140 encoding="UTF-8")
141 141 except Exception:
142 142 log.error(traceback.format_exc())
143 143 h.flash(_('error occurred during update of repos group %s') \
144 144 % request.POST.get('group_name'), category='error')
145 145
146 146 return redirect(url('repos_groups'))
147 147
148 148
149 149 @HasPermissionAnyDecorator('hg.admin')
150 150 def delete(self, id):
151 151 """DELETE /repos_groups/id: Delete an existing item"""
152 152 # Forms posted to this method should contain a hidden field:
153 153 # <input type="hidden" name="_method" value="DELETE" />
154 154 # Or using helpers:
155 155 # h.form(url('repos_group', id=ID),
156 156 # method='delete')
157 157 # url('repos_group', id=ID)
158 158
159 159 repos_group_model = ReposGroupModel()
160 160 gr = Group.get(id)
161 161 repos = gr.repositories.all()
162 162 if repos:
163 163 h.flash(_('This group contains %s repositores and cannot be '
164 164 'deleted' % len(repos)),
165 165 category='error')
166 166 return redirect(url('repos_groups'))
167 167
168 168
169 169 try:
170 170 repos_group_model.delete(id)
171 171 h.flash(_('removed repos group %s' % gr.group_name), category='success')
172 172 #TODO: in futureaction_logger(, '', '', '', self.sa)
173 173 except Exception:
174 174 log.error(traceback.format_exc())
175 175 h.flash(_('error occurred during deletion of repos group %s' % gr.group_name),
176 176 category='error')
177 177
178 178 return redirect(url('repos_groups'))
179 179
180 180 def show(self, id, format='html'):
181 181 """GET /repos_groups/id: Show a specific item"""
182 182 # url('repos_group', id=ID)
183 183
184 c.group = Group.get(id)
184 gr = c.group = Group.get(id)
185
185 186 if c.group:
186 187 c.group_repos = c.group.repositories.all()
187 188 else:
188 189 return redirect(url('repos_group'))
189 190
191
190 192 sortables = ['name', 'description', 'last_change', 'tip', 'owner']
191 193 current_sort = request.GET.get('sort', 'name')
192 194 current_sort_slug = current_sort.replace('-', '')
193 195
194 196 if current_sort_slug not in sortables:
195 197 c.sort_by = 'name'
196 198 current_sort_slug = c.sort_by
197 199 else:
198 200 c.sort_by = current_sort
199 201 c.sort_slug = current_sort_slug
200 202
201 203 sort_key = current_sort_slug + '_sort'
202 204
203 205 #overwrite our cached list with current filter
204 gr_filter = [r.repo_name for r in c.group_repos]
206 gr_filter = c.group_repos
205 207 c.cached_repo_list = self.scm_model.get_repos(all_repos=gr_filter)
206 208
207 if c.sort_by.startswith('-'):
208 c.repos_list = sorted(c.cached_repo_list, key=itemgetter(sort_key),
209 reverse=True)
210 else:
211 c.repos_list = sorted(c.cached_repo_list, key=itemgetter(sort_key),
212 reverse=False)
209 c.repos_list = c.cached_repo_list
213 210
214 c.repo_cnt = len(c.repos_list)
215
211 c.repo_cnt = 0
216 212
217 213 c.groups = self.sa.query(Group).order_by(Group.group_name)\
218 214 .filter(Group.group_parent_id == id).all()
219 215
220 216 return render('admin/repos_groups/repos_groups.html')
221 217
222 218 @HasPermissionAnyDecorator('hg.admin')
223 219 def edit(self, id, format='html'):
224 220 """GET /repos_groups/id/edit: Form to edit an existing item"""
225 221 # url('edit_repos_group', id=ID)
226 222
227 223 id = int(id)
228 224
229 225 c.repos_group = Group.get(id)
230 226 defaults = self.__load_data(id)
231 227
232 228 # we need to exclude this group from the group list for editing
233 229 c.repo_groups = filter(lambda x:x[0] != id, c.repo_groups)
234 230
235 231 return htmlfill.render(
236 232 render('admin/repos_groups/repos_groups_edit.html'),
237 233 defaults=defaults,
238 234 encoding="UTF-8",
239 235 force_defaults=False
240 236 )
241 237
242 238
@@ -1,364 +1,365 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 settings controller for rhodecode admin
7 7
8 8 :created_on: Jul 14, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29
30 30 from sqlalchemy import func
31 31 from formencode import htmlfill
32 32 from pylons import request, session, tmpl_context as c, url, config
33 33 from pylons.controllers.util import abort, redirect
34 34 from pylons.i18n.translation import _
35 35
36 36 from rhodecode.lib import helpers as h
37 37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
38 38 HasPermissionAnyDecorator, NotAnonymous
39 39 from rhodecode.lib.base import BaseController, render
40 40 from rhodecode.lib.celerylib import tasks, run_task
41 41 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
42 42 set_rhodecode_config, repo_name_slug
43 43 from rhodecode.model.db import RhodeCodeUi, Repository, Group, \
44 44 RhodeCodeSettings
45 45 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
46 46 ApplicationUiSettingsForm
47 47 from rhodecode.model.scm import ScmModel
48 48 from rhodecode.model.user import UserModel
49 49
50 50 log = logging.getLogger(__name__)
51 51
52 52
53 53 class SettingsController(BaseController):
54 54 """REST Controller styled on the Atom Publishing Protocol"""
55 55 # To properly map this controller, ensure your config/routing.py
56 56 # file has a resource setup:
57 57 # map.resource('setting', 'settings', controller='admin/settings',
58 58 # path_prefix='/admin', name_prefix='admin_')
59 59
60 60 @LoginRequired()
61 61 def __before__(self):
62 62 c.admin_user = session.get('admin_user')
63 63 c.admin_username = session.get('admin_username')
64 64 super(SettingsController, self).__before__()
65 65
66 66 @HasPermissionAllDecorator('hg.admin')
67 67 def index(self, format='html'):
68 68 """GET /admin/settings: All items in the collection"""
69 69 # url('admin_settings')
70 70
71 71 defaults = RhodeCodeSettings.get_app_settings()
72 72 defaults.update(self.get_hg_ui_settings())
73 73 return htmlfill.render(
74 74 render('admin/settings/settings.html'),
75 75 defaults=defaults,
76 76 encoding="UTF-8",
77 77 force_defaults=False
78 78 )
79 79
80 80 @HasPermissionAllDecorator('hg.admin')
81 81 def create(self):
82 82 """POST /admin/settings: Create a new item"""
83 83 # url('admin_settings')
84 84
85 85 @HasPermissionAllDecorator('hg.admin')
86 86 def new(self, format='html'):
87 87 """GET /admin/settings/new: Form to create a new item"""
88 88 # url('admin_new_setting')
89 89
90 90 @HasPermissionAllDecorator('hg.admin')
91 91 def update(self, setting_id):
92 92 """PUT /admin/settings/setting_id: Update an existing item"""
93 93 # Forms posted to this method should contain a hidden field:
94 94 # <input type="hidden" name="_method" value="PUT" />
95 95 # Or using helpers:
96 96 # h.form(url('admin_setting', setting_id=ID),
97 97 # method='put')
98 98 # url('admin_setting', setting_id=ID)
99 99 if setting_id == 'mapping':
100 100 rm_obsolete = request.POST.get('destroy', False)
101 101 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
102 102 initial = ScmModel().repo_scan()
103 103 log.debug('invalidating all repositories')
104 104 for repo_name in initial.keys():
105 105 invalidate_cache('get_repo_cached_%s' % repo_name)
106 106
107 107 added, removed = repo2db_mapper(initial, rm_obsolete)
108 108
109 109 h.flash(_('Repositories successfully'
110 110 ' rescanned added: %s,removed: %s') % (added, removed),
111 111 category='success')
112 112
113 113 if setting_id == 'whoosh':
114 114 repo_location = self.get_hg_ui_settings()['paths_root_path']
115 115 full_index = request.POST.get('full_index', False)
116 116 run_task(tasks.whoosh_index, repo_location, full_index)
117 117
118 118 h.flash(_('Whoosh reindex task scheduled'), category='success')
119 119 if setting_id == 'global':
120 120
121 121 application_form = ApplicationSettingsForm()()
122 122 try:
123 123 form_result = application_form.to_python(dict(request.POST))
124 124
125 125 try:
126 126 hgsettings1 = RhodeCodeSettings.get_by_name('title')
127 127 hgsettings1.app_settings_value = \
128 128 form_result['rhodecode_title']
129 129
130 130 hgsettings2 = RhodeCodeSettings.get_by_name('realm')
131 131 hgsettings2.app_settings_value = \
132 132 form_result['rhodecode_realm']
133 133
134 134 hgsettings3 = RhodeCodeSettings.get_by_name('ga_code')
135 135 hgsettings3.app_settings_value = \
136 136 form_result['rhodecode_ga_code']
137 137
138 138 self.sa.add(hgsettings1)
139 139 self.sa.add(hgsettings2)
140 140 self.sa.add(hgsettings3)
141 141 self.sa.commit()
142 142 set_rhodecode_config(config)
143 143 h.flash(_('Updated application settings'),
144 144 category='success')
145 145
146 146 except Exception:
147 147 log.error(traceback.format_exc())
148 148 h.flash(_('error occurred during updating '
149 149 'application settings'),
150 150 category='error')
151 151
152 152 self.sa.rollback()
153 153
154 154 except formencode.Invalid, errors:
155 155 return htmlfill.render(
156 156 render('admin/settings/settings.html'),
157 157 defaults=errors.value,
158 158 errors=errors.error_dict or {},
159 159 prefix_error=False,
160 160 encoding="UTF-8")
161 161
162 162 if setting_id == 'mercurial':
163 163 application_form = ApplicationUiSettingsForm()()
164 164 try:
165 165 form_result = application_form.to_python(dict(request.POST))
166 166
167 167 try:
168 168
169 169 hgsettings1 = self.sa.query(RhodeCodeUi)\
170 170 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
171 171 hgsettings1.ui_value = form_result['web_push_ssl']
172 172
173 173 hgsettings2 = self.sa.query(RhodeCodeUi)\
174 174 .filter(RhodeCodeUi.ui_key == '/').one()
175 175 hgsettings2.ui_value = form_result['paths_root_path']
176 176
177 177 #HOOKS
178 178 hgsettings3 = self.sa.query(RhodeCodeUi)\
179 179 .filter(RhodeCodeUi.ui_key == 'changegroup.update').one()
180 180 hgsettings3.ui_active = \
181 181 bool(form_result['hooks_changegroup_update'])
182 182
183 183 hgsettings4 = self.sa.query(RhodeCodeUi)\
184 184 .filter(RhodeCodeUi.ui_key ==
185 185 'changegroup.repo_size').one()
186 186 hgsettings4.ui_active = \
187 187 bool(form_result['hooks_changegroup_repo_size'])
188 188
189 189 hgsettings5 = self.sa.query(RhodeCodeUi)\
190 190 .filter(RhodeCodeUi.ui_key ==
191 191 'pretxnchangegroup.push_logger').one()
192 192 hgsettings5.ui_active = \
193 193 bool(form_result['hooks_pretxnchangegroup'
194 194 '_push_logger'])
195 195
196 196 hgsettings6 = self.sa.query(RhodeCodeUi)\
197 197 .filter(RhodeCodeUi.ui_key ==
198 198 'preoutgoing.pull_logger').one()
199 199 hgsettings6.ui_active = \
200 200 bool(form_result['hooks_preoutgoing_pull_logger'])
201 201
202 202 self.sa.add(hgsettings1)
203 203 self.sa.add(hgsettings2)
204 204 self.sa.add(hgsettings3)
205 205 self.sa.add(hgsettings4)
206 206 self.sa.add(hgsettings5)
207 207 self.sa.add(hgsettings6)
208 208 self.sa.commit()
209 209
210 210 h.flash(_('Updated mercurial settings'),
211 211 category='success')
212 212
213 213 except:
214 214 log.error(traceback.format_exc())
215 215 h.flash(_('error occurred during updating '
216 216 'application settings'), category='error')
217 217
218 218 self.sa.rollback()
219 219
220 220 except formencode.Invalid, errors:
221 221 return htmlfill.render(
222 222 render('admin/settings/settings.html'),
223 223 defaults=errors.value,
224 224 errors=errors.error_dict or {},
225 225 prefix_error=False,
226 226 encoding="UTF-8")
227 227
228 228 return redirect(url('admin_settings'))
229 229
230 230 @HasPermissionAllDecorator('hg.admin')
231 231 def delete(self, setting_id):
232 232 """DELETE /admin/settings/setting_id: Delete an existing item"""
233 233 # Forms posted to this method should contain a hidden field:
234 234 # <input type="hidden" name="_method" value="DELETE" />
235 235 # Or using helpers:
236 236 # h.form(url('admin_setting', setting_id=ID),
237 237 # method='delete')
238 238 # url('admin_setting', setting_id=ID)
239 239
240 240 @HasPermissionAllDecorator('hg.admin')
241 241 def show(self, setting_id, format='html'):
242 242 """
243 243 GET /admin/settings/setting_id: Show a specific item"""
244 244 # url('admin_setting', setting_id=ID)
245 245
246 246 @HasPermissionAllDecorator('hg.admin')
247 247 def edit(self, setting_id, format='html'):
248 248 """
249 249 GET /admin/settings/setting_id/edit: Form to
250 250 edit an existing item"""
251 251 # url('admin_edit_setting', setting_id=ID)
252 252
253 253 @NotAnonymous()
254 254 def my_account(self):
255 255 """
256 256 GET /_admin/my_account Displays info about my account
257 257 """
258 258 # url('admin_settings_my_account')
259 259
260 260 c.user = UserModel().get(self.rhodecode_user.user_id, cache=False)
261 all_repos = [r.repo_name for r in self.sa.query(Repository)\
261 all_repos = self.sa.query(Repository)\
262 262 .filter(Repository.user_id == c.user.user_id)\
263 .order_by(func.lower(Repository.repo_name)).all()]
263 .order_by(func.lower(Repository.repo_name)).all()
264
264 265 c.user_repos = ScmModel().get_repos(all_repos)
265 266
266 267 if c.user.username == 'default':
267 268 h.flash(_("You can't edit this user since it's"
268 269 " crucial for entire application"), category='warning')
269 270 return redirect(url('users'))
270 271
271 272 defaults = c.user.get_dict()
272 273 return htmlfill.render(
273 274 render('admin/users/user_edit_my_account.html'),
274 275 defaults=defaults,
275 276 encoding="UTF-8",
276 277 force_defaults=False
277 278 )
278 279
279 280 def my_account_update(self):
280 281 """PUT /_admin/my_account_update: Update an existing item"""
281 282 # Forms posted to this method should contain a hidden field:
282 283 # <input type="hidden" name="_method" value="PUT" />
283 284 # Or using helpers:
284 285 # h.form(url('admin_settings_my_account_update'),
285 286 # method='put')
286 287 # url('admin_settings_my_account_update', id=ID)
287 288 user_model = UserModel()
288 289 uid = self.rhodecode_user.user_id
289 290 _form = UserForm(edit=True,
290 291 old_data={'user_id': uid,
291 292 'email': self.rhodecode_user.email})()
292 293 form_result = {}
293 294 try:
294 295 form_result = _form.to_python(dict(request.POST))
295 296 user_model.update_my_account(uid, form_result)
296 297 h.flash(_('Your account was updated successfully'),
297 298 category='success')
298 299
299 300 except formencode.Invalid, errors:
300 301 c.user = user_model.get(self.rhodecode_user.user_id, cache=False)
301 302 c.user = UserModel().get(self.rhodecode_user.user_id, cache=False)
302 303 all_repos = self.sa.query(Repository)\
303 304 .filter(Repository.user_id == c.user.user_id)\
304 305 .order_by(func.lower(Repository.repo_name))\
305 306 .all()
306 307 c.user_repos = ScmModel().get_repos(all_repos)
307 308
308 309 return htmlfill.render(
309 310 render('admin/users/user_edit_my_account.html'),
310 311 defaults=errors.value,
311 312 errors=errors.error_dict or {},
312 313 prefix_error=False,
313 314 encoding="UTF-8")
314 315 except Exception:
315 316 log.error(traceback.format_exc())
316 317 h.flash(_('error occurred during update of user %s') \
317 318 % form_result.get('username'), category='error')
318 319
319 320 return redirect(url('my_account'))
320 321
321 322 @NotAnonymous()
322 323 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
323 324 def create_repository(self):
324 325 """GET /_admin/create_repository: Form to create a new item"""
325 326
326 327 c.repo_groups = [('', '')]
327 328 parents_link = lambda k: h.literal('&raquo;'.join(
328 329 map(lambda k: k.group_name,
329 330 k.parents + [k])
330 331 )
331 332 )
332 333
333 334 c.repo_groups.extend([(x.group_id, parents_link(x)) for \
334 335 x in self.sa.query(Group).all()])
335 336 c.repo_groups = sorted(c.repo_groups,
336 337 key=lambda t: t[1].split('&raquo;')[0])
337 338 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
338 339
339 340 new_repo = request.GET.get('repo', '')
340 341 c.new_repo = repo_name_slug(new_repo)
341 342
342 343 return render('admin/repos/repo_add_create_repository.html')
343 344
344 345 def get_hg_ui_settings(self):
345 346 ret = self.sa.query(RhodeCodeUi).all()
346 347
347 348 if not ret:
348 349 raise Exception('Could not get application ui settings !')
349 350 settings = {}
350 351 for each in ret:
351 352 k = each.ui_key
352 353 v = each.ui_value
353 354 if k == '/':
354 355 k = 'root_path'
355 356
356 357 if k.find('.') != -1:
357 358 k = k.replace('.', '_')
358 359
359 360 if each.ui_section == 'hooks':
360 361 v = each.ui_active
361 362
362 363 settings[each.ui_section + '_' + k] = v
363 364
364 365 return settings
@@ -1,80 +1,76 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.home
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Home controller for Rhodecode
7 7
8 8 :created_on: Feb 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 from operator import itemgetter
28 28
29 29 from pylons import tmpl_context as c, request
30 30 from paste.httpexceptions import HTTPBadRequest
31 31
32 32 from rhodecode.lib.auth import LoginRequired
33 33 from rhodecode.lib.base import BaseController, render
34 from rhodecode.model.db import Group
34 from rhodecode.model.db import Group, Repository
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 class HomeController(BaseController):
40 40
41 41 @LoginRequired()
42 42 def __before__(self):
43 43 super(HomeController, self).__before__()
44 44
45 45 def index(self):
46 46 sortables = ['name', 'description', 'last_change', 'tip', 'owner']
47 47 current_sort = request.GET.get('sort', 'name')
48 48 current_sort_slug = current_sort.replace('-', '')
49 49
50 50 if current_sort_slug not in sortables:
51 51 c.sort_by = 'name'
52 52 current_sort_slug = c.sort_by
53 53 else:
54 54 c.sort_by = current_sort
55 55 c.sort_slug = current_sort_slug
56 56
57 57 sort_key = current_sort_slug + '_sort'
58 58
59 if c.sort_by.startswith('-'):
60 c.repos_list = sorted(c.cached_repo_list, key=itemgetter(sort_key),
61 reverse=True)
62 else:
63 c.repos_list = sorted(c.cached_repo_list, key=itemgetter(sort_key),
64 reverse=False)
59
60 c.repos_list = self.scm_model.get_repos(sort_key=sort_key)
65 61
66 62 c.repo_cnt = len(c.repos_list)
67 63
68
69 64 c.groups = Group.query().filter(Group.group_parent_id == None).all()
70 65
71 66
72 67 return render('/index.html')
73 68
74 69 def repo_switcher(self):
75 70 if request.is_xhr:
76 c.repos_list = sorted(c.cached_repo_list,
77 key=itemgetter('name_sort'), reverse=False)
71 all_repos = Repository.query().order_by(Repository.repo_name).all()
72 c.repos_list = self.scm_model.get_repos(all_repos,
73 sort_key='name_sort')
78 74 return render('/repo_switcher_list.html')
79 75 else:
80 76 return HTTPBadRequest()
@@ -1,208 +1,214 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Settings controller for rhodecode
7 7
8 8 :created_on: Jun 30, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29
30 30 from formencode import htmlfill
31 31
32 32 from pylons import tmpl_context as c, request, url
33 33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 35
36 36 import rhodecode.lib.helpers as h
37 37
38 38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator, \
39 39 HasRepoPermissionAnyDecorator, NotAnonymous
40 40 from rhodecode.lib.base import BaseRepoController, render
41 41 from rhodecode.lib.utils import invalidate_cache, action_logger
42 42
43 43 from rhodecode.model.forms import RepoSettingsForm, RepoForkForm
44 44 from rhodecode.model.repo import RepoModel
45 45 from rhodecode.model.db import User
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class SettingsController(BaseRepoController):
51 51
52 52 @LoginRequired()
53 53 def __before__(self):
54 54 super(SettingsController, self).__before__()
55 55
56 56 @HasRepoPermissionAllDecorator('repository.admin')
57 57 def index(self, repo_name):
58 58 repo_model = RepoModel()
59 59 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
60 60 if not repo:
61 61 h.flash(_('%s repository is not mapped to db perhaps'
62 62 ' it was created or renamed from the file system'
63 63 ' please run the application again'
64 64 ' in order to rescan repositories') % repo_name,
65 65 category='error')
66 66
67 67 return redirect(url('home'))
68 68
69 69 c.users_array = repo_model.get_users_js()
70 70 c.users_groups_array = repo_model.get_users_groups_js()
71 71
72 72 defaults = c.repo_info.get_dict()
73 73
74 74 #fill owner
75 75 if c.repo_info.user:
76 76 defaults.update({'user': c.repo_info.user.username})
77 77 else:
78 78 replacement_user = self.sa.query(User)\
79 79 .filter(User.admin == True).first().username
80 80 defaults.update({'user': replacement_user})
81 81
82 82 #fill repository users
83 83 for p in c.repo_info.repo_to_perm:
84 84 defaults.update({'u_perm_%s' % p.user.username:
85 85 p.permission.permission_name})
86 86
87 87 #fill repository groups
88 88 for p in c.repo_info.users_group_to_perm:
89 89 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
90 90 p.permission.permission_name})
91 91
92 92 return htmlfill.render(
93 93 render('settings/repo_settings.html'),
94 94 defaults=defaults,
95 95 encoding="UTF-8",
96 96 force_defaults=False
97 97 )
98 98
99 99 @HasRepoPermissionAllDecorator('repository.admin')
100 100 def update(self, repo_name):
101 101 repo_model = RepoModel()
102 102 changed_name = repo_name
103 103 _form = RepoSettingsForm(edit=True,
104 104 old_data={'repo_name': repo_name})()
105 105 try:
106 106 form_result = _form.to_python(dict(request.POST))
107 107 repo_model.update(repo_name, form_result)
108 108 invalidate_cache('get_repo_cached_%s' % repo_name)
109 109 h.flash(_('Repository %s updated successfully' % repo_name),
110 110 category='success')
111 111 changed_name = form_result['repo_name']
112 112 action_logger(self.rhodecode_user, 'user_updated_repo',
113 113 changed_name, '', self.sa)
114 114 except formencode.Invalid, errors:
115 115 c.repo_info = repo_model.get_by_repo_name(repo_name)
116 116 c.users_array = repo_model.get_users_js()
117 117 errors.value.update({'user': c.repo_info.user.username})
118 118 return htmlfill.render(
119 119 render('settings/repo_settings.html'),
120 120 defaults=errors.value,
121 121 errors=errors.error_dict or {},
122 122 prefix_error=False,
123 123 encoding="UTF-8")
124 124 except Exception:
125 125 log.error(traceback.format_exc())
126 126 h.flash(_('error occurred during update of repository %s') \
127 127 % repo_name, category='error')
128 128
129 129 return redirect(url('repo_settings_home', repo_name=changed_name))
130 130
131 131 @HasRepoPermissionAllDecorator('repository.admin')
132 132 def delete(self, repo_name):
133 133 """DELETE /repos/repo_name: Delete an existing item"""
134 134 # Forms posted to this method should contain a hidden field:
135 135 # <input type="hidden" name="_method" value="DELETE" />
136 136 # Or using helpers:
137 137 # h.form(url('repo_settings_delete', repo_name=ID),
138 138 # method='delete')
139 139 # url('repo_settings_delete', repo_name=ID)
140 140
141 141 repo_model = RepoModel()
142 142 repo = repo_model.get_by_repo_name(repo_name)
143 143 if not repo:
144 144 h.flash(_('%s repository is not mapped to db perhaps'
145 145 ' it was moved or renamed from the filesystem'
146 146 ' please run the application again'
147 147 ' in order to rescan repositories') % repo_name,
148 148 category='error')
149 149
150 150 return redirect(url('home'))
151 151 try:
152 152 action_logger(self.rhodecode_user, 'user_deleted_repo',
153 153 repo_name, '', self.sa)
154 154 repo_model.delete(repo)
155 155 invalidate_cache('get_repo_cached_%s' % repo_name)
156 156 h.flash(_('deleted repository %s') % repo_name, category='success')
157 157 except Exception:
158 log.error(traceback.format_exc())
158 159 h.flash(_('An error occurred during deletion of %s') % repo_name,
159 160 category='error')
160 161
161 162 return redirect(url('home'))
162 163
163 164 @NotAnonymous()
164 165 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
165 166 'repository.admin')
166 167 def fork(self, repo_name):
167 168 repo_model = RepoModel()
168 169 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
169 170 if not repo:
170 171 h.flash(_('%s repository is not mapped to db perhaps'
171 172 ' it was created or renamed from the file system'
172 173 ' please run the application again'
173 174 ' in order to rescan repositories') % repo_name,
174 175 category='error')
175 176
176 177 return redirect(url('home'))
177 178
178 179 return render('settings/repo_fork.html')
179 180
180 181 @NotAnonymous()
181 182 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
182 183 'repository.admin')
183 184 def fork_create(self, repo_name):
184 185 repo_model = RepoModel()
185 186 c.repo_info = repo_model.get_by_repo_name(repo_name)
186 187 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type})()
187 188 form_result = {}
188 189 try:
189 190 form_result = _form.to_python(dict(request.POST))
190 191 form_result.update({'repo_name': repo_name})
191 192 repo_model.create_fork(form_result, self.rhodecode_user)
192 193 h.flash(_('forked %s repository as %s') \
193 194 % (repo_name, form_result['fork_name']),
194 195 category='success')
195 196 action_logger(self.rhodecode_user,
196 197 'user_forked_repo:%s' % form_result['fork_name'],
197 198 repo_name, '', self.sa)
198 199 except formencode.Invalid, errors:
199 200 c.new_repo = errors.value['fork_name']
200 201 r = render('settings/repo_fork.html')
201 202
202 203 return htmlfill.render(
203 204 r,
204 205 defaults=errors.value,
205 206 errors=errors.error_dict or {},
206 207 prefix_error=False,
207 208 encoding="UTF-8")
209 except Exception:
210 log.error(traceback.format_exc())
211 h.flash(_('An error occurred during repository forking %s') %
212 repo_name, category='error')
213
208 214 return redirect(url('home'))
@@ -1,75 +1,75 b''
1 1 """The base Controller API
2 2
3 3 Provides the BaseController class for subclassing.
4 4 """
5 5 from pylons import config, tmpl_context as c, request, session
6 6 from pylons.controllers import WSGIController
7 7 from pylons.templating import render_mako as render
8 8
9 9 from rhodecode import __version__
10 10 from rhodecode.lib.auth import AuthUser
11 11 from rhodecode.lib.utils import get_repo_slug
12 12 from rhodecode.model import meta
13 13 from rhodecode.model.scm import ScmModel
14 14 from rhodecode import BACKENDS
15 from rhodecode.model.db import Repository
15 16
16 17
17 18 class BaseController(WSGIController):
18 19
19 20 def __before__(self):
20 21 c.rhodecode_version = __version__
21 22 c.rhodecode_name = config.get('rhodecode_title')
22 23 c.ga_code = config.get('rhodecode_ga_code')
23 24 c.repo_name = get_repo_slug(request)
24 25 c.backends = BACKENDS.keys()
25 26 self.cut_off_limit = int(config.get('cut_off_limit'))
26 27
27 28 self.sa = meta.Session()
28 29 self.scm_model = ScmModel(self.sa)
29 c.cached_repo_list = self.scm_model.get_repos()
30
30 31 #c.unread_journal = scm_model.get_unread_journal()
31 32
32 33 def __call__(self, environ, start_response):
33 34 """Invoke the Controller"""
34 35 # WSGIController.__call__ dispatches to the Controller method
35 36 # the request is routed to. This routing information is
36 37 # available in environ['pylons.routes_dict']
37 38 try:
38 39 # putting this here makes sure that we update permissions each time
39 40 api_key = request.GET.get('api_key')
40 41 user_id = getattr(session.get('rhodecode_user'), 'user_id', None)
41 42 self.rhodecode_user = c.rhodecode_user = AuthUser(user_id, api_key)
42 43 self.rhodecode_user.set_authenticated(
43 44 getattr(session.get('rhodecode_user'),
44 45 'is_authenticated', False))
45 46 session['rhodecode_user'] = self.rhodecode_user
46 47 session.save()
47 48 return WSGIController.__call__(self, environ, start_response)
48 49 finally:
49 50 meta.Session.remove()
50 51
51 52
52 53 class BaseRepoController(BaseController):
53 54 """
54 55 Base class for controllers responsible for loading all needed data
55 56 for those controllers, loaded items are
56 57
57 58 c.rhodecode_repo: instance of scm repository (taken from cache)
58 59
59 60 """
60 61
61 62 def __before__(self):
62 63 super(BaseRepoController, self).__before__()
63 64 if c.repo_name:
64 65
65 c.rhodecode_repo, dbrepo = self.scm_model.get(c.repo_name,
66 retval='repo')
66 c.rhodecode_repo = Repository.by_repo_name(c.repo_name).scm_instance
67 67
68 68 if c.rhodecode_repo is not None:
69 69 c.repository_followers = \
70 70 self.scm_model.get_followers(c.repo_name)
71 71 c.repository_forks = self.scm_model.get_forks(c.repo_name)
72 72 else:
73 73 c.repository_followers = 0
74 74 c.repository_forks = 0
75 75
@@ -1,378 +1,377 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.celerylib.tasks
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 RhodeCode task modules, containing all task that suppose to be run
7 7 by celery daemon
8 8
9 9 :created_on: Oct 6, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26 from celery.decorators import task
27 27
28 28 import os
29 29 import traceback
30 30 import logging
31 31 from os.path import dirname as dn, join as jn
32 32
33 33 from time import mktime
34 34 from operator import itemgetter
35 35 from string import lower
36 36
37 37 from pylons import config
38 38 from pylons.i18n.translation import _
39 39
40 40 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP
41 41 from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
42 42 __get_lockkey, LockHeld, DaemonLock
43 43 from rhodecode.lib.helpers import person
44 44 from rhodecode.lib.smtp_mailer import SmtpMailer
45 45 from rhodecode.lib.utils import add_cache
46 46 from rhodecode.lib.odict import OrderedDict
47 47 from rhodecode.model import init_model
48 48 from rhodecode.model import meta
49 49 from rhodecode.model.db import RhodeCodeUi, Statistics, Repository
50 50
51 51 from vcs.backends import get_repo
52 52
53 53 from sqlalchemy import engine_from_config
54 54
55 55 add_cache(config)
56 56
57 57 try:
58 58 import json
59 59 except ImportError:
60 60 #python 2.5 compatibility
61 61 import simplejson as json
62 62
63 63 __all__ = ['whoosh_index', 'get_commits_stats',
64 64 'reset_user_password', 'send_email']
65 65
66 66 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
67 67
68 68
69 69
70 70 def get_session():
71 71 if CELERY_ON:
72 72 engine = engine_from_config(config, 'sqlalchemy.db1.')
73 73 init_model(engine)
74 74 sa = meta.Session()
75 75 return sa
76 76
77 77
78 78 def get_repos_path():
79 79 sa = get_session()
80 80 q = sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
81 81 return q.ui_value
82 82
83 83
84 84 @task(ignore_result=True)
85 85 @locked_task
86 86 def whoosh_index(repo_location, full_index):
87 87 #log = whoosh_index.get_logger()
88 88 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
89 89 index_location = config['index_dir']
90 90 WhooshIndexingDaemon(index_location=index_location,
91 91 repo_location=repo_location, sa=get_session())\
92 92 .run(full_index=full_index)
93 93
94 94
95 95 @task(ignore_result=True)
96 96 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
97 97 try:
98 98 log = get_commits_stats.get_logger()
99 99 except:
100 100 log = logging.getLogger(__name__)
101 101
102 102 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
103 103 ts_max_y)
104 104 lockkey_path = dn(dn(dn(dn(os.path.abspath(__file__)))))
105 print jn(lockkey_path, lockkey)
106 105 log.info('running task with lockkey %s', lockkey)
107 106 try:
108 107 lock = l = DaemonLock(jn(lockkey_path, lockkey))
109 108
110 109 #for js data compatibilty cleans the key for person from '
111 110 akc = lambda k: person(k).replace('"', "")
112 111
113 112 co_day_auth_aggr = {}
114 113 commits_by_day_aggregate = {}
115 114 repos_path = get_repos_path()
116 115 p = os.path.join(repos_path, repo_name)
117 116 repo = get_repo(p)
118 117 repo_size = len(repo.revisions)
119 118 #return if repo have no revisions
120 119 if repo_size < 1:
121 120 lock.release()
122 121 return True
123 122
124 123 skip_date_limit = True
125 124 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
126 125 last_rev = 0
127 126 last_cs = None
128 127 timegetter = itemgetter('time')
129 128
130 129 sa = get_session()
131 130
132 131 dbrepo = sa.query(Repository)\
133 132 .filter(Repository.repo_name == repo_name).scalar()
134 133 cur_stats = sa.query(Statistics)\
135 134 .filter(Statistics.repository == dbrepo).scalar()
136 135
137 136 if cur_stats is not None:
138 137 last_rev = cur_stats.stat_on_revision
139 138
140 139 if last_rev == repo.get_changeset().revision and repo_size > 1:
141 140 #pass silently without any work if we're not on first revision or
142 141 #current state of parsing revision(from db marker) is the
143 142 #last revision
144 143 lock.release()
145 144 return True
146 145
147 146 if cur_stats:
148 147 commits_by_day_aggregate = OrderedDict(json.loads(
149 148 cur_stats.commit_activity_combined))
150 149 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
151 150
152 151 log.debug('starting parsing %s', parse_limit)
153 152 lmktime = mktime
154 153
155 154 last_rev = last_rev + 1 if last_rev > 0 else last_rev
156 155
157 156 for cs in repo[last_rev:last_rev + parse_limit]:
158 157 last_cs = cs # remember last parsed changeset
159 158 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
160 159 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
161 160
162 161 if akc(cs.author) in co_day_auth_aggr:
163 162 try:
164 163 l = [timegetter(x) for x in
165 164 co_day_auth_aggr[akc(cs.author)]['data']]
166 165 time_pos = l.index(k)
167 166 except ValueError:
168 167 time_pos = False
169 168
170 169 if time_pos >= 0 and time_pos is not False:
171 170
172 171 datadict = \
173 172 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
174 173
175 174 datadict["commits"] += 1
176 175 datadict["added"] += len(cs.added)
177 176 datadict["changed"] += len(cs.changed)
178 177 datadict["removed"] += len(cs.removed)
179 178
180 179 else:
181 180 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
182 181
183 182 datadict = {"time": k,
184 183 "commits": 1,
185 184 "added": len(cs.added),
186 185 "changed": len(cs.changed),
187 186 "removed": len(cs.removed),
188 187 }
189 188 co_day_auth_aggr[akc(cs.author)]['data']\
190 189 .append(datadict)
191 190
192 191 else:
193 192 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
194 193 co_day_auth_aggr[akc(cs.author)] = {
195 194 "label": akc(cs.author),
196 195 "data": [{"time":k,
197 196 "commits":1,
198 197 "added":len(cs.added),
199 198 "changed":len(cs.changed),
200 199 "removed":len(cs.removed),
201 200 }],
202 201 "schema": ["commits"],
203 202 }
204 203
205 204 #gather all data by day
206 205 if k in commits_by_day_aggregate:
207 206 commits_by_day_aggregate[k] += 1
208 207 else:
209 208 commits_by_day_aggregate[k] = 1
210 209
211 210 overview_data = sorted(commits_by_day_aggregate.items(),
212 211 key=itemgetter(0))
213 212
214 213 if not co_day_auth_aggr:
215 214 co_day_auth_aggr[akc(repo.contact)] = {
216 215 "label": akc(repo.contact),
217 216 "data": [0, 1],
218 217 "schema": ["commits"],
219 218 }
220 219
221 220 stats = cur_stats if cur_stats else Statistics()
222 221 stats.commit_activity = json.dumps(co_day_auth_aggr)
223 222 stats.commit_activity_combined = json.dumps(overview_data)
224 223
225 224 log.debug('last revison %s', last_rev)
226 225 leftovers = len(repo.revisions[last_rev:])
227 226 log.debug('revisions to parse %s', leftovers)
228 227
229 228 if last_rev == 0 or leftovers < parse_limit:
230 229 log.debug('getting code trending stats')
231 230 stats.languages = json.dumps(__get_codes_stats(repo_name))
232 231
233 232 try:
234 233 stats.repository = dbrepo
235 234 stats.stat_on_revision = last_cs.revision if last_cs else 0
236 235 sa.add(stats)
237 236 sa.commit()
238 237 except:
239 238 log.error(traceback.format_exc())
240 239 sa.rollback()
241 240 lock.release()
242 241 return False
243 242
244 243 #final release
245 244 lock.release()
246 245
247 246 #execute another task if celery is enabled
248 247 if len(repo.revisions) > 1 and CELERY_ON:
249 248 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
250 249 return True
251 250 except LockHeld:
252 251 log.info('LockHeld')
253 252 return 'Task with key %s already running' % lockkey
254 253
255 254
256 255 @task(ignore_result=True)
257 256 def reset_user_password(user_email):
258 257 try:
259 258 log = reset_user_password.get_logger()
260 259 except:
261 260 log = logging.getLogger(__name__)
262 261
263 262 from rhodecode.lib import auth
264 263 from rhodecode.model.db import User
265 264
266 265 try:
267 266 try:
268 267 sa = get_session()
269 268 user = sa.query(User).filter(User.email == user_email).scalar()
270 269 new_passwd = auth.PasswordGenerator().gen_password(8,
271 270 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
272 271 if user:
273 272 user.password = auth.get_crypt_password(new_passwd)
274 273 user.api_key = auth.generate_api_key(user.username)
275 274 sa.add(user)
276 275 sa.commit()
277 276 log.info('change password for %s', user_email)
278 277 if new_passwd is None:
279 278 raise Exception('unable to generate new password')
280 279
281 280 except:
282 281 log.error(traceback.format_exc())
283 282 sa.rollback()
284 283
285 284 run_task(send_email, user_email,
286 285 "Your new rhodecode password",
287 286 'Your new rhodecode password:%s' % (new_passwd))
288 287 log.info('send new password mail to %s', user_email)
289 288
290 289 except:
291 290 log.error('Failed to update user password')
292 291 log.error(traceback.format_exc())
293 292
294 293 return True
295 294
296 295
297 296 @task(ignore_result=True)
298 297 def send_email(recipients, subject, body):
299 298 """
300 299 Sends an email with defined parameters from the .ini files.
301 300
302 301 :param recipients: list of recipients, it this is empty the defined email
303 302 address from field 'email_to' is used instead
304 303 :param subject: subject of the mail
305 304 :param body: body of the mail
306 305 """
307 306 try:
308 307 log = send_email.get_logger()
309 308 except:
310 309 log = logging.getLogger(__name__)
311 310
312 311 email_config = config
313 312
314 313 if not recipients:
315 314 recipients = [email_config.get('email_to')]
316 315
317 316 mail_from = email_config.get('app_email_from')
318 317 user = email_config.get('smtp_username')
319 318 passwd = email_config.get('smtp_password')
320 319 mail_server = email_config.get('smtp_server')
321 320 mail_port = email_config.get('smtp_port')
322 321 tls = str2bool(email_config.get('smtp_use_tls'))
323 322 ssl = str2bool(email_config.get('smtp_use_ssl'))
324 323 debug = str2bool(config.get('debug'))
325 324
326 325 try:
327 326 m = SmtpMailer(mail_from, user, passwd, mail_server,
328 327 mail_port, ssl, tls, debug=debug)
329 328 m.send(recipients, subject, body)
330 329 except:
331 330 log.error('Mail sending failed')
332 331 log.error(traceback.format_exc())
333 332 return False
334 333 return True
335 334
336 335
337 336 @task(ignore_result=True)
338 337 def create_repo_fork(form_data, cur_user):
339 338 from rhodecode.model.repo import RepoModel
340 339 from vcs import get_backend
341 340
342 341 try:
343 342 log = create_repo_fork.get_logger()
344 343 except:
345 344 log = logging.getLogger(__name__)
346 345
347 346 repo_model = RepoModel(get_session())
348 347 repo_model.create(form_data, cur_user, just_db=True, fork=True)
349 348 repo_name = form_data['repo_name']
350 349 repos_path = get_repos_path()
351 350 repo_path = os.path.join(repos_path, repo_name)
352 351 repo_fork_path = os.path.join(repos_path, form_data['fork_name'])
353 352 alias = form_data['repo_type']
354 353
355 354 log.info('creating repo fork %s as %s', repo_name, repo_path)
356 355 backend = get_backend(alias)
357 356 backend(str(repo_fork_path), create=True, src_url=str(repo_path))
358 357
359 358
360 359 def __get_codes_stats(repo_name):
361 360 repos_path = get_repos_path()
362 361 p = os.path.join(repos_path, repo_name)
363 362 repo = get_repo(p)
364 363 tip = repo.get_changeset()
365 364 code_stats = {}
366 365
367 366 def aggregate(cs):
368 367 for f in cs[2]:
369 368 ext = lower(f.extension)
370 369 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
371 370 if ext in code_stats:
372 371 code_stats[ext] += 1
373 372 else:
374 373 code_stats[ext] = 1
375 374
376 375 map(aggregate, tip.walk('/'))
377 376
378 377 return code_stats or {}
@@ -1,695 +1,694 b''
1 1 """Helper functions
2 2
3 3 Consists of functions to typically be used within templates, but also
4 4 available to Controllers. This module is available to both as 'h'.
5 5 """
6 6 import random
7 7 import hashlib
8 8 import StringIO
9 9 import urllib
10 10
11 11 from datetime import datetime
12 12 from pygments.formatters import HtmlFormatter
13 13 from pygments import highlight as code_highlight
14 14 from pylons import url, request, config
15 15 from pylons.i18n.translation import _, ungettext
16 16
17 17 from webhelpers.html import literal, HTML, escape
18 18 from webhelpers.html.tools import *
19 19 from webhelpers.html.builder import make_tag
20 20 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
21 21 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
22 22 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
23 23 password, textarea, title, ul, xml_declaration, radio
24 24 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
25 25 mail_to, strip_links, strip_tags, tag_re
26 26 from webhelpers.number import format_byte_size, format_bit_size
27 27 from webhelpers.pylonslib import Flash as _Flash
28 28 from webhelpers.pylonslib.secure_form import secure_form
29 29 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
30 30 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
31 31 replace_whitespace, urlify, truncate, wrap_paragraphs
32 32 from webhelpers.date import time_ago_in_words
33 33 from webhelpers.paginate import Page
34 34 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
35 35 convert_boolean_attrs, NotGiven
36 36
37 37 from vcs.utils.annotate import annotate_highlight
38 38 from rhodecode.lib.utils import repo_name_slug
39 39 from rhodecode.lib import str2bool, safe_unicode
40 40
41 41 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
42 42 """
43 43 Reset button
44 44 """
45 45 _set_input_attrs(attrs, type, name, value)
46 46 _set_id_attr(attrs, id, name)
47 47 convert_boolean_attrs(attrs, ["disabled"])
48 48 return HTML.input(**attrs)
49 49
50 50 reset = _reset
51 51
52 52
53 53 def get_token():
54 54 """Return the current authentication token, creating one if one doesn't
55 55 already exist.
56 56 """
57 57 token_key = "_authentication_token"
58 58 from pylons import session
59 59 if not token_key in session:
60 60 try:
61 61 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
62 62 except AttributeError: # Python < 2.4
63 63 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
64 64 session[token_key] = token
65 65 if hasattr(session, 'save'):
66 66 session.save()
67 67 return session[token_key]
68 68
69 69 class _GetError(object):
70 70 """Get error from form_errors, and represent it as span wrapped error
71 71 message
72 72
73 73 :param field_name: field to fetch errors for
74 74 :param form_errors: form errors dict
75 75 """
76 76
77 77 def __call__(self, field_name, form_errors):
78 78 tmpl = """<span class="error_msg">%s</span>"""
79 79 if form_errors and form_errors.has_key(field_name):
80 80 return literal(tmpl % form_errors.get(field_name))
81 81
82 82 get_error = _GetError()
83 83
84 84 class _ToolTip(object):
85 85
86 86 def __call__(self, tooltip_title, trim_at=50):
87 87 """Special function just to wrap our text into nice formatted
88 88 autowrapped text
89 89
90 90 :param tooltip_title:
91 91 """
92 92
93 93 return escape(tooltip_title)
94 94
95 95 def activate(self):
96 96 """Adds tooltip mechanism to the given Html all tooltips have to have
97 97 set class `tooltip` and set attribute `tooltip_title`.
98 98 Then a tooltip will be generated based on that. All with yui js tooltip
99 99 """
100 100
101 101 js = '''
102 102 YAHOO.util.Event.onDOMReady(function(){
103 103 function toolTipsId(){
104 104 var ids = [];
105 105 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
106 106
107 107 for (var i = 0; i < tts.length; i++) {
108 108 //if element doesn't not have and id autogenerate one for tooltip
109 109
110 110 if (!tts[i].id){
111 111 tts[i].id='tt'+i*100;
112 112 }
113 113 ids.push(tts[i].id);
114 114 }
115 115 return ids
116 116 };
117 117 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
118 118 context: [[toolTipsId()],"tl","bl",null,[0,5]],
119 119 monitorresize:false,
120 120 xyoffset :[0,0],
121 121 autodismissdelay:300000,
122 122 hidedelay:5,
123 123 showdelay:20,
124 124 });
125 125
126 126 });
127 127 '''
128 128 return literal(js)
129 129
130 130 tooltip = _ToolTip()
131 131
132 132 class _FilesBreadCrumbs(object):
133 133
134 134 def __call__(self, repo_name, rev, paths):
135 135 if isinstance(paths, str):
136 136 paths = safe_unicode(paths)
137 137 url_l = [link_to(repo_name, url('files_home',
138 138 repo_name=repo_name,
139 139 revision=rev, f_path=''))]
140 140 paths_l = paths.split('/')
141 141 for cnt, p in enumerate(paths_l):
142 142 if p != '':
143 143 url_l.append(link_to(p, url('files_home',
144 144 repo_name=repo_name,
145 145 revision=rev,
146 146 f_path='/'.join(paths_l[:cnt + 1]))))
147 147
148 148 return literal('/'.join(url_l))
149 149
150 150 files_breadcrumbs = _FilesBreadCrumbs()
151 151
152 152 class CodeHtmlFormatter(HtmlFormatter):
153 153 """My code Html Formatter for source codes
154 154 """
155 155
156 156 def wrap(self, source, outfile):
157 157 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
158 158
159 159 def _wrap_code(self, source):
160 160 for cnt, it in enumerate(source):
161 161 i, t = it
162 162 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
163 163 yield i, t
164 164
165 165 def _wrap_tablelinenos(self, inner):
166 166 dummyoutfile = StringIO.StringIO()
167 167 lncount = 0
168 168 for t, line in inner:
169 169 if t:
170 170 lncount += 1
171 171 dummyoutfile.write(line)
172 172
173 173 fl = self.linenostart
174 174 mw = len(str(lncount + fl - 1))
175 175 sp = self.linenospecial
176 176 st = self.linenostep
177 177 la = self.lineanchors
178 178 aln = self.anchorlinenos
179 179 nocls = self.noclasses
180 180 if sp:
181 181 lines = []
182 182
183 183 for i in range(fl, fl + lncount):
184 184 if i % st == 0:
185 185 if i % sp == 0:
186 186 if aln:
187 187 lines.append('<a href="#%s%d" class="special">%*d</a>' %
188 188 (la, i, mw, i))
189 189 else:
190 190 lines.append('<span class="special">%*d</span>' % (mw, i))
191 191 else:
192 192 if aln:
193 193 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
194 194 else:
195 195 lines.append('%*d' % (mw, i))
196 196 else:
197 197 lines.append('')
198 198 ls = '\n'.join(lines)
199 199 else:
200 200 lines = []
201 201 for i in range(fl, fl + lncount):
202 202 if i % st == 0:
203 203 if aln:
204 204 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
205 205 else:
206 206 lines.append('%*d' % (mw, i))
207 207 else:
208 208 lines.append('')
209 209 ls = '\n'.join(lines)
210 210
211 211 # in case you wonder about the seemingly redundant <div> here: since the
212 212 # content in the other cell also is wrapped in a div, some browsers in
213 213 # some configurations seem to mess up the formatting...
214 214 if nocls:
215 215 yield 0, ('<table class="%stable">' % self.cssclass +
216 216 '<tr><td><div class="linenodiv" '
217 217 'style="background-color: #f0f0f0; padding-right: 10px">'
218 218 '<pre style="line-height: 125%">' +
219 219 ls + '</pre></div></td><td id="hlcode" class="code">')
220 220 else:
221 221 yield 0, ('<table class="%stable">' % self.cssclass +
222 222 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
223 223 ls + '</pre></div></td><td id="hlcode" class="code">')
224 224 yield 0, dummyoutfile.getvalue()
225 225 yield 0, '</td></tr></table>'
226 226
227 227
228 228 def pygmentize(filenode, **kwargs):
229 229 """pygmentize function using pygments
230 230
231 231 :param filenode:
232 232 """
233 233
234 234 return literal(code_highlight(filenode.content,
235 235 filenode.lexer, CodeHtmlFormatter(**kwargs)))
236 236
237 237 def pygmentize_annotation(repo_name, filenode, **kwargs):
238 238 """pygmentize function for annotation
239 239
240 240 :param filenode:
241 241 """
242 242
243 243 color_dict = {}
244 244 def gen_color(n=10000):
245 245 """generator for getting n of evenly distributed colors using
246 246 hsv color and golden ratio. It always return same order of colors
247 247
248 248 :returns: RGB tuple
249 249 """
250 250 import colorsys
251 251 golden_ratio = 0.618033988749895
252 252 h = 0.22717784590367374
253 253
254 254 for _ in xrange(n):
255 255 h += golden_ratio
256 256 h %= 1
257 257 HSV_tuple = [h, 0.95, 0.95]
258 258 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
259 259 yield map(lambda x:str(int(x * 256)), RGB_tuple)
260 260
261 261 cgenerator = gen_color()
262 262
263 263 def get_color_string(cs):
264 264 if color_dict.has_key(cs):
265 265 col = color_dict[cs]
266 266 else:
267 267 col = color_dict[cs] = cgenerator.next()
268 268 return "color: rgb(%s)! important;" % (', '.join(col))
269 269
270 270 def url_func(repo_name):
271 271
272 272 def _url_func(changeset):
273 273 author = changeset.author
274 274 date = changeset.date
275 275 message = tooltip(changeset.message)
276 276
277 277 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
278 278 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
279 279 "</b> %s<br/></div>")
280 280
281 281 tooltip_html = tooltip_html % (author, date, message)
282 282 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
283 283 short_id(changeset.raw_id))
284 284 uri = link_to(
285 285 lnk_format,
286 286 url('changeset_home', repo_name=repo_name,
287 287 revision=changeset.raw_id),
288 288 style=get_color_string(changeset.raw_id),
289 289 class_='tooltip',
290 290 title=tooltip_html
291 291 )
292 292
293 293 uri += '\n'
294 294 return uri
295 295 return _url_func
296 296
297 297 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
298 298
299 299 def get_changeset_safe(repo, rev):
300 300 from vcs.backends.base import BaseRepository
301 301 from vcs.exceptions import RepositoryError
302 302 if not isinstance(repo, BaseRepository):
303 303 raise Exception('You must pass an Repository '
304 304 'object as first argument got %s', type(repo))
305 305
306 306 try:
307 307 cs = repo.get_changeset(rev)
308 308 except RepositoryError:
309 309 from rhodecode.lib.utils import EmptyChangeset
310 310 cs = EmptyChangeset()
311 311 return cs
312 312
313 313
314 314 def is_following_repo(repo_name, user_id):
315 315 from rhodecode.model.scm import ScmModel
316 316 return ScmModel().is_following_repo(repo_name, user_id)
317 317
318 318 flash = _Flash()
319 319
320 320 #==============================================================================
321 321 # SCM FILTERS available via h.
322 322 #==============================================================================
323 323 from vcs.utils import author_name, author_email
324 324 from rhodecode.lib import credentials_hidder, age as _age
325 325
326 326 age = lambda x:_age(x)
327 327 capitalize = lambda x: x.capitalize()
328 328 email = author_email
329 329 email_or_none = lambda x: email(x) if email(x) != x else None
330 330 person = lambda x: author_name(x)
331 331 short_id = lambda x: x[:12]
332 332 hide_credentials = lambda x: ''.join(credentials_hidder(x))
333 333
334 334 def bool2icon(value):
335 335 """Returns True/False values represented as small html image of true/false
336 336 icons
337 337
338 338 :param value: bool value
339 339 """
340 340
341 341 if value is True:
342 342 return HTML.tag('img', src=url("/images/icons/accept.png"),
343 343 alt=_('True'))
344 344
345 345 if value is False:
346 346 return HTML.tag('img', src=url("/images/icons/cancel.png"),
347 347 alt=_('False'))
348 348
349 349 return value
350 350
351 351
352 352 def action_parser(user_log, feed=False):
353 353 """This helper will action_map the specified string action into translated
354 354 fancy names with icons and links
355 355
356 356 :param user_log: user log instance
357 357 :param feed: use output for feeds (no html and fancy icons)
358 358 """
359 359
360 360 action = user_log.action
361 361 action_params = ' '
362 362
363 363 x = action.split(':')
364 364
365 365 if len(x) > 1:
366 366 action, action_params = x
367 367
368 368 def get_cs_links():
369 369 revs_limit = 5 #display this amount always
370 370 revs_top_limit = 50 #show upto this amount of changesets hidden
371 371 revs = action_params.split(',')
372 372 repo_name = user_log.repository.repo_name
373 373
374 374 from rhodecode.model.scm import ScmModel
375 repo, dbrepo = ScmModel().get(repo_name, retval='repo',
376 invalidation_list=[])
375 repo = user_log.repository.scm_instance
377 376
378 377 message = lambda rev: get_changeset_safe(repo, rev).message
379 378 cs_links = []
380 379 cs_links.append(" " + ', '.join ([link_to(rev,
381 380 url('changeset_home',
382 381 repo_name=repo_name,
383 382 revision=rev), title=tooltip(message(rev)),
384 383 class_='tooltip') for rev in revs[:revs_limit] ]))
385 384
386 385 compare_view = (' <div class="compare_view tooltip" title="%s">'
387 386 '<a href="%s">%s</a> '
388 387 '</div>' % (_('Show all combined changesets %s->%s' \
389 388 % (revs[0], revs[-1])),
390 389 url('changeset_home', repo_name=repo_name,
391 390 revision='%s...%s' % (revs[0], revs[-1])
392 391 ),
393 392 _('compare view'))
394 393 )
395 394
396 395 if len(revs) > revs_limit:
397 396 uniq_id = revs[0]
398 397 html_tmpl = ('<span> %s '
399 398 '<a class="show_more" id="_%s" href="#more">%s</a> '
400 399 '%s</span>')
401 400 if not feed:
402 401 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
403 402 % (len(revs) - revs_limit),
404 403 _('revisions')))
405 404
406 405 if not feed:
407 406 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
408 407 else:
409 408 html_tmpl = '<span id="%s"> %s </span>'
410 409
411 410 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
412 411 url('changeset_home',
413 412 repo_name=repo_name, revision=rev),
414 413 title=message(rev), class_='tooltip')
415 414 for rev in revs[revs_limit:revs_top_limit]])))
416 415 if len(revs) > 1:
417 416 cs_links.append(compare_view)
418 417 return ''.join(cs_links)
419 418
420 419 def get_fork_name():
421 420 repo_name = action_params
422 421 return _('fork name ') + str(link_to(action_params, url('summary_home',
423 422 repo_name=repo_name,)))
424 423
425 424 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
426 425 'user_created_repo':(_('[created] repository'), None),
427 426 'user_forked_repo':(_('[forked] repository'), get_fork_name),
428 427 'user_updated_repo':(_('[updated] repository'), None),
429 428 'admin_deleted_repo':(_('[delete] repository'), None),
430 429 'admin_created_repo':(_('[created] repository'), None),
431 430 'admin_forked_repo':(_('[forked] repository'), None),
432 431 'admin_updated_repo':(_('[updated] repository'), None),
433 432 'push':(_('[pushed] into'), get_cs_links),
434 433 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
435 434 'push_remote':(_('[pulled from remote] into'), get_cs_links),
436 435 'pull':(_('[pulled] from'), None),
437 436 'started_following_repo':(_('[started following] repository'), None),
438 437 'stopped_following_repo':(_('[stopped following] repository'), None),
439 438 }
440 439
441 440 action_str = action_map.get(action, action)
442 441 if feed:
443 442 action = action_str[0].replace('[', '').replace(']', '')
444 443 else:
445 444 action = action_str[0].replace('[', '<span class="journal_highlight">')\
446 445 .replace(']', '</span>')
447 446
448 447 action_params_func = lambda :""
449 448
450 449 if callable(action_str[1]):
451 450 action_params_func = action_str[1]
452 451
453 452 return [literal(action), action_params_func]
454 453
455 454 def action_parser_icon(user_log):
456 455 action = user_log.action
457 456 action_params = None
458 457 x = action.split(':')
459 458
460 459 if len(x) > 1:
461 460 action, action_params = x
462 461
463 462 tmpl = """<img src="%s%s" alt="%s"/>"""
464 463 map = {'user_deleted_repo':'database_delete.png',
465 464 'user_created_repo':'database_add.png',
466 465 'user_forked_repo':'arrow_divide.png',
467 466 'user_updated_repo':'database_edit.png',
468 467 'admin_deleted_repo':'database_delete.png',
469 468 'admin_created_repo':'database_add.png',
470 469 'admin_forked_repo':'arrow_divide.png',
471 470 'admin_updated_repo':'database_edit.png',
472 471 'push':'script_add.png',
473 472 'push_local':'script_edit.png',
474 473 'push_remote':'connect.png',
475 474 'pull':'down_16.png',
476 475 'started_following_repo':'heart_add.png',
477 476 'stopped_following_repo':'heart_delete.png',
478 477 }
479 478 return literal(tmpl % ((url('/images/icons/')),
480 479 map.get(action, action), action))
481 480
482 481
483 482 #==============================================================================
484 483 # PERMS
485 484 #==============================================================================
486 485 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
487 486 HasRepoPermissionAny, HasRepoPermissionAll
488 487
489 488 #==============================================================================
490 489 # GRAVATAR URL
491 490 #==============================================================================
492 491
493 492 def gravatar_url(email_address, size=30):
494 493 if not str2bool(config['app_conf'].get('use_gravatar')) or \
495 494 email_address == 'anonymous@rhodecode.org':
496 495 return "/images/user%s.png" % size
497 496
498 497 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
499 498 default = 'identicon'
500 499 baseurl_nossl = "http://www.gravatar.com/avatar/"
501 500 baseurl_ssl = "https://secure.gravatar.com/avatar/"
502 501 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
503 502
504 503 if isinstance(email_address, unicode):
505 504 #hashlib crashes on unicode items
506 505 email_address = email_address.encode('utf8', 'replace')
507 506 # construct the url
508 507 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
509 508 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
510 509
511 510 return gravatar_url
512 511
513 512
514 513 #==============================================================================
515 514 # REPO PAGER, PAGER FOR REPOSITORY
516 515 #==============================================================================
517 516 class RepoPage(Page):
518 517
519 518 def __init__(self, collection, page=1, items_per_page=20,
520 519 item_count=None, url=None, branch_name=None, **kwargs):
521 520
522 521 """Create a "RepoPage" instance. special pager for paging
523 522 repository
524 523 """
525 524 self._url_generator = url
526 525
527 526 # Safe the kwargs class-wide so they can be used in the pager() method
528 527 self.kwargs = kwargs
529 528
530 529 # Save a reference to the collection
531 530 self.original_collection = collection
532 531
533 532 self.collection = collection
534 533
535 534 # The self.page is the number of the current page.
536 535 # The first page has the number 1!
537 536 try:
538 537 self.page = int(page) # make it int() if we get it as a string
539 538 except (ValueError, TypeError):
540 539 self.page = 1
541 540
542 541 self.items_per_page = items_per_page
543 542
544 543 # Unless the user tells us how many items the collections has
545 544 # we calculate that ourselves.
546 545 if item_count is not None:
547 546 self.item_count = item_count
548 547 else:
549 548 self.item_count = len(self.collection)
550 549
551 550 # Compute the number of the first and last available page
552 551 if self.item_count > 0:
553 552 self.first_page = 1
554 553 self.page_count = ((self.item_count - 1) / self.items_per_page) + 1
555 554 self.last_page = self.first_page + self.page_count - 1
556 555
557 556 # Make sure that the requested page number is the range of valid pages
558 557 if self.page > self.last_page:
559 558 self.page = self.last_page
560 559 elif self.page < self.first_page:
561 560 self.page = self.first_page
562 561
563 562 # Note: the number of items on this page can be less than
564 563 # items_per_page if the last page is not full
565 564 self.first_item = max(0, (self.item_count) - (self.page * items_per_page))
566 565 self.last_item = ((self.item_count - 1) - items_per_page * (self.page - 1))
567 566
568 567 iterator = self.collection.get_changesets(start=self.first_item,
569 568 end=self.last_item,
570 569 reverse=True,
571 570 branch_name=branch_name)
572 571 self.items = list(iterator)
573 572
574 573 # Links to previous and next page
575 574 if self.page > self.first_page:
576 575 self.previous_page = self.page - 1
577 576 else:
578 577 self.previous_page = None
579 578
580 579 if self.page < self.last_page:
581 580 self.next_page = self.page + 1
582 581 else:
583 582 self.next_page = None
584 583
585 584 # No items available
586 585 else:
587 586 self.first_page = None
588 587 self.page_count = 0
589 588 self.last_page = None
590 589 self.first_item = None
591 590 self.last_item = None
592 591 self.previous_page = None
593 592 self.next_page = None
594 593 self.items = []
595 594
596 595 # This is a subclass of the 'list' type. Initialise the list now.
597 596 list.__init__(self, self.items)
598 597
599 598
600 599 def changed_tooltip(nodes):
601 600 """
602 601 Generates a html string for changed nodes in changeset page.
603 602 It limits the output to 30 entries
604 603
605 604 :param nodes: LazyNodesGenerator
606 605 """
607 606 if nodes:
608 607 pref = ': <br/> '
609 608 suf = ''
610 609 if len(nodes) > 30:
611 610 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
612 611 return literal(pref + '<br/> '.join([safe_unicode(x.path)
613 612 for x in nodes[:30]]) + suf)
614 613 else:
615 614 return ': ' + _('No Files')
616 615
617 616
618 617
619 618 def repo_link(groups_and_repos):
620 619 """
621 620 Makes a breadcrumbs link to repo within a group
622 621 joins &raquo; on each group to create a fancy link
623 622
624 623 ex::
625 624 group >> subgroup >> repo
626 625
627 626 :param groups_and_repos:
628 627 """
629 628 groups, repo_name = groups_and_repos
630 629
631 630 if not groups:
632 631 return repo_name
633 632 else:
634 633 def make_link(group):
635 634 return link_to(group.group_name, url('repos_group',
636 635 id=group.group_id))
637 636 return literal(' &raquo; '.join(map(make_link, groups)) + \
638 637 " &raquo; " + repo_name)
639 638
640 639
641 640 def fancy_file_stats(stats):
642 641 """
643 642 Displays a fancy two colored bar for number of added/deleted
644 643 lines of code on file
645 644
646 645 :param stats: two element list of added/deleted lines of code
647 646 """
648 647
649 648 a, d, t = stats[0], stats[1], stats[0] + stats[1]
650 649 width = 100
651 650 unit = float(width) / (t or 1)
652 651
653 652 # needs > 9% of width to be visible or 0 to be hidden
654 653 a_p = max(9, unit * a) if a > 0 else 0
655 654 d_p = max(9, unit * d) if d > 0 else 0
656 655 p_sum = a_p + d_p
657 656
658 657 if p_sum > width:
659 658 #adjust the percentage to be == 100% since we adjusted to 9
660 659 if a_p > d_p:
661 660 a_p = a_p - (p_sum - width)
662 661 else:
663 662 d_p = d_p - (p_sum - width)
664 663
665 664 a_v = a if a > 0 else ''
666 665 d_v = d if d > 0 else ''
667 666
668 667
669 668 def cgen(l_type):
670 669 mapping = {'tr':'top-right-rounded-corner',
671 670 'tl':'top-left-rounded-corner',
672 671 'br':'bottom-right-rounded-corner',
673 672 'bl':'bottom-left-rounded-corner'}
674 673 map_getter = lambda x:mapping[x]
675 674
676 675 if l_type == 'a' and d_v:
677 676 #case when added and deleted are present
678 677 return ' '.join(map(map_getter, ['tl', 'bl']))
679 678
680 679 if l_type == 'a' and not d_v:
681 680 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
682 681
683 682 if l_type == 'd' and a_v:
684 683 return ' '.join(map(map_getter, ['tr', 'br']))
685 684
686 685 if l_type == 'd' and not a_v:
687 686 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
688 687
689 688
690 689
691 690 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
692 691 a_p, a_v)
693 692 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
694 693 d_p, d_v)
695 694 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
@@ -1,607 +1,607 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Utilities library for RhodeCode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 import paste
31 31 import beaker
32 32 from os.path import dirname as dn, join as jn
33 33
34 34 from paste.script.command import Command, BadCommand
35 35
36 36 from UserDict import DictMixin
37 37
38 38 from mercurial import ui, config, hg
39 39 from mercurial.error import RepoError
40 40
41 41 from webhelpers.text import collapse, remove_formatting, strip_tags
42 42
43 43 from vcs.backends.base import BaseChangeset
44 44 from vcs.utils.lazy import LazyProperty
45 45
46 46 from rhodecode.model import meta
47 47 from rhodecode.model.caching_query import FromCache
48 48 from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, Group, \
49 49 RhodeCodeSettings
50 50 from rhodecode.model.repo import RepoModel
51 51 from rhodecode.model.user import UserModel
52 52
53 53 log = logging.getLogger(__name__)
54 54
55 55
56 56 def recursive_replace(str, replace=' '):
57 57 """Recursive replace of given sign to just one instance
58 58
59 59 :param str: given string
60 60 :param replace: char to find and replace multiple instances
61 61
62 62 Examples::
63 63 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
64 64 'Mighty-Mighty-Bo-sstones'
65 65 """
66 66
67 67 if str.find(replace * 2) == -1:
68 68 return str
69 69 else:
70 70 str = str.replace(replace * 2, replace)
71 71 return recursive_replace(str, replace)
72 72
73 73
74 74 def repo_name_slug(value):
75 75 """Return slug of name of repository
76 76 This function is called on each creation/modification
77 77 of repository to prevent bad names in repo
78 78 """
79 79
80 80 slug = remove_formatting(value)
81 81 slug = strip_tags(slug)
82 82
83 83 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
84 84 slug = slug.replace(c, '-')
85 85 slug = recursive_replace(slug, '-')
86 86 slug = collapse(slug, '-')
87 87 return slug
88 88
89 89
90 90 def get_repo_slug(request):
91 91 return request.environ['pylons.routes_dict'].get('repo_name')
92 92
93 93
94 94 def action_logger(user, action, repo, ipaddr='', sa=None):
95 95 """
96 96 Action logger for various actions made by users
97 97
98 98 :param user: user that made this action, can be a unique username string or
99 99 object containing user_id attribute
100 100 :param action: action to log, should be on of predefined unique actions for
101 101 easy translations
102 102 :param repo: string name of repository or object containing repo_id,
103 103 that action was made on
104 104 :param ipaddr: optional ip address from what the action was made
105 105 :param sa: optional sqlalchemy session
106 106
107 107 """
108 108
109 109 if not sa:
110 110 sa = meta.Session()
111 111
112 112 try:
113 113 um = UserModel()
114 114 if hasattr(user, 'user_id'):
115 115 user_obj = user
116 116 elif isinstance(user, basestring):
117 117 user_obj = um.get_by_username(user, cache=False)
118 118 else:
119 119 raise Exception('You have to provide user object or username')
120 120
121 121 rm = RepoModel()
122 122 if hasattr(repo, 'repo_id'):
123 123 repo_obj = rm.get(repo.repo_id, cache=False)
124 124 repo_name = repo_obj.repo_name
125 125 elif isinstance(repo, basestring):
126 126 repo_name = repo.lstrip('/')
127 127 repo_obj = rm.get_by_repo_name(repo_name, cache=False)
128 128 else:
129 129 raise Exception('You have to provide repository to action logger')
130 130
131 131 user_log = UserLog()
132 132 user_log.user_id = user_obj.user_id
133 133 user_log.action = action
134 134
135 135 user_log.repository_id = repo_obj.repo_id
136 136 user_log.repository_name = repo_name
137 137
138 138 user_log.action_date = datetime.datetime.now()
139 139 user_log.user_ip = ipaddr
140 140 sa.add(user_log)
141 141 sa.commit()
142 142
143 143 log.info('Adding user %s, action %s on %s', user_obj, action, repo)
144 144 except:
145 145 log.error(traceback.format_exc())
146 146 sa.rollback()
147 147
148 148
149 149 def get_repos(path, recursive=False):
150 150 """
151 151 Scans given path for repos and return (name,(type,path)) tuple
152 152
153 153 :param path: path to scann for repositories
154 154 :param recursive: recursive search and return names with subdirs in front
155 155 """
156 156 from vcs.utils.helpers import get_scm
157 157 from vcs.exceptions import VCSError
158 158
159 159 if path.endswith(os.sep):
160 160 #remove ending slash for better results
161 161 path = path[:-1]
162 162
163 163 def _get_repos(p):
164 164 if not os.access(p, os.W_OK):
165 165 return
166 166 for dirpath in os.listdir(p):
167 167 if os.path.isfile(os.path.join(p, dirpath)):
168 168 continue
169 169 cur_path = os.path.join(p, dirpath)
170 170 try:
171 171 scm_info = get_scm(cur_path)
172 172 yield scm_info[1].split(path)[-1].lstrip(os.sep), scm_info
173 173 except VCSError:
174 174 if not recursive:
175 175 continue
176 176 #check if this dir containts other repos for recursive scan
177 177 rec_path = os.path.join(p, dirpath)
178 178 if os.path.isdir(rec_path):
179 179 for inner_scm in _get_repos(rec_path):
180 180 yield inner_scm
181 181
182 182 return _get_repos(path)
183 183
184 184
185 185 def check_repo_fast(repo_name, base_path):
186 186 """
187 187 Check given path for existence of directory
188 188 :param repo_name:
189 189 :param base_path:
190 190
191 191 :return False: if this directory is present
192 192 """
193 193 if os.path.isdir(os.path.join(base_path, repo_name)):
194 194 return False
195 195 return True
196 196
197 197
198 198 def check_repo(repo_name, base_path, verify=True):
199 199
200 200 repo_path = os.path.join(base_path, repo_name)
201 201
202 202 try:
203 203 if not check_repo_fast(repo_name, base_path):
204 204 return False
205 205 r = hg.repository(ui.ui(), repo_path)
206 206 if verify:
207 207 hg.verify(r)
208 208 #here we hnow that repo exists it was verified
209 209 log.info('%s repo is already created', repo_name)
210 210 return False
211 211 except RepoError:
212 212 #it means that there is no valid repo there...
213 213 log.info('%s repo is free for creation', repo_name)
214 214 return True
215 215
216 216
217 217 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
218 218 while True:
219 219 ok = raw_input(prompt)
220 220 if ok in ('y', 'ye', 'yes'):
221 221 return True
222 222 if ok in ('n', 'no', 'nop', 'nope'):
223 223 return False
224 224 retries = retries - 1
225 225 if retries < 0:
226 226 raise IOError
227 227 print complaint
228 228
229 229 #propagated from mercurial documentation
230 230 ui_sections = ['alias', 'auth',
231 231 'decode/encode', 'defaults',
232 232 'diff', 'email',
233 233 'extensions', 'format',
234 234 'merge-patterns', 'merge-tools',
235 235 'hooks', 'http_proxy',
236 236 'smtp', 'patch',
237 237 'paths', 'profiling',
238 238 'server', 'trusted',
239 239 'ui', 'web', ]
240 240
241 241
242 242 def make_ui(read_from='file', path=None, checkpaths=True):
243 243 """A function that will read python rc files or database
244 244 and make an mercurial ui object from read options
245 245
246 246 :param path: path to mercurial config file
247 247 :param checkpaths: check the path
248 248 :param read_from: read from 'file' or 'db'
249 249 """
250 250
251 251 baseui = ui.ui()
252 252
253 253 #clean the baseui object
254 254 baseui._ocfg = config.config()
255 255 baseui._ucfg = config.config()
256 256 baseui._tcfg = config.config()
257 257
258 258 if read_from == 'file':
259 259 if not os.path.isfile(path):
260 260 log.warning('Unable to read config file %s' % path)
261 261 return False
262 262 log.debug('reading hgrc from %s', path)
263 263 cfg = config.config()
264 264 cfg.read(path)
265 265 for section in ui_sections:
266 266 for k, v in cfg.items(section):
267 267 log.debug('settings ui from file[%s]%s:%s', section, k, v)
268 268 baseui.setconfig(section, k, v)
269 269
270 270 elif read_from == 'db':
271 271 sa = meta.Session()
272 272 ret = sa.query(RhodeCodeUi)\
273 273 .options(FromCache("sql_cache_short",
274 274 "get_hg_ui_settings")).all()
275 275
276 276 hg_ui = ret
277 277 for ui_ in hg_ui:
278 278 if ui_.ui_active:
279 279 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
280 280 ui_.ui_key, ui_.ui_value)
281 281 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
282 282
283 283 meta.Session.remove()
284 284 return baseui
285 285
286 286
287 287 def set_rhodecode_config(config):
288 288 """Updates pylons config with new settings from database
289 289
290 290 :param config:
291 291 """
292 292 hgsettings = RhodeCodeSettings.get_app_settings()
293 293
294 294 for k, v in hgsettings.items():
295 295 config[k] = v
296 296
297 297
298 298 def invalidate_cache(cache_key, *args):
299 299 """Puts cache invalidation task into db for
300 300 further global cache invalidation
301 301 """
302 302
303 303 from rhodecode.model.scm import ScmModel
304 304
305 305 if cache_key.startswith('get_repo_cached_'):
306 306 name = cache_key.split('get_repo_cached_')[-1]
307 307 ScmModel().mark_for_invalidation(name)
308 308
309 309
310 310 class EmptyChangeset(BaseChangeset):
311 311 """
312 312 An dummy empty changeset. It's possible to pass hash when creating
313 313 an EmptyChangeset
314 314 """
315 315
316 316 def __init__(self, cs='0' * 40, repo=None):
317 317 self._empty_cs = cs
318 318 self.revision = -1
319 319 self.message = ''
320 320 self.author = ''
321 321 self.date = ''
322 322 self.repository = repo
323 323
324 324 @LazyProperty
325 325 def raw_id(self):
326 326 """Returns raw string identifying this changeset, useful for web
327 327 representation.
328 328 """
329 329
330 330 return self._empty_cs
331 331
332 332 @LazyProperty
333 333 def short_id(self):
334 334 return self.raw_id[:12]
335 335
336 336 def get_file_changeset(self, path):
337 337 return self
338 338
339 339 def get_file_content(self, path):
340 340 return u''
341 341
342 342 def get_file_size(self, path):
343 343 return 0
344 344
345 345
346 346 def map_groups(groups):
347 347 """Checks for groups existence, and creates groups structures.
348 348 It returns last group in structure
349 349
350 350 :param groups: list of groups structure
351 351 """
352 352 sa = meta.Session()
353 353
354 354 parent = None
355 355 group = None
356 356 for lvl, group_name in enumerate(groups[:-1]):
357 357 group = sa.query(Group).filter(Group.group_name == group_name).scalar()
358 358
359 359 if group is None:
360 360 group = Group(group_name, parent)
361 361 sa.add(group)
362 362 sa.commit()
363 363
364 364 parent = group
365 365
366 366 return group
367 367
368 368
369 369 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
370 370 """maps all repos given in initial_repo_list, non existing repositories
371 371 are created, if remove_obsolete is True it also check for db entries
372 372 that are not in initial_repo_list and removes them.
373 373
374 374 :param initial_repo_list: list of repositories found by scanning methods
375 375 :param remove_obsolete: check for obsolete entries in database
376 376 """
377 377
378 378 sa = meta.Session()
379 379 rm = RepoModel()
380 380 user = sa.query(User).filter(User.admin == True).first()
381 381 added = []
382 382 for name, repo in initial_repo_list.items():
383 383 group = map_groups(name.split('/'))
384 384 if not rm.get_by_repo_name(name, cache=False):
385 385 log.info('repository %s not found creating default', name)
386 386 added.append(name)
387 387 form_data = {
388 388 'repo_name': name,
389 389 'repo_name_full': name,
390 390 'repo_type': repo.alias,
391 391 'description': repo.description \
392 392 if repo.description != 'unknown' else \
393 393 '%s repository' % name,
394 394 'private': False,
395 395 'group_id': getattr(group, 'group_id', None)
396 396 }
397 397 rm.create(form_data, user, just_db=True)
398 398
399 399 removed = []
400 400 if remove_obsolete:
401 401 #remove from database those repositories that are not in the filesystem
402 402 for repo in sa.query(Repository).all():
403 403 if repo.repo_name not in initial_repo_list.keys():
404 404 removed.append(repo.repo_name)
405 405 sa.delete(repo)
406 406 sa.commit()
407 407
408 408 return added, removed
409 409
410 410 #set cache regions for beaker so celery can utilise it
411 411 def add_cache(settings):
412 412 cache_settings = {'regions': None}
413 413 for key in settings.keys():
414 414 for prefix in ['beaker.cache.', 'cache.']:
415 415 if key.startswith(prefix):
416 416 name = key.split(prefix)[1].strip()
417 417 cache_settings[name] = settings[key].strip()
418 418 if cache_settings['regions']:
419 419 for region in cache_settings['regions'].split(','):
420 420 region = region.strip()
421 421 region_settings = {}
422 422 for key, value in cache_settings.items():
423 423 if key.startswith(region):
424 424 region_settings[key.split('.')[1]] = value
425 425 region_settings['expire'] = int(region_settings.get('expire',
426 426 60))
427 427 region_settings.setdefault('lock_dir',
428 428 cache_settings.get('lock_dir'))
429 429 region_settings.setdefault('data_dir',
430 430 cache_settings.get('data_dir'))
431 431
432 432 if 'type' not in region_settings:
433 433 region_settings['type'] = cache_settings.get('type',
434 434 'memory')
435 435 beaker.cache.cache_regions[region] = region_settings
436 436
437 437
438 438 def get_current_revision():
439 439 """Returns tuple of (number, id) from repository containing this package
440 440 or None if repository could not be found.
441 441 """
442 442
443 443 try:
444 444 from vcs import get_repo
445 445 from vcs.utils.helpers import get_scm
446 446 from vcs.exceptions import RepositoryError, VCSError
447 447 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
448 448 scm = get_scm(repopath)[0]
449 449 repo = get_repo(path=repopath, alias=scm)
450 450 tip = repo.get_changeset()
451 451 return (tip.revision, tip.short_id)
452 452 except (ImportError, RepositoryError, VCSError), err:
453 453 logging.debug("Cannot retrieve rhodecode's revision. Original error "
454 454 "was: %s" % err)
455 455 return None
456 456
457 457
458 458 #==============================================================================
459 459 # TEST FUNCTIONS AND CREATORS
460 460 #==============================================================================
461 461 def create_test_index(repo_location, full_index):
462 462 """Makes default test index
463 463 :param repo_location:
464 464 :param full_index:
465 465 """
466 466 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
467 467 from rhodecode.lib.pidlock import DaemonLock, LockHeld
468 468 import shutil
469 469
470 470 index_location = os.path.join(repo_location, 'index')
471 471 if os.path.exists(index_location):
472 472 shutil.rmtree(index_location)
473 473
474 474 try:
475 l = DaemonLock(file=jn(dn(dn(index_location)), 'make_index.lock'))
475 l = DaemonLock(file=jn(dn(index_location), 'make_index.lock'))
476 476 WhooshIndexingDaemon(index_location=index_location,
477 477 repo_location=repo_location)\
478 478 .run(full_index=full_index)
479 479 l.release()
480 480 except LockHeld:
481 481 pass
482 482
483 483
484 484 def create_test_env(repos_test_path, config):
485 485 """Makes a fresh database and
486 486 install test repository into tmp dir
487 487 """
488 488 from rhodecode.lib.db_manage import DbManage
489 489 from rhodecode.tests import HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, \
490 490 HG_FORK, GIT_FORK, TESTS_TMP_PATH
491 491 import tarfile
492 492 import shutil
493 493 from os.path import dirname as dn, join as jn, abspath
494 494
495 495 log = logging.getLogger('TestEnvCreator')
496 496 # create logger
497 497 log.setLevel(logging.DEBUG)
498 498 log.propagate = True
499 499 # create console handler and set level to debug
500 500 ch = logging.StreamHandler()
501 501 ch.setLevel(logging.DEBUG)
502 502
503 503 # create formatter
504 504 formatter = logging.Formatter("%(asctime)s - %(name)s -"
505 505 " %(levelname)s - %(message)s")
506 506
507 507 # add formatter to ch
508 508 ch.setFormatter(formatter)
509 509
510 510 # add ch to logger
511 511 log.addHandler(ch)
512 512
513 513 #PART ONE create db
514 514 dbconf = config['sqlalchemy.db1.url']
515 515 log.debug('making test db %s', dbconf)
516 516
517 517 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
518 518 tests=True)
519 519 dbmanage.create_tables(override=True)
520 520 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
521 521 dbmanage.create_default_user()
522 522 dbmanage.admin_prompt()
523 523 dbmanage.create_permissions()
524 524 dbmanage.populate_default_permissions()
525 525
526 526 #PART TWO make test repo
527 527 log.debug('making test vcs repositories')
528 528
529 529 #remove old one from previos tests
530 530 for r in [HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, HG_FORK, GIT_FORK]:
531 531
532 532 if os.path.isdir(jn(TESTS_TMP_PATH, r)):
533 533 log.debug('removing %s', r)
534 534 shutil.rmtree(jn(TESTS_TMP_PATH, r))
535 535
536 536 #CREATE DEFAULT HG REPOSITORY
537 537 cur_dir = dn(dn(abspath(__file__)))
538 538 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
539 539 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
540 540 tar.close()
541 541
542 542
543 543 #==============================================================================
544 544 # PASTER COMMANDS
545 545 #==============================================================================
546 546 class BasePasterCommand(Command):
547 547 """
548 548 Abstract Base Class for paster commands.
549 549
550 550 The celery commands are somewhat aggressive about loading
551 551 celery.conf, and since our module sets the `CELERY_LOADER`
552 552 environment variable to our loader, we have to bootstrap a bit and
553 553 make sure we've had a chance to load the pylons config off of the
554 554 command line, otherwise everything fails.
555 555 """
556 556 min_args = 1
557 557 min_args_error = "Please provide a paster config file as an argument."
558 558 takes_config_file = 1
559 559 requires_config_file = True
560 560
561 561 def notify_msg(self, msg, log=False):
562 562 """Make a notification to user, additionally if logger is passed
563 563 it logs this action using given logger
564 564
565 565 :param msg: message that will be printed to user
566 566 :param log: logging instance, to use to additionally log this message
567 567
568 568 """
569 569 if log and isinstance(log, logging):
570 570 log(msg)
571 571
572 572 def run(self, args):
573 573 """
574 574 Overrides Command.run
575 575
576 576 Checks for a config file argument and loads it.
577 577 """
578 578 if len(args) < self.min_args:
579 579 raise BadCommand(
580 580 self.min_args_error % {'min_args': self.min_args,
581 581 'actual_args': len(args)})
582 582
583 583 # Decrement because we're going to lob off the first argument.
584 584 # @@ This is hacky
585 585 self.min_args -= 1
586 586 self.bootstrap_config(args[0])
587 587 self.update_parser()
588 588 return super(BasePasterCommand, self).run(args[1:])
589 589
590 590 def update_parser(self):
591 591 """
592 592 Abstract method. Allows for the class's parser to be updated
593 593 before the superclass's `run` method is called. Necessary to
594 594 allow options/arguments to be passed through to the underlying
595 595 celery command.
596 596 """
597 597 raise NotImplementedError("Abstract Method.")
598 598
599 599 def bootstrap_config(self, conf):
600 600 """
601 601 Loads the pylons configuration.
602 602 """
603 603 from pylons import config as pylonsconfig
604 604
605 605 path_to_ini_file = os.path.realpath(conf)
606 606 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
607 607 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
@@ -1,566 +1,703 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 import traceback
29 30 from datetime import date
30 31
31 32 from sqlalchemy import *
32 33 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.orm import relationship, backref
34 from sqlalchemy.orm import relationship, backref, joinedload
34 35 from sqlalchemy.orm.interfaces import MapperExtension
35 36
37 from beaker.cache import cache_region, region_invalidate
38
39
40 from vcs import get_backend
41 from vcs.utils.helpers import get_scm
42 from vcs.exceptions import RepositoryError, VCSError
43 from vcs.utils.lazy import LazyProperty
44 from vcs.nodes import FileNode
45
36 46 from rhodecode.lib import str2bool
37 47 from rhodecode.model.meta import Base, Session
38 48 from rhodecode.model.caching_query import FromCache
39 49
40 50 log = logging.getLogger(__name__)
41 51
42 52 #==============================================================================
43 53 # MAPPER EXTENSIONS
44 54 #==============================================================================
45 55
46 56 class RepositoryMapper(MapperExtension):
47 57 def after_update(self, mapper, connection, instance):
48 58 pass
49 59
50 60
51 61 class RhodeCodeSettings(Base):
52 62 __tablename__ = 'rhodecode_settings'
53 63 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
54 64 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
55 65 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
56 66 app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
57 67
58 68 def __init__(self, k='', v=''):
59 69 self.app_settings_name = k
60 70 self.app_settings_value = v
61 71
62 72 def __repr__(self):
63 73 return "<%s('%s:%s')>" % (self.__class__.__name__,
64 74 self.app_settings_name, self.app_settings_value)
65 75
66 76
67 77 @classmethod
68 78 def get_by_name(cls, ldap_key):
69 79 return Session.query(cls)\
70 80 .filter(cls.app_settings_name == ldap_key).scalar()
71 81
72 82 @classmethod
73 83 def get_app_settings(cls, cache=False):
74 84
75 85 ret = Session.query(cls)
76 86
77 87 if cache:
78 88 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
79 89
80 90 if not ret:
81 91 raise Exception('Could not get application settings !')
82 92 settings = {}
83 93 for each in ret:
84 94 settings['rhodecode_' + each.app_settings_name] = \
85 95 each.app_settings_value
86 96
87 97 return settings
88 98
89 99 @classmethod
90 100 def get_ldap_settings(cls, cache=False):
91 101 ret = Session.query(cls)\
92 102 .filter(cls.app_settings_name.startswith('ldap_'))\
93 103 .all()
94 104 fd = {}
95 105 for row in ret:
96 106 fd.update({row.app_settings_name:row.app_settings_value})
97 107 return fd
98 108
99 109
100 110 class RhodeCodeUi(Base):
101 111 __tablename__ = 'rhodecode_ui'
102 112 __table_args__ = {'extend_existing':True}
103 113 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
104 114 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
105 115 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
106 116 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
107 117 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
108 118
109 119
110 120 @classmethod
111 121 def get_by_key(cls, key):
112 122 return Session.query(cls).filter(cls.ui_key == key)
113 123
114 124
115 125 class User(Base):
116 126 __tablename__ = 'users'
117 127 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
118 128 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
119 129 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
120 130 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
121 131 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
122 132 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
123 133 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
124 134 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
125 135 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
126 136 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
127 137 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
128 138 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
129 139
130 140 user_log = relationship('UserLog', cascade='all')
131 141 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
132 142
133 143 repositories = relationship('Repository')
134 144 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
135 145 repo_to_perm = relationship('RepoToPerm', primaryjoin='RepoToPerm.user_id==User.user_id', cascade='all')
136 146
137 147 group_member = relationship('UsersGroupMember', cascade='all')
138 148
139 149 @property
140 150 def full_contact(self):
141 151 return '%s %s <%s>' % (self.name, self.lastname, self.email)
142 152
143 153 @property
144 154 def short_contact(self):
145 155 return '%s %s' % (self.name, self.lastname)
146 156
147 157
148 158 @property
149 159 def is_admin(self):
150 160 return self.admin
151 161
152 162 def __repr__(self):
163 return 'ahmmm'
153 164 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
154 165 self.user_id, self.username)
155 166
156 167 @classmethod
157 168 def by_username(cls, username):
158 169 return Session.query(cls).filter(cls.username == username).one()
159 170
160 171
161 172 def update_lastlogin(self):
162 173 """Update user lastlogin"""
163 174
164 175 try:
165 176 session = Session.object_session(self)
166 177 self.last_login = datetime.datetime.now()
167 178 session.add(self)
168 179 session.commit()
169 180 log.debug('updated user %s lastlogin', self.username)
170 181 except (DatabaseError,):
171 182 session.rollback()
172 183
173 184
174 185 class UserLog(Base):
175 186 __tablename__ = 'user_logs'
176 187 __table_args__ = {'extend_existing':True}
177 188 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
178 189 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
179 190 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
180 191 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
181 192 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
182 193 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
183 194 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
184 195
185 196 @property
186 197 def action_as_day(self):
187 198 return date(*self.action_date.timetuple()[:3])
188 199
189 200 user = relationship('User')
190 201 repository = relationship('Repository')
191 202
192 203
193 204 class UsersGroup(Base):
194 205 __tablename__ = 'users_groups'
195 206 __table_args__ = {'extend_existing':True}
196 207
197 208 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
198 209 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
199 210 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
200 211
201 212 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
202 213
203 214
204 215 @classmethod
205 216 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
206 217 if case_insensitive:
207 218 gr = Session.query(cls)\
208 219 .filter(cls.users_group_name.ilike(group_name))
209 220 else:
210 221 gr = Session.query(UsersGroup)\
211 222 .filter(UsersGroup.users_group_name == group_name)
212 223 if cache:
213 224 gr = gr.options(FromCache("sql_cache_short",
214 225 "get_user_%s" % group_name))
215 226 return gr.scalar()
216 227
217 228 class UsersGroupMember(Base):
218 229 __tablename__ = 'users_groups_members'
219 230 __table_args__ = {'extend_existing':True}
220 231
221 232 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
222 233 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
223 234 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
224 235
225 236 user = relationship('User', lazy='joined')
226 237 users_group = relationship('UsersGroup')
227 238
228 239 def __init__(self, gr_id='', u_id=''):
229 240 self.users_group_id = gr_id
230 241 self.user_id = u_id
231 242
232 243 class Repository(Base):
233 244 __tablename__ = 'repositories'
234 245 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
235 246 __mapper_args__ = {'extension':RepositoryMapper()}
236 247
237 248 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
238 249 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
239 250 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
240 251 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
241 252 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
242 253 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
243 254 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
244 255 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
245 256 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
246 257 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
247 258
248 259 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
249 260 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
250 261
251 262
252 263 user = relationship('User')
253 264 fork = relationship('Repository', remote_side=repo_id)
254 265 group = relationship('Group')
255 266 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
256 267 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
257 268 stats = relationship('Statistics', cascade='all', uselist=False)
258 269
259 270 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
260 271
261 272 logs = relationship('UserLog', cascade='all')
262 273
263 274 def __repr__(self):
264 275 return "<%s('%s:%s')>" % (self.__class__.__name__,
265 276 self.repo_id, self.repo_name)
266 277
267 278 @classmethod
268 279 def by_repo_name(cls, repo_name):
269 return Session.query(cls).filter(cls.repo_name == repo_name).one()
280 q = Session.query(cls).filter(cls.repo_name == repo_name)
270 281
282 q = q.options(joinedload(Repository.fork))\
283 .options(joinedload(Repository.user))\
284 .options(joinedload(Repository.group))\
285
286 return q.one()
271 287
272 288 @classmethod
273 289 def get_repo_forks(cls, repo_id):
274 290 return Session.query(cls).filter(Repository.fork_id == repo_id)
275 291
276 292 @property
277 293 def just_name(self):
278 294 return self.repo_name.split(os.sep)[-1]
279 295
280 296 @property
281 297 def groups_with_parents(self):
282 298 groups = []
283 299 if self.group is None:
284 300 return groups
285 301
286 302 cur_gr = self.group
287 303 groups.insert(0, cur_gr)
288 304 while 1:
289 305 gr = getattr(cur_gr, 'parent_group', None)
290 306 cur_gr = cur_gr.parent_group
291 307 if gr is None:
292 308 break
293 309 groups.insert(0, gr)
294 310
295 311 return groups
296 312
297 313 @property
298 314 def groups_and_repo(self):
299 315 return self.groups_with_parents, self.just_name
300 316
317 @LazyProperty
318 def repo_path(self):
319 """
320 Returns base full path for that repository means where it actually
321 exists on a filesystem
322 """
323
324 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
325 return q.ui_value
326
327 @property
328 def repo_full_path(self):
329 p = [self.repo_path]
330 # we need to split the name by / since this is how we store the
331 # names in the database, but that eventually needs to be converted
332 # into a valid system path
333 p += self.repo_name.split('/')
334 return os.path.join(*p)
335
336 @property
337 def _ui(self):
338 """
339 Creates an db based ui object for this repository
340 """
341 from mercurial import ui
342 from mercurial import config
343 baseui = ui.ui()
344
345 #clean the baseui object
346 baseui._ocfg = config.config()
347 baseui._ucfg = config.config()
348 baseui._tcfg = config.config()
349
350
351 ret = Session.query(RhodeCodeUi)\
352 .options(FromCache("sql_cache_short",
353 "repository_repo_ui")).all()
354
355 hg_ui = ret
356 for ui_ in hg_ui:
357 if ui_.ui_active:
358 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
359 ui_.ui_key, ui_.ui_value)
360 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
361
362 return baseui
363
364 #==========================================================================
365 # SCM CACHE INSTANCE
366 #==========================================================================
367
368 @property
369 def invalidate(self):
370 """
371 Returns Invalidation object if this repo should be invalidated
372 None otherwise. `cache_active = False` means that this cache
373 state is not valid and needs to be invalidated
374 """
375 return Session.query(CacheInvalidation)\
376 .filter(CacheInvalidation.cache_key == self.repo_name)\
377 .filter(CacheInvalidation.cache_active == False)\
378 .scalar()
379
380 @property
381 def set_invalidate(self):
382 """
383 set a cache for invalidation for this instance
384 """
385 inv = Session.query(CacheInvalidation)\
386 .filter(CacheInvalidation.cache_key == self.repo_name)\
387 .scalar()
388
389 if inv is None:
390 inv = CacheInvalidation(self.repo_name)
391 inv.cache_active = True
392 Session.add(inv)
393 Session.commit()
394
395 @property
396 def scm_instance(self):
397 return self.__get_instance(self.repo_name)
398
399 @property
400 def scm_instance_cached(self):
401 @cache_region('long_term')
402 def _c(repo_name):
403 return self.__get_instance(repo_name)
404
405 inv = self.invalidate
406 if inv:
407 region_invalidate(_c, None, self.repo_name)
408 #update our cache
409 inv.cache_key.cache_active = True
410 Session.add(inv)
411 Session.commit()
412
413 return _c(self.repo_name)
414
415 def __get_instance(self, repo_name):
416 try:
417 alias = get_scm(self.repo_full_path)[0]
418 log.debug('Creating instance of %s repository', alias)
419 backend = get_backend(alias)
420 except VCSError:
421 log.error(traceback.format_exc())
422 log.error('Perhaps this repository is in db and not in '
423 'filesystem run rescan repositories with '
424 '"destroy old data " option from admin panel')
425 return
426
427 if alias == 'hg':
428 repo = backend(self.repo_full_path, create=False,
429 baseui=self._ui)
430 #skip hidden web repository
431 if repo._get_hidden():
432 return
433 else:
434 repo = backend(self.repo_full_path, create=False)
435
436 return repo
437
301 438
302 439 class Group(Base):
303 440 __tablename__ = 'groups'
304 441 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
305 442 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
306 443 __mapper_args__ = {'order_by':'group_name'}
307 444
308 445 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
309 446 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
310 447 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
311 448 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
312 449
313 450 parent_group = relationship('Group', remote_side=group_id)
314 451
315 452
316 453 def __init__(self, group_name='', parent_group=None):
317 454 self.group_name = group_name
318 455 self.parent_group = parent_group
319 456
320 457 def __repr__(self):
321 458 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
322 459 self.group_name)
323 460
324 461 @classmethod
325 462 def url_sep(cls):
326 463 return '/'
327 464
328 465 @property
329 466 def parents(self):
330 467 parents_limit = 5
331 468 groups = []
332 469 if self.parent_group is None:
333 470 return groups
334 471 cur_gr = self.parent_group
335 472 groups.insert(0, cur_gr)
336 473 cnt = 0
337 474 while 1:
338 475 cnt += 1
339 476 gr = getattr(cur_gr, 'parent_group', None)
340 477 cur_gr = cur_gr.parent_group
341 478 if gr is None:
342 479 break
343 480 if cnt == parents_limit:
344 481 # this will prevent accidental infinit loops
345 482 log.error('group nested more than %s' %
346 483 parents_limit)
347 484 break
348 485
349 486 groups.insert(0, gr)
350 487 return groups
351 488
352 489
353 490 @property
354 491 def full_path(self):
355 492 return Group.url_sep().join([g.group_name for g in self.parents] +
356 493 [self.group_name])
357 494
358 495 @property
359 496 def repositories(self):
360 497 return Session.query(Repository).filter(Repository.group == self)
361 498
362 499 class Permission(Base):
363 500 __tablename__ = 'permissions'
364 501 __table_args__ = {'extend_existing':True}
365 502 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
366 503 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
367 504 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
368 505
369 506 def __repr__(self):
370 507 return "<%s('%s:%s')>" % (self.__class__.__name__,
371 508 self.permission_id, self.permission_name)
372 509
373 510 @classmethod
374 511 def get_by_key(cls, key):
375 512 return Session.query(cls).filter(cls.permission_name == key).scalar()
376 513
377 514 class RepoToPerm(Base):
378 515 __tablename__ = 'repo_to_perm'
379 516 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
380 517 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
381 518 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
382 519 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
383 520 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
384 521
385 522 user = relationship('User')
386 523 permission = relationship('Permission')
387 524 repository = relationship('Repository')
388 525
389 526 class UserToPerm(Base):
390 527 __tablename__ = 'user_to_perm'
391 528 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
392 529 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
393 530 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
394 531 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
395 532
396 533 user = relationship('User')
397 534 permission = relationship('Permission')
398 535
399 536 @classmethod
400 537 def has_perm(cls, user_id, perm):
401 538 if not isinstance(perm, Permission):
402 539 raise Exception('perm needs to be an instance of Permission class')
403 540
404 541 return Session.query(cls).filter(cls.user_id == user_id)\
405 542 .filter(cls.permission == perm).scalar() is not None
406 543
407 544 @classmethod
408 545 def grant_perm(cls, user_id, perm):
409 546 if not isinstance(perm, Permission):
410 547 raise Exception('perm needs to be an instance of Permission class')
411 548
412 549 new = cls()
413 550 new.user_id = user_id
414 551 new.permission = perm
415 552 try:
416 553 Session.add(new)
417 554 Session.commit()
418 555 except:
419 556 Session.rollback()
420 557
421 558
422 559 @classmethod
423 560 def revoke_perm(cls, user_id, perm):
424 561 if not isinstance(perm, Permission):
425 562 raise Exception('perm needs to be an instance of Permission class')
426 563
427 564 try:
428 565 Session.query(cls).filter(cls.user_id == user_id)\
429 566 .filter(cls.permission == perm).delete()
430 567 Session.commit()
431 568 except:
432 569 Session.rollback()
433 570
434 571 class UsersGroupRepoToPerm(Base):
435 572 __tablename__ = 'users_group_repo_to_perm'
436 573 __table_args__ = (UniqueConstraint('users_group_id', 'permission_id'), {'extend_existing':True})
437 574 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
438 575 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
439 576 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
440 577 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
441 578
442 579 users_group = relationship('UsersGroup')
443 580 permission = relationship('Permission')
444 581 repository = relationship('Repository')
445 582
446 583
447 584 class UsersGroupToPerm(Base):
448 585 __tablename__ = 'users_group_to_perm'
449 586 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
450 587 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
451 588 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
452 589
453 590 users_group = relationship('UsersGroup')
454 591 permission = relationship('Permission')
455 592
456 593
457 594 @classmethod
458 595 def has_perm(cls, users_group_id, perm):
459 596 if not isinstance(perm, Permission):
460 597 raise Exception('perm needs to be an instance of Permission class')
461 598
462 599 return Session.query(cls).filter(cls.users_group_id ==
463 600 users_group_id)\
464 601 .filter(cls.permission == perm)\
465 602 .scalar() is not None
466 603
467 604 @classmethod
468 605 def grant_perm(cls, users_group_id, perm):
469 606 if not isinstance(perm, Permission):
470 607 raise Exception('perm needs to be an instance of Permission class')
471 608
472 609 new = cls()
473 610 new.users_group_id = users_group_id
474 611 new.permission = perm
475 612 try:
476 613 Session.add(new)
477 614 Session.commit()
478 615 except:
479 616 Session.rollback()
480 617
481 618
482 619 @classmethod
483 620 def revoke_perm(cls, users_group_id, perm):
484 621 if not isinstance(perm, Permission):
485 622 raise Exception('perm needs to be an instance of Permission class')
486 623
487 624 try:
488 625 Session.query(cls).filter(cls.users_group_id == users_group_id)\
489 626 .filter(cls.permission == perm).delete()
490 627 Session.commit()
491 628 except:
492 629 Session.rollback()
493 630
494 631
495 632 class GroupToPerm(Base):
496 633 __tablename__ = 'group_to_perm'
497 634 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
498 635
499 636 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
500 637 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
501 638 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
502 639 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
503 640
504 641 user = relationship('User')
505 642 permission = relationship('Permission')
506 643 group = relationship('Group')
507 644
508 645 class Statistics(Base):
509 646 __tablename__ = 'statistics'
510 647 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
511 648 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
512 649 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
513 650 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
514 651 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
515 652 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
516 653 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
517 654
518 655 repository = relationship('Repository', single_parent=True)
519 656
520 657 class UserFollowing(Base):
521 658 __tablename__ = 'user_followings'
522 659 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
523 660 UniqueConstraint('user_id', 'follows_user_id')
524 661 , {'extend_existing':True})
525 662
526 663 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
527 664 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
528 665 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
529 666 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
530 667 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
531 668
532 669 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
533 670
534 671 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
535 672 follows_repository = relationship('Repository', order_by='Repository.repo_name')
536 673
537 674
538 675
539 676 @classmethod
540 677 def get_repo_followers(cls, repo_id):
541 678 return Session.query(cls).filter(cls.follows_repo_id == repo_id)
542 679
543 680 class CacheInvalidation(Base):
544 681 __tablename__ = 'cache_invalidation'
545 682 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
546 683 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
547 684 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
548 685 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
549 686 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
550 687
551 688
552 689 def __init__(self, cache_key, cache_args=''):
553 690 self.cache_key = cache_key
554 691 self.cache_args = cache_args
555 692 self.cache_active = False
556 693
557 694 def __repr__(self):
558 695 return "<%s('%s:%s')>" % (self.__class__.__name__,
559 696 self.cache_id, self.cache_key)
560 697
561 698 class DbMigrateVersion(Base):
562 699 __tablename__ = 'db_migrate_version'
563 700 __table_args__ = {'extend_existing':True}
564 701 repository_id = Column('repository_id', String(250), primary_key=True)
565 702 repository_path = Column('repository_path', Text)
566 703 version = Column('version', Integer)
@@ -1,657 +1,668 b''
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14 14
15 15
16 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 import os
23 23 import re
24 24 import logging
25 25 import traceback
26 26
27 27 import formencode
28 28 from formencode import All
29 29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
30 30 Email, Bool, StringBoolean, Set
31 31
32 32 from pylons.i18n.translation import _
33 33 from webhelpers.pylonslib.secure_form import authentication_token
34 34
35 35 from rhodecode.lib.utils import repo_name_slug
36 36 from rhodecode.lib.auth import authenticate, get_crypt_password
37 37 from rhodecode.lib.exceptions import LdapImportError
38 38 from rhodecode.model.user import UserModel
39 39 from rhodecode.model.repo import RepoModel
40 40 from rhodecode.model.db import User, UsersGroup, Group
41 41 from rhodecode import BACKENDS
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45 #this is needed to translate the messages using _() in validators
46 46 class State_obj(object):
47 47 _ = staticmethod(_)
48 48
49 49 #==============================================================================
50 50 # VALIDATORS
51 51 #==============================================================================
52 52 class ValidAuthToken(formencode.validators.FancyValidator):
53 53 messages = {'invalid_token':_('Token mismatch')}
54 54
55 55 def validate_python(self, value, state):
56 56
57 57 if value != authentication_token():
58 58 raise formencode.Invalid(self.message('invalid_token', state,
59 59 search_number=value), value, state)
60 60
61 61 def ValidUsername(edit, old_data):
62 62 class _ValidUsername(formencode.validators.FancyValidator):
63 63
64 64 def validate_python(self, value, state):
65 65 if value in ['default', 'new_user']:
66 66 raise formencode.Invalid(_('Invalid username'), value, state)
67 67 #check if user is unique
68 68 old_un = None
69 69 if edit:
70 70 old_un = UserModel().get(old_data.get('user_id')).username
71 71
72 72 if old_un != value or not edit:
73 73 if UserModel().get_by_username(value, cache=False,
74 74 case_insensitive=True):
75 75 raise formencode.Invalid(_('This username already '
76 76 'exists') , value, state)
77 77
78 78 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
79 79 raise formencode.Invalid(_('Username may only contain '
80 80 'alphanumeric characters '
81 81 'underscores, periods or dashes '
82 82 'and must begin with alphanumeric '
83 83 'character'), value, state)
84 84
85 85 return _ValidUsername
86 86
87 87
88 88 def ValidUsersGroup(edit, old_data):
89 89
90 90 class _ValidUsersGroup(formencode.validators.FancyValidator):
91 91
92 92 def validate_python(self, value, state):
93 93 if value in ['default']:
94 94 raise formencode.Invalid(_('Invalid group name'), value, state)
95 95 #check if group is unique
96 96 old_ugname = None
97 97 if edit:
98 98 old_ugname = UsersGroup.get(
99 99 old_data.get('users_group_id')).users_group_name
100 100
101 101 if old_ugname != value or not edit:
102 102 if UsersGroup.get_by_group_name(value, cache=False,
103 103 case_insensitive=True):
104 104 raise formencode.Invalid(_('This users group '
105 105 'already exists') , value,
106 106 state)
107 107
108 108
109 109 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
110 110 raise formencode.Invalid(_('Group name may only contain '
111 111 'alphanumeric characters '
112 112 'underscores, periods or dashes '
113 113 'and must begin with alphanumeric '
114 114 'character'), value, state)
115 115
116 116 return _ValidUsersGroup
117 117
118 118
119 119 def ValidReposGroup(edit, old_data):
120 120 class _ValidReposGroup(formencode.validators.FancyValidator):
121 121
122 122 def validate_python(self, value, state):
123 123 #TODO WRITE VALIDATIONS
124 124 group_name = value.get('group_name')
125 125 group_parent_id = int(value.get('group_parent_id') or - 1)
126 126
127 127 # slugify repo group just in case :)
128 128 slug = repo_name_slug(group_name)
129 129
130 130 # check for parent of self
131 131 if edit and old_data['group_id'] == group_parent_id:
132 132 e_dict = {'group_parent_id':_('Cannot assign this group '
133 133 'as parent')}
134 134 raise formencode.Invalid('', value, state,
135 135 error_dict=e_dict)
136 136
137 137 old_gname = None
138 138 if edit:
139 139 old_gname = Group.get(
140 140 old_data.get('group_id')).group_name
141 141
142 142 if old_gname != group_name or not edit:
143 143 # check filesystem
144 144 gr = Group.query().filter(Group.group_name == slug)\
145 145 .filter(Group.group_parent_id == group_parent_id).scalar()
146 146
147 147 if gr:
148 148 e_dict = {'group_name':_('This group already exists')}
149 149 raise formencode.Invalid('', value, state,
150 150 error_dict=e_dict)
151 151
152 152 return _ValidReposGroup
153 153
154 154 class ValidPassword(formencode.validators.FancyValidator):
155 155
156 156 def to_python(self, value, state):
157 157
158 158 if value:
159 159
160 160 if value.get('password'):
161 161 try:
162 162 value['password'] = get_crypt_password(value['password'])
163 163 except UnicodeEncodeError:
164 164 e_dict = {'password':_('Invalid characters in password')}
165 165 raise formencode.Invalid('', value, state, error_dict=e_dict)
166 166
167 167 if value.get('password_confirmation'):
168 168 try:
169 169 value['password_confirmation'] = \
170 170 get_crypt_password(value['password_confirmation'])
171 171 except UnicodeEncodeError:
172 172 e_dict = {'password_confirmation':_('Invalid characters in password')}
173 173 raise formencode.Invalid('', value, state, error_dict=e_dict)
174 174
175 175 if value.get('new_password'):
176 176 try:
177 177 value['new_password'] = \
178 178 get_crypt_password(value['new_password'])
179 179 except UnicodeEncodeError:
180 180 e_dict = {'new_password':_('Invalid characters in password')}
181 181 raise formencode.Invalid('', value, state, error_dict=e_dict)
182 182
183 183 return value
184 184
185 185 class ValidPasswordsMatch(formencode.validators.FancyValidator):
186 186
187 187 def validate_python(self, value, state):
188 188
189 189 if value['password'] != value['password_confirmation']:
190 190 e_dict = {'password_confirmation':
191 191 _('Password do not match')}
192 192 raise formencode.Invalid('', value, state, error_dict=e_dict)
193 193
194 194 class ValidAuth(formencode.validators.FancyValidator):
195 195 messages = {
196 196 'invalid_password':_('invalid password'),
197 197 'invalid_login':_('invalid user name'),
198 198 'disabled_account':_('Your account is disabled')
199 199
200 200 }
201 201 #error mapping
202 202 e_dict = {'username':messages['invalid_login'],
203 203 'password':messages['invalid_password']}
204 204 e_dict_disable = {'username':messages['disabled_account']}
205 205
206 206 def validate_python(self, value, state):
207 207 password = value['password']
208 208 username = value['username']
209 209 user = UserModel().get_by_username(username)
210 210
211 211 if authenticate(username, password):
212 212 return value
213 213 else:
214 214 if user and user.active is False:
215 215 log.warning('user %s is disabled', username)
216 216 raise formencode.Invalid(self.message('disabled_account',
217 217 state=State_obj),
218 218 value, state,
219 219 error_dict=self.e_dict_disable)
220 220 else:
221 221 log.warning('user %s not authenticated', username)
222 222 raise formencode.Invalid(self.message('invalid_password',
223 223 state=State_obj), value, state,
224 224 error_dict=self.e_dict)
225 225
226 226 class ValidRepoUser(formencode.validators.FancyValidator):
227 227
228 228 def to_python(self, value, state):
229 229 try:
230 230 User.query().filter(User.active == True)\
231 231 .filter(User.username == value).one()
232 232 except Exception:
233 233 raise formencode.Invalid(_('This username is not valid'),
234 234 value, state)
235 235 return value
236 236
237 237 def ValidRepoName(edit, old_data):
238 238 class _ValidRepoName(formencode.validators.FancyValidator):
239 239 def to_python(self, value, state):
240 240
241 241 repo_name = value.get('repo_name')
242 242
243 243 slug = repo_name_slug(repo_name)
244 244 if slug in ['_admin', '']:
245 245 e_dict = {'repo_name': _('This repository name is disallowed')}
246 246 raise formencode.Invalid('', value, state, error_dict=e_dict)
247 247
248 248
249 249 if value.get('repo_group'):
250 250 gr = Group.get(value.get('repo_group'))
251 251 group_path = gr.full_path
252 252 # value needs to be aware of group name in order to check
253 253 # db key This is an actuall just the name to store in the
254 254 # database
255 255 repo_name_full = group_path + Group.url_sep() + repo_name
256 256 else:
257 257 group_path = ''
258 258 repo_name_full = repo_name
259 259
260 260
261 261 value['repo_name_full'] = repo_name_full
262 262 if old_data.get('repo_name') != repo_name_full or not edit:
263 263
264 264 if group_path != '':
265 265 if RepoModel().get_by_repo_name(repo_name_full,):
266 266 e_dict = {'repo_name':_('This repository already '
267 267 'exists in group "%s"') %
268 268 gr.group_name}
269 269 raise formencode.Invalid('', value, state,
270 270 error_dict=e_dict)
271 271
272 272 else:
273 273 if RepoModel().get_by_repo_name(repo_name_full):
274 274 e_dict = {'repo_name':_('This repository '
275 275 'already exists')}
276 276 raise formencode.Invalid('', value, state,
277 277 error_dict=e_dict)
278 278 return value
279 279
280 280
281 281 return _ValidRepoName
282 282
283 def ValidForkName():
284 class _ValidForkName(formencode.validators.FancyValidator):
285 def to_python(self, value, state):
286 return value
287 return _ValidForkName
288
289
283 290 def SlugifyName():
284 291 class _SlugifyName(formencode.validators.FancyValidator):
285 292
286 293 def to_python(self, value, state):
287 294 return repo_name_slug(value)
288 295
289 296 return _SlugifyName
290 297
291 298 def ValidCloneUri():
292 299 from mercurial.httprepo import httprepository, httpsrepository
293 300 from rhodecode.lib.utils import make_ui
294 301
295 302 class _ValidCloneUri(formencode.validators.FancyValidator):
296 303
297 304 def to_python(self, value, state):
298 305 if not value:
299 306 pass
300 307 elif value.startswith('https'):
301 308 try:
302 309 httpsrepository(make_ui('db'), value).capabilities
303 310 except Exception, e:
304 311 log.error(traceback.format_exc())
305 312 raise formencode.Invalid(_('invalid clone url'), value,
306 313 state)
307 314 elif value.startswith('http'):
308 315 try:
309 316 httprepository(make_ui('db'), value).capabilities
310 317 except Exception, e:
311 318 log.error(traceback.format_exc())
312 319 raise formencode.Invalid(_('invalid clone url'), value,
313 320 state)
314 321 else:
315 322 raise formencode.Invalid(_('Invalid clone url, provide a '
316 323 'valid clone http\s url'), value,
317 324 state)
318 325 return value
319 326
320 327 return _ValidCloneUri
321 328
322 329 def ValidForkType(old_data):
323 330 class _ValidForkType(formencode.validators.FancyValidator):
324 331
325 332 def to_python(self, value, state):
326 333 if old_data['repo_type'] != value:
327 334 raise formencode.Invalid(_('Fork have to be the same '
328 335 'type as original'), value, state)
336
329 337 return value
330 338 return _ValidForkType
331 339
332 340 class ValidPerms(formencode.validators.FancyValidator):
333 341 messages = {'perm_new_member_name':_('This username or users group name'
334 342 ' is not valid')}
335 343
336 344 def to_python(self, value, state):
337 345 perms_update = []
338 346 perms_new = []
339 347 #build a list of permission to update and new permission to create
340 348 for k, v in value.items():
341 349 #means new added member to permissions
342 350 if k.startswith('perm_new_member'):
343 351 new_perm = value.get('perm_new_member', False)
344 352 new_member = value.get('perm_new_member_name', False)
345 353 new_type = value.get('perm_new_member_type')
346 354
347 355 if new_member and new_perm:
348 356 if (new_member, new_perm, new_type) not in perms_new:
349 357 perms_new.append((new_member, new_perm, new_type))
350 358 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
351 359 member = k[7:]
352 360 t = {'u':'user',
353 361 'g':'users_group'}[k[0]]
354 362 if member == 'default':
355 363 if value['private']:
356 364 #set none for default when updating to private repo
357 365 v = 'repository.none'
358 366 perms_update.append((member, v, t))
359 367
360 368 value['perms_updates'] = perms_update
361 369 value['perms_new'] = perms_new
362 370
363 371 #update permissions
364 372 for k, v, t in perms_new:
365 373 try:
366 374 if t is 'user':
367 375 self.user_db = User.query()\
368 376 .filter(User.active == True)\
369 377 .filter(User.username == k).one()
370 378 if t is 'users_group':
371 379 self.user_db = UsersGroup.query()\
372 380 .filter(UsersGroup.users_group_active == True)\
373 381 .filter(UsersGroup.users_group_name == k).one()
374 382
375 383 except Exception:
376 384 msg = self.message('perm_new_member_name',
377 385 state=State_obj)
378 386 raise formencode.Invalid(msg, value, state,
379 387 error_dict={'perm_new_member_name':msg})
380 388 return value
381 389
382 390 class ValidSettings(formencode.validators.FancyValidator):
383 391
384 392 def to_python(self, value, state):
385 393 #settings form can't edit user
386 394 if value.has_key('user'):
387 395 del['value']['user']
388 396
389 397 return value
390 398
391 399 class ValidPath(formencode.validators.FancyValidator):
392 400 def to_python(self, value, state):
393 401
394 402 if not os.path.isdir(value):
395 403 msg = _('This is not a valid path')
396 404 raise formencode.Invalid(msg, value, state,
397 405 error_dict={'paths_root_path':msg})
398 406 return value
399 407
400 408 def UniqSystemEmail(old_data):
401 409 class _UniqSystemEmail(formencode.validators.FancyValidator):
402 410 def to_python(self, value, state):
403 411 value = value.lower()
404 412 if old_data.get('email') != value:
405 413 user = User.query().filter(User.email == value).scalar()
406 414 if user:
407 415 raise formencode.Invalid(
408 416 _("This e-mail address is already taken"),
409 417 value, state)
410 418 return value
411 419
412 420 return _UniqSystemEmail
413 421
414 422 class ValidSystemEmail(formencode.validators.FancyValidator):
415 423 def to_python(self, value, state):
416 424 value = value.lower()
417 425 user = User.query().filter(User.email == value).scalar()
418 426 if user is None:
419 427 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
420 428 value, state)
421 429
422 430 return value
423 431
424 432 class LdapLibValidator(formencode.validators.FancyValidator):
425 433
426 434 def to_python(self, value, state):
427 435
428 436 try:
429 437 import ldap
430 438 except ImportError:
431 439 raise LdapImportError
432 440 return value
433 441
434 442 class AttrLoginValidator(formencode.validators.FancyValidator):
435 443
436 444 def to_python(self, value, state):
437 445
438 446 if not value or not isinstance(value, (str, unicode)):
439 447 raise formencode.Invalid(_("The LDAP Login attribute of the CN "
440 448 "must be specified - this is the name "
441 449 "of the attribute that is equivalent "
442 450 "to 'username'"),
443 451 value, state)
444 452
445 453 return value
446 454
447 455 #===============================================================================
448 456 # FORMS
449 457 #===============================================================================
450 458 class LoginForm(formencode.Schema):
451 459 allow_extra_fields = True
452 460 filter_extra_fields = True
453 461 username = UnicodeString(
454 462 strip=True,
455 463 min=1,
456 464 not_empty=True,
457 465 messages={
458 466 'empty':_('Please enter a login'),
459 467 'tooShort':_('Enter a value %(min)i characters long or more')}
460 468 )
461 469
462 470 password = UnicodeString(
463 471 strip=True,
464 472 min=3,
465 473 not_empty=True,
466 474 messages={
467 475 'empty':_('Please enter a password'),
468 476 'tooShort':_('Enter %(min)i characters or more')}
469 477 )
470 478
471 479
472 480 #chained validators have access to all data
473 481 chained_validators = [ValidAuth]
474 482
475 483 def UserForm(edit=False, old_data={}):
476 484 class _UserForm(formencode.Schema):
477 485 allow_extra_fields = True
478 486 filter_extra_fields = True
479 487 username = All(UnicodeString(strip=True, min=1, not_empty=True),
480 488 ValidUsername(edit, old_data))
481 489 if edit:
482 490 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
483 491 admin = StringBoolean(if_missing=False)
484 492 else:
485 493 password = All(UnicodeString(strip=True, min=6, not_empty=True))
486 494 active = StringBoolean(if_missing=False)
487 495 name = UnicodeString(strip=True, min=1, not_empty=True)
488 496 lastname = UnicodeString(strip=True, min=1, not_empty=True)
489 497 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
490 498
491 499 chained_validators = [ValidPassword]
492 500
493 501 return _UserForm
494 502
495 503
496 504 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
497 505 class _UsersGroupForm(formencode.Schema):
498 506 allow_extra_fields = True
499 507 filter_extra_fields = True
500 508
501 509 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
502 510 ValidUsersGroup(edit, old_data))
503 511
504 512 users_group_active = StringBoolean(if_missing=False)
505 513
506 514 if edit:
507 515 users_group_members = OneOf(available_members, hideList=False,
508 516 testValueList=True,
509 517 if_missing=None, not_empty=False)
510 518
511 519 return _UsersGroupForm
512 520
513 521 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
514 522 class _ReposGroupForm(formencode.Schema):
515 523 allow_extra_fields = True
516 524 filter_extra_fields = True
517 525
518 526 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
519 527 SlugifyName())
520 528 group_description = UnicodeString(strip=True, min=1,
521 529 not_empty=True)
522 530 group_parent_id = OneOf(available_groups, hideList=False,
523 531 testValueList=True,
524 532 if_missing=None, not_empty=False)
525 533
526 534 chained_validators = [ValidReposGroup(edit, old_data)]
527 535
528 536 return _ReposGroupForm
529 537
530 538 def RegisterForm(edit=False, old_data={}):
531 539 class _RegisterForm(formencode.Schema):
532 540 allow_extra_fields = True
533 541 filter_extra_fields = True
534 542 username = All(ValidUsername(edit, old_data),
535 543 UnicodeString(strip=True, min=1, not_empty=True))
536 544 password = All(UnicodeString(strip=True, min=6, not_empty=True))
537 545 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
538 546 active = StringBoolean(if_missing=False)
539 547 name = UnicodeString(strip=True, min=1, not_empty=True)
540 548 lastname = UnicodeString(strip=True, min=1, not_empty=True)
541 549 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
542 550
543 551 chained_validators = [ValidPasswordsMatch, ValidPassword]
544 552
545 553 return _RegisterForm
546 554
547 555 def PasswordResetForm():
548 556 class _PasswordResetForm(formencode.Schema):
549 557 allow_extra_fields = True
550 558 filter_extra_fields = True
551 559 email = All(ValidSystemEmail(), Email(not_empty=True))
552 560 return _PasswordResetForm
553 561
554 562 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
555 563 repo_groups=[]):
556 564 class _RepoForm(formencode.Schema):
557 565 allow_extra_fields = True
558 566 filter_extra_fields = False
559 567 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
560 568 SlugifyName())
561 569 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
562 570 ValidCloneUri()())
563 571 repo_group = OneOf(repo_groups, hideList=True)
564 572 repo_type = OneOf(supported_backends)
565 573 description = UnicodeString(strip=True, min=1, not_empty=True)
566 574 private = StringBoolean(if_missing=False)
567 575 enable_statistics = StringBoolean(if_missing=False)
568 576 enable_downloads = StringBoolean(if_missing=False)
569 577
570 578 if edit:
571 579 #this is repo owner
572 580 user = All(UnicodeString(not_empty=True), ValidRepoUser)
573 581
574 582 chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
575 583 return _RepoForm
576 584
577 585 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
578 586 class _RepoForkForm(formencode.Schema):
579 587 allow_extra_fields = True
580 588 filter_extra_fields = False
581 589 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
582 590 SlugifyName())
583 591 description = UnicodeString(strip=True, min=1, not_empty=True)
584 592 private = StringBoolean(if_missing=False)
585 593 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
594
595 chained_validators = [ValidForkName()]
596
586 597 return _RepoForkForm
587 598
588 599 def RepoSettingsForm(edit=False, old_data={}):
589 600 class _RepoForm(formencode.Schema):
590 601 allow_extra_fields = True
591 602 filter_extra_fields = False
592 603 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
593 604 SlugifyName())
594 605 description = UnicodeString(strip=True, min=1, not_empty=True)
595 606 private = StringBoolean(if_missing=False)
596 607
597 608 chained_validators = [ValidRepoName(edit, old_data), ValidPerms, ValidSettings]
598 609 return _RepoForm
599 610
600 611
601 612 def ApplicationSettingsForm():
602 613 class _ApplicationSettingsForm(formencode.Schema):
603 614 allow_extra_fields = True
604 615 filter_extra_fields = False
605 616 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
606 617 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
607 618 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
608 619
609 620 return _ApplicationSettingsForm
610 621
611 622 def ApplicationUiSettingsForm():
612 623 class _ApplicationUiSettingsForm(formencode.Schema):
613 624 allow_extra_fields = True
614 625 filter_extra_fields = False
615 626 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
616 627 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
617 628 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
618 629 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
619 630 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
620 631 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
621 632
622 633 return _ApplicationUiSettingsForm
623 634
624 635 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
625 636 class _DefaultPermissionsForm(formencode.Schema):
626 637 allow_extra_fields = True
627 638 filter_extra_fields = True
628 639 overwrite_default = StringBoolean(if_missing=False)
629 640 anonymous = OneOf(['True', 'False'], if_missing=False)
630 641 default_perm = OneOf(perms_choices)
631 642 default_register = OneOf(register_choices)
632 643 default_create = OneOf(create_choices)
633 644
634 645 return _DefaultPermissionsForm
635 646
636 647
637 648 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
638 649 class _LdapSettingsForm(formencode.Schema):
639 650 allow_extra_fields = True
640 651 filter_extra_fields = True
641 652 pre_validators = [LdapLibValidator]
642 653 ldap_active = StringBoolean(if_missing=False)
643 654 ldap_host = UnicodeString(strip=True,)
644 655 ldap_port = Number(strip=True,)
645 656 ldap_tls_kind = OneOf(tls_kind_choices)
646 657 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
647 658 ldap_dn_user = UnicodeString(strip=True,)
648 659 ldap_dn_pass = UnicodeString(strip=True,)
649 660 ldap_base_dn = UnicodeString(strip=True,)
650 661 ldap_filter = UnicodeString(strip=True,)
651 662 ldap_search_scope = OneOf(search_scope_choices)
652 663 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
653 664 ldap_attr_firstname = UnicodeString(strip=True,)
654 665 ldap_attr_lastname = UnicodeString(strip=True,)
655 666 ldap_attr_email = UnicodeString(strip=True,)
656 667
657 668 return _LdapSettingsForm
@@ -1,376 +1,358 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.repo
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Repository model for rhodecode
7 7
8 8 :created_on: Jun 5, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import os
26 26 import shutil
27 27 import logging
28 28 import traceback
29 29 from datetime import datetime
30 30
31 31 from sqlalchemy.orm import joinedload, make_transient
32 32
33 33 from vcs.utils.lazy import LazyProperty
34 34 from vcs.backends import get_backend
35 35
36 36 from rhodecode.model import BaseModel
37 37 from rhodecode.model.caching_query import FromCache
38 38 from rhodecode.model.db import Repository, RepoToPerm, User, Permission, \
39 39 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, Group
40 40 from rhodecode.model.user import UserModel
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44
45 45 class RepoModel(BaseModel):
46 46
47 47 @LazyProperty
48 48 def repos_path(self):
49 49 """Get's the repositories root path from database
50 50 """
51 51
52 52 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
53 53 return q.ui_value
54 54
55 55 def get(self, repo_id, cache=False):
56 56 repo = self.sa.query(Repository)\
57 57 .filter(Repository.repo_id == repo_id)
58 58
59 59 if cache:
60 60 repo = repo.options(FromCache("sql_cache_short",
61 61 "get_repo_%s" % repo_id))
62 62 return repo.scalar()
63 63
64 64 def get_by_repo_name(self, repo_name, cache=False):
65 65 repo = self.sa.query(Repository)\
66 66 .filter(Repository.repo_name == repo_name)
67 67
68 68 if cache:
69 69 repo = repo.options(FromCache("sql_cache_short",
70 70 "get_repo_%s" % repo_name))
71 71 return repo.scalar()
72 72
73 def get_full(self, repo_name, cache=False, invalidate=False):
74 repo = self.sa.query(Repository)\
75 .options(joinedload(Repository.fork))\
76 .options(joinedload(Repository.user))\
77 .options(joinedload(Repository.group))\
78 .filter(Repository.repo_name == repo_name)\
79
80 if cache:
81 repo = repo.options(FromCache("sql_cache_long",
82 "get_repo_full_%s" % repo_name))
83 if invalidate and cache:
84 repo.invalidate()
85
86 ret = repo.scalar()
87
88 #make transient for sake of errors
89 make_transient(ret)
90 for k in ['fork', 'user', 'group']:
91 attr = getattr(ret, k, False)
92 if attr:
93 make_transient(attr)
94 return ret
95 73
96 74 def get_users_js(self):
97 75
98 76 users = self.sa.query(User).filter(User.active == True).all()
99 77 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
100 78 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
101 79 u.lastname, u.username)
102 80 for u in users])
103 81 return users_array
104 82
105 83 def get_users_groups_js(self):
106 84 users_groups = self.sa.query(UsersGroup)\
107 85 .filter(UsersGroup.users_group_active == True).all()
108 86
109 87 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
110 88
111 89 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
112 90 (gr.users_group_id, gr.users_group_name,
113 91 len(gr.members))
114 92 for gr in users_groups])
115 93 return users_groups_array
116 94
117 95 def update(self, repo_name, form_data):
118 96 try:
119 97 cur_repo = self.get_by_repo_name(repo_name, cache=False)
120 98 user_model = UserModel(self.sa)
121 99
122 100 #update permissions
123 101 for member, perm, member_type in form_data['perms_updates']:
124 102 if member_type == 'user':
125 103 r2p = self.sa.query(RepoToPerm)\
126 104 .filter(RepoToPerm.user == user_model.
127 105 get_by_username(member))\
128 106 .filter(RepoToPerm.repository == cur_repo)\
129 107 .one()
130 108
131 109 r2p.permission = self.sa.query(Permission)\
132 110 .filter(Permission.permission_name ==
133 111 perm).scalar()
134 112 self.sa.add(r2p)
135 113 else:
136 114 g2p = self.sa.query(UsersGroupRepoToPerm)\
137 115 .filter(UsersGroupRepoToPerm.users_group ==
138 116 UsersGroup.get_by_group_name(member))\
139 117 .filter(UsersGroupRepoToPerm.repository ==
140 118 cur_repo).one()
141 119
142 120 g2p.permission = self.sa.query(Permission)\
143 121 .filter(Permission.permission_name ==
144 122 perm).scalar()
145 123 self.sa.add(g2p)
146 124
147 125 #set new permissions
148 126 for member, perm, member_type in form_data['perms_new']:
149 127 if member_type == 'user':
150 128 r2p = RepoToPerm()
151 129 r2p.repository = cur_repo
152 130 r2p.user = user_model.get_by_username(member)
153 131
154 132 r2p.permission = self.sa.query(Permission)\
155 133 .filter(Permission.
156 134 permission_name == perm)\
157 135 .scalar()
158 136 self.sa.add(r2p)
159 137 else:
160 138 g2p = UsersGroupRepoToPerm()
161 139 g2p.repository = cur_repo
162 140 g2p.users_group = UsersGroup.get_by_group_name(member)
163 141
164 142 g2p.permission = self.sa.query(Permission)\
165 143 .filter(Permission.
166 144 permission_name == perm)\
167 145 .scalar()
168 146 self.sa.add(g2p)
169 147
170 148 #update current repo
171 149 for k, v in form_data.items():
172 150 if k == 'user':
173 151 cur_repo.user = user_model.get_by_username(v)
174 152 elif k == 'repo_name':
175 153 cur_repo.repo_name = form_data['repo_name_full']
176 154 elif k == 'repo_group' and v:
177 155 cur_repo.group_id = v
178 156
179 157 else:
180 158 setattr(cur_repo, k, v)
181 159
182 160 self.sa.add(cur_repo)
183 161
184 162 if repo_name != form_data['repo_name_full']:
185 163 # rename repository
186 164 self.__rename_repo(old=repo_name,
187 165 new=form_data['repo_name_full'])
188 166
189 167 self.sa.commit()
190 168 except:
191 169 log.error(traceback.format_exc())
192 170 self.sa.rollback()
193 171 raise
194 172
195 173 def create(self, form_data, cur_user, just_db=False, fork=False):
174
196 175 try:
197 176 if fork:
198 177 #force str since hg doesn't go with unicode
199 178 repo_name = str(form_data['fork_name'])
200 179 org_name = str(form_data['repo_name'])
201 org_full_name = str(form_data['repo_name_full'])
180 org_full_name = org_name#str(form_data['fork_name_full'])
202 181
203 182 else:
204 183 org_name = repo_name = str(form_data['repo_name'])
205 184 repo_name_full = form_data['repo_name_full']
206 185
207 186 new_repo = Repository()
208 187 new_repo.enable_statistics = False
209 188 for k, v in form_data.items():
210 189 if k == 'repo_name':
211 v = repo_name_full
190 if fork:
191 v = repo_name
192 else:
193 v = repo_name_full
212 194 if k == 'repo_group':
213 195 k = 'group_id'
214 196
215 197 setattr(new_repo, k, v)
216 198
217 199 if fork:
218 200 parent_repo = self.sa.query(Repository)\
219 .filter(Repository.repo_name == org_full_name).scalar()
201 .filter(Repository.repo_name == org_full_name).one()
220 202 new_repo.fork = parent_repo
221 203
222 204 new_repo.user_id = cur_user.user_id
223 205 self.sa.add(new_repo)
224 206
225 207 #create default permission
226 208 repo_to_perm = RepoToPerm()
227 209 default = 'repository.read'
228 210 for p in UserModel(self.sa).get_by_username('default',
229 211 cache=False).user_perms:
230 212 if p.permission.permission_name.startswith('repository.'):
231 213 default = p.permission.permission_name
232 214 break
233 215
234 216 default_perm = 'repository.none' if form_data['private'] else default
235 217
236 218 repo_to_perm.permission_id = self.sa.query(Permission)\
237 219 .filter(Permission.permission_name == default_perm)\
238 220 .one().permission_id
239 221
240 222 repo_to_perm.repository = new_repo
241 223 repo_to_perm.user_id = UserModel(self.sa)\
242 224 .get_by_username('default', cache=False).user_id
243 225
244 226 self.sa.add(repo_to_perm)
245 227
246 228 if not just_db:
247 229 self.__create_repo(repo_name, form_data['repo_type'],
248 230 form_data['repo_group'],
249 231 form_data['clone_uri'])
250 232
251 233 self.sa.commit()
252 234
253 235 #now automatically start following this repository as owner
254 236 from rhodecode.model.scm import ScmModel
255 237 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
256 238 cur_user.user_id)
257 239
258 240 except:
259 241 log.error(traceback.format_exc())
260 242 self.sa.rollback()
261 243 raise
262 244
263 245 def create_fork(self, form_data, cur_user):
264 246 from rhodecode.lib.celerylib import tasks, run_task
265 247 run_task(tasks.create_repo_fork, form_data, cur_user)
266 248
267 249 def delete(self, repo):
268 250 try:
269 251 self.sa.delete(repo)
270 252 self.__delete_repo(repo)
271 253 self.sa.commit()
272 254 except:
273 255 log.error(traceback.format_exc())
274 256 self.sa.rollback()
275 257 raise
276 258
277 259 def delete_perm_user(self, form_data, repo_name):
278 260 try:
279 261 self.sa.query(RepoToPerm)\
280 262 .filter(RepoToPerm.repository \
281 263 == self.get_by_repo_name(repo_name))\
282 264 .filter(RepoToPerm.user_id == form_data['user_id']).delete()
283 265 self.sa.commit()
284 266 except:
285 267 log.error(traceback.format_exc())
286 268 self.sa.rollback()
287 269 raise
288 270
289 271 def delete_perm_users_group(self, form_data, repo_name):
290 272 try:
291 273 self.sa.query(UsersGroupRepoToPerm)\
292 274 .filter(UsersGroupRepoToPerm.repository \
293 275 == self.get_by_repo_name(repo_name))\
294 276 .filter(UsersGroupRepoToPerm.users_group_id \
295 277 == form_data['users_group_id']).delete()
296 278 self.sa.commit()
297 279 except:
298 280 log.error(traceback.format_exc())
299 281 self.sa.rollback()
300 282 raise
301 283
302 284 def delete_stats(self, repo_name):
303 285 try:
304 286 self.sa.query(Statistics)\
305 287 .filter(Statistics.repository == \
306 288 self.get_by_repo_name(repo_name)).delete()
307 289 self.sa.commit()
308 290 except:
309 291 log.error(traceback.format_exc())
310 292 self.sa.rollback()
311 293 raise
312 294
313 295 def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
314 296 """
315 297 makes repository on filesystem. It's group aware means it'll create
316 298 a repository within a group, and alter the paths accordingly of
317 299 group location
318 300
319 301 :param repo_name:
320 302 :param alias:
321 303 :param parent_id:
322 304 :param clone_uri:
323 305 """
324 306 from rhodecode.lib.utils import check_repo
325 307
326 308
327 309 if new_parent_id:
328 310 paths = Group.get(new_parent_id).full_path.split(Group.url_sep())
329 311 new_parent_path = os.sep.join(paths)
330 312 else:
331 313 new_parent_path = ''
332 314
333 315 repo_path = os.path.join(self.repos_path, new_parent_path, repo_name)
334 316
335 317 if check_repo(repo_name, self.repos_path):
336 318 log.info('creating repo %s in %s @ %s', repo_name, repo_path,
337 319 clone_uri)
338 320 backend = get_backend(alias)
339 321 backend(repo_path, create=True, src_url=clone_uri)
340 322
341 323 def __rename_repo(self, old, new):
342 324 """
343 325 renames repository on filesystem
344 326
345 327 :param old: old name
346 328 :param new: new name
347 329 """
348 330 log.info('renaming repo from %s to %s', old, new)
349 331
350 332 old_path = os.path.join(self.repos_path, old)
351 333 new_path = os.path.join(self.repos_path, new)
352 334 if os.path.isdir(new_path):
353 335 raise Exception('Was trying to rename to already existing dir %s',
354 336 new_path)
355 337 shutil.move(old_path, new_path)
356 338
357 339 def __delete_repo(self, repo):
358 340 """
359 341 removes repo from filesystem, the removal is acctually made by
360 342 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
361 343 repository is no longer valid for rhodecode, can be undeleted later on
362 344 by reverting the renames on this repository
363 345
364 346 :param repo: repo object
365 347 """
366 348 rm_path = os.path.join(self.repos_path, repo.repo_name)
367 349 log.info("Removing %s", rm_path)
368 350 #disable hg/git
369 351 alias = repo.repo_type
370 352 shutil.move(os.path.join(rm_path, '.%s' % alias),
371 353 os.path.join(rm_path, 'rm__.%s' % alias))
372 354 #disable repo
373 355 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
374 356 % (datetime.today()\
375 357 .strftime('%Y%m%d_%H%M%S_%f'),
376 358 repo.repo_name)))
@@ -1,442 +1,412 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.scm
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Scm model for RhodeCode
7 7
8 8 :created_on: Apr 9, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import os
26 26 import time
27 27 import traceback
28 28 import logging
29 29
30 30 from mercurial import ui
31 31
32 32 from sqlalchemy.exc import DatabaseError
33 33 from sqlalchemy.orm import make_transient
34 34
35 35 from beaker.cache import cache_region, region_invalidate
36 36
37 37 from vcs import get_backend
38 38 from vcs.utils.helpers import get_scm
39 39 from vcs.exceptions import RepositoryError, VCSError
40 40 from vcs.utils.lazy import LazyProperty
41 41 from vcs.nodes import FileNode
42 42
43 43 from rhodecode import BACKENDS
44 44 from rhodecode.lib import helpers as h
45 45 from rhodecode.lib.auth import HasRepoPermissionAny
46 46 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
47 47 action_logger
48 48 from rhodecode.model import BaseModel
49 49 from rhodecode.model.user import UserModel
50 50 from rhodecode.model.repo import RepoModel
51 51 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
52 52 UserFollowing, UserLog
53 53 from rhodecode.model.caching_query import FromCache
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class UserTemp(object):
59 59 def __init__(self, user_id):
60 60 self.user_id = user_id
61 61
62 62 def __repr__(self):
63 63 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
64 64
65 65
66 66 class RepoTemp(object):
67 67 def __init__(self, repo_id):
68 68 self.repo_id = repo_id
69 69
70 70 def __repr__(self):
71 71 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
72 72
73 class CachedRepoList(object):
74
75 def __init__(self, db_repo_list, invalidation_list, repos_path,
76 order_by=None):
77 self.db_repo_list = db_repo_list
78 self.invalidation_list = invalidation_list
79 self.repos_path = repos_path
80 self.order_by = order_by
81 self.reversed = (order_by or '').startswith('-')
82
83 def __len__(self):
84 return len(self.db_repo_list)
85
86 def __repr__(self):
87 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
88
89 def __iter__(self):
90 for db_repo in self.db_repo_list:
91 dbr = db_repo
92
93 # invalidate the repo cache if needed before getting the
94 # scm instance
95
96 scm_invalidate = False
97 if self.invalidation_list is not None:
98 scm_invalidate = dbr.repo_name in self.invalidation_list
99
100 if scm_invalidate:
101 log.info('invalidating cache for repository %s',
102 dbr.repo_name)
103 db_repo.set_invalidate
104
105 scmr = db_repo.scm_instance_cached
106
107 #check permission at this level
108 if not HasRepoPermissionAny('repository.read',
109 'repository.write',
110 'repository.admin')(dbr.repo_name,
111 'get repo check'):
112 continue
113
114
115
116
117
118 last_change = scmr.last_change
119 tip = h.get_changeset_safe(scmr, 'tip')
120
121 tmp_d = {}
122 tmp_d['name'] = dbr.repo_name
123 tmp_d['name_sort'] = tmp_d['name'].lower()
124 tmp_d['description'] = dbr.description
125 tmp_d['description_sort'] = tmp_d['description']
126 tmp_d['last_change'] = last_change
127 tmp_d['last_change_sort'] = time.mktime(last_change \
128 .timetuple())
129 tmp_d['tip'] = tip.raw_id
130 tmp_d['tip_sort'] = tip.revision
131 tmp_d['rev'] = tip.revision
132 tmp_d['contact'] = dbr.user.full_contact
133 tmp_d['contact_sort'] = tmp_d['contact']
134 tmp_d['owner_sort'] = tmp_d['contact']
135 tmp_d['repo_archives'] = list(scmr._get_archives())
136 tmp_d['last_msg'] = tip.message
137 tmp_d['repo'] = scmr
138 tmp_d['dbrepo'] = dbr.get_dict()
139 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork \
140 else {}
141 yield tmp_d
73 142
74 143 class ScmModel(BaseModel):
75 144 """Generic Scm Model
76 145 """
77 146
78 147 @LazyProperty
79 148 def repos_path(self):
80 149 """Get's the repositories root path from database
81 150 """
82 151
83 152 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
84 153
85 154 return q.ui_value
86 155
87 156 def repo_scan(self, repos_path=None):
88 157 """Listing of repositories in given path. This path should not be a
89 158 repository itself. Return a dictionary of repository objects
90 159
91 160 :param repos_path: path to directory containing repositories
92 161 """
93 162
94 163 log.info('scanning for repositories in %s', repos_path)
95 164
96 165 if repos_path is None:
97 166 repos_path = self.repos_path
98 167
99 168 baseui = make_ui('db')
100 169 repos_list = {}
101 170
102 171 for name, path in get_filesystem_repos(repos_path, recursive=True):
103 172 try:
104 173 if name in repos_list:
105 174 raise RepositoryError('Duplicate repository name %s '
106 175 'found in %s' % (name, path))
107 176 else:
108 177
109 178 klass = get_backend(path[0])
110 179
111 180 if path[0] == 'hg' and path[0] in BACKENDS.keys():
112 181 repos_list[name] = klass(path[1], baseui=baseui)
113 182
114 183 if path[0] == 'git' and path[0] in BACKENDS.keys():
115 184 repos_list[name] = klass(path[1])
116 185 except OSError:
117 186 continue
118 187
119 188 return repos_list
120 189
121 def get_repos(self, all_repos=None):
190 def get_repos(self, all_repos=None, sort_key=None):
122 191 """
123 192 Get all repos from db and for each repo create it's
124 193 backend instance and fill that backed with information from database
125 194
126 195 :param all_repos: list of repository names as strings
127 196 give specific repositories list, good for filtering
128 197 """
129 198 if all_repos is None:
130 repos = self.sa.query(Repository)\
199 all_repos = self.sa.query(Repository)\
131 200 .filter(Repository.group_id == None)\
132 201 .order_by(Repository.repo_name).all()
133 all_repos = [r.repo_name for r in repos]
134 202
135 203 #get the repositories that should be invalidated
136 204 invalidation_list = [str(x.cache_key) for x in \
137 205 self.sa.query(CacheInvalidation.cache_key)\
138 206 .filter(CacheInvalidation.cache_active == False)\
139 207 .all()]
140 for r_name in all_repos:
141 r_dbr = self.get(r_name, invalidation_list)
142 if r_dbr is not None:
143 repo, dbrepo = r_dbr
144
145 if repo is None or dbrepo is None:
146 log.error('Repository "%s" looks somehow corrupted '
147 'fs-repo:%s,db-repo:%s both values should be '
148 'present', r_name, repo, dbrepo)
149 continue
150 last_change = repo.last_change
151 tip = h.get_changeset_safe(repo, 'tip')
152
153 tmp_d = {}
154 tmp_d['name'] = dbrepo.repo_name
155 tmp_d['name_sort'] = tmp_d['name'].lower()
156 tmp_d['description'] = dbrepo.description
157 tmp_d['description_sort'] = tmp_d['description']
158 tmp_d['last_change'] = last_change
159 tmp_d['last_change_sort'] = time.mktime(last_change \
160 .timetuple())
161 tmp_d['tip'] = tip.raw_id
162 tmp_d['tip_sort'] = tip.revision
163 tmp_d['rev'] = tip.revision
164 tmp_d['contact'] = dbrepo.user.full_contact
165 tmp_d['contact_sort'] = tmp_d['contact']
166 tmp_d['owner_sort'] = tmp_d['contact']
167 tmp_d['repo_archives'] = list(repo._get_archives())
168 tmp_d['last_msg'] = tip.message
169 tmp_d['repo'] = repo
170 tmp_d['dbrepo'] = dbrepo.get_dict()
171 tmp_d['dbrepo_fork'] = dbrepo.fork.get_dict() if dbrepo.fork \
172 else {}
173 yield tmp_d
174
175 def get(self, repo_name, invalidation_list=None, retval='all'):
176 """Returns a tuple of Repository,DbRepository,
177 Get's repository from given name, creates BackendInstance and
178 propagates it's data from database with all additional information
179
180 :param repo_name:
181 :param invalidation_list: if a invalidation list is given the get
182 method should not manually check if this repository needs
183 invalidation and just invalidate the repositories in list
184 :param retval: string specifing what to return one of 'repo','dbrepo',
185 'all'if repo or dbrepo is given it'll just lazy load chosen type
186 and return None as the second
187 """
188 if not HasRepoPermissionAny('repository.read', 'repository.write',
189 'repository.admin')(repo_name, 'get repo check'):
190 return
191 208
192 #======================================================================
193 # CACHE FUNCTION
194 #======================================================================
195 @cache_region('long_term')
196 def _get_repo(repo_name):
197
198 repo_path = os.path.join(self.repos_path, repo_name)
199
200 try:
201 alias = get_scm(repo_path)[0]
202 log.debug('Creating instance of %s repository', alias)
203 backend = get_backend(alias)
204 except VCSError:
205 log.error(traceback.format_exc())
206 log.error('Perhaps this repository is in db and not in '
207 'filesystem run rescan repositories with '
208 '"destroy old data " option from admin panel')
209 return
209 repo_iter = CachedRepoList(all_repos, invalidation_list,
210 repos_path=self.repos_path,
211 order_by=sort_key)
210 212
211 if alias == 'hg':
212 repo = backend(repo_path, create=False, baseui=make_ui('db'))
213 #skip hidden web repository
214 if repo._get_hidden():
215 return
216 else:
217 repo = backend(repo_path, create=False)
218
219 return repo
220
221 pre_invalidate = True
222 dbinvalidate = False
223
224 if invalidation_list is not None:
225 pre_invalidate = repo_name in invalidation_list
226
227 if pre_invalidate:
228 #this returns object to invalidate
229 invalidate = self._should_invalidate(repo_name)
230 if invalidate:
231 log.info('invalidating cache for repository %s', repo_name)
232 region_invalidate(_get_repo, None, repo_name)
233 self._mark_invalidated(invalidate)
234 dbinvalidate = True
235
236 r, dbr = None, None
237 if retval == 'repo' or 'all':
238 r = _get_repo(repo_name)
239 if retval == 'dbrepo' or 'all':
240 dbr = RepoModel().get_full(repo_name, cache=True,
241 invalidate=dbinvalidate)
242
243 return r, dbr
213 return repo_iter
244 214
245 215 def mark_for_invalidation(self, repo_name):
246 216 """Puts cache invalidation task into db for
247 217 further global cache invalidation
248 218
249 219 :param repo_name: this repo that should invalidation take place
250 220 """
251 221
252 222 log.debug('marking %s for invalidation', repo_name)
253 223 cache = self.sa.query(CacheInvalidation)\
254 224 .filter(CacheInvalidation.cache_key == repo_name).scalar()
255 225
256 226 if cache:
257 227 #mark this cache as inactive
258 228 cache.cache_active = False
259 229 else:
260 230 log.debug('cache key not found in invalidation db -> creating one')
261 231 cache = CacheInvalidation(repo_name)
262 232
263 233 try:
264 234 self.sa.add(cache)
265 235 self.sa.commit()
266 236 except (DatabaseError,):
267 237 log.error(traceback.format_exc())
268 238 self.sa.rollback()
269 239
270 240 def toggle_following_repo(self, follow_repo_id, user_id):
271 241
272 242 f = self.sa.query(UserFollowing)\
273 243 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
274 244 .filter(UserFollowing.user_id == user_id).scalar()
275 245
276 246 if f is not None:
277 247
278 248 try:
279 249 self.sa.delete(f)
280 250 self.sa.commit()
281 251 action_logger(UserTemp(user_id),
282 252 'stopped_following_repo',
283 253 RepoTemp(follow_repo_id))
284 254 return
285 255 except:
286 256 log.error(traceback.format_exc())
287 257 self.sa.rollback()
288 258 raise
289 259
290 260 try:
291 261 f = UserFollowing()
292 262 f.user_id = user_id
293 263 f.follows_repo_id = follow_repo_id
294 264 self.sa.add(f)
295 265 self.sa.commit()
296 266 action_logger(UserTemp(user_id),
297 267 'started_following_repo',
298 268 RepoTemp(follow_repo_id))
299 269 except:
300 270 log.error(traceback.format_exc())
301 271 self.sa.rollback()
302 272 raise
303 273
304 274 def toggle_following_user(self, follow_user_id, user_id):
305 275 f = self.sa.query(UserFollowing)\
306 276 .filter(UserFollowing.follows_user_id == follow_user_id)\
307 277 .filter(UserFollowing.user_id == user_id).scalar()
308 278
309 279 if f is not None:
310 280 try:
311 281 self.sa.delete(f)
312 282 self.sa.commit()
313 283 return
314 284 except:
315 285 log.error(traceback.format_exc())
316 286 self.sa.rollback()
317 287 raise
318 288
319 289 try:
320 290 f = UserFollowing()
321 291 f.user_id = user_id
322 292 f.follows_user_id = follow_user_id
323 293 self.sa.add(f)
324 294 self.sa.commit()
325 295 except:
326 296 log.error(traceback.format_exc())
327 297 self.sa.rollback()
328 298 raise
329 299
330 300 def is_following_repo(self, repo_name, user_id, cache=False):
331 301 r = self.sa.query(Repository)\
332 302 .filter(Repository.repo_name == repo_name).scalar()
333 303
334 304 f = self.sa.query(UserFollowing)\
335 305 .filter(UserFollowing.follows_repository == r)\
336 306 .filter(UserFollowing.user_id == user_id).scalar()
337 307
338 308 return f is not None
339 309
340 310 def is_following_user(self, username, user_id, cache=False):
341 311 u = UserModel(self.sa).get_by_username(username)
342 312
343 313 f = self.sa.query(UserFollowing)\
344 314 .filter(UserFollowing.follows_user == u)\
345 315 .filter(UserFollowing.user_id == user_id).scalar()
346 316
347 317 return f is not None
348 318
349 319 def get_followers(self, repo_id):
350 320 if not isinstance(repo_id, int):
351 321 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
352 322
353 323 return self.sa.query(UserFollowing)\
354 324 .filter(UserFollowing.follows_repo_id == repo_id).count()
355 325
356 326 def get_forks(self, repo_id):
357 327 if not isinstance(repo_id, int):
358 328 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
359 329
360 330 return self.sa.query(Repository)\
361 331 .filter(Repository.fork_id == repo_id).count()
362 332
363 333 def pull_changes(self, repo_name, username):
364 334 repo, dbrepo = self.get(repo_name, retval='all')
365 335
366 336 try:
367 337 extras = {'ip': '',
368 338 'username': username,
369 339 'action': 'push_remote',
370 340 'repository': repo_name}
371 341
372 342 #inject ui extra param to log this action via push logger
373 343 for k, v in extras.items():
374 344 repo._repo.ui.setconfig('rhodecode_extras', k, v)
375 345
376 346 repo.pull(dbrepo.clone_uri)
377 347 self.mark_for_invalidation(repo_name)
378 348 except:
379 349 log.error(traceback.format_exc())
380 350 raise
381 351
382 352
383 353 def commit_change(self, repo, repo_name, cs, user, author, message, content,
384 354 f_path):
385 355
386 356 if repo.alias == 'hg':
387 357 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
388 358 elif repo.alias == 'git':
389 359 from vcs.backends.git import GitInMemoryChangeset as IMC
390 360
391 361 # decoding here will force that we have proper encoded values
392 362 # in any other case this will throw exceptions and deny commit
393 363 content = content.encode('utf8')
394 364 message = message.encode('utf8')
395 365 path = f_path.encode('utf8')
396 366 author = author.encode('utf8')
397 367 m = IMC(repo)
398 368 m.change(FileNode(path, content))
399 369 tip = m.commit(message=message,
400 370 author=author,
401 371 parents=[cs], branch=cs.branch)
402 372
403 373 new_cs = tip.short_id
404 374 action = 'push_local:%s' % new_cs
405 375
406 376 action_logger(user, action, repo_name)
407 377
408 378 self.mark_for_invalidation(repo_name)
409 379
410 380
411 381 def get_unread_journal(self):
412 382 return self.sa.query(UserLog).count()
413 383
414 384 def _should_invalidate(self, repo_name):
415 385 """Looks up database for invalidation signals for this repo_name
416 386
417 387 :param repo_name:
418 388 """
419 389
420 390 ret = self.sa.query(CacheInvalidation)\
421 391 .filter(CacheInvalidation.cache_key == repo_name)\
422 392 .filter(CacheInvalidation.cache_active == False)\
423 393 .scalar()
424 394
425 395 return ret
426 396
427 397 def _mark_invalidated(self, cache_key):
428 398 """ Marks all occurrences of cache to invalidation as already
429 399 invalidated
430 400
431 401 :param cache_key:
432 402 """
433 403
434 404 if cache_key:
435 405 log.debug('marking %s as already invalidated', cache_key)
436 406 try:
437 407 cache_key.cache_active = True
438 408 self.sa.add(cache_key)
439 409 self.sa.commit()
440 410 except (DatabaseError,):
441 411 log.error(traceback.format_exc())
442 412 self.sa.rollback()
@@ -1,149 +1,149 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Edit user')} ${c.user.username} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 11 ${h.link_to(_('Users'),h.url('users'))}
12 12 &raquo;
13 13 ${_('edit')} "${c.user.username}"
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
17 17 ${self.menu('admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box box-left">
22 22 <!-- box / title -->
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 </div>
26 26 <!-- end box / title -->
27 27 ${h.form(url('update_user', id=c.user.user_id),method='put')}
28 28 <div class="form">
29 29 <div class="field">
30 30 <div class="gravatar_box">
31 31 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
32 32 <p>
33 33 <strong>Change your avatar at <a href="http://gravatar.com">gravatar.com</a></strong><br/>
34 34 ${_('Using')} ${c.user.email}
35 35 </p>
36 36 </div>
37 37 </div>
38 38 <div class="field">
39 39 <div class="label">
40 40 <label>${_('API key')}</label> ${c.user.api_key}
41 41 </div>
42 42 </div>
43 43
44 44 <div class="fields">
45 45 <div class="field">
46 46 <div class="label">
47 47 <label for="username">${_('Username')}:</label>
48 48 </div>
49 49 <div class="input">
50 50 ${h.text('username',class_='medium')}
51 51 </div>
52 52 </div>
53 53
54 54 <div class="field">
55 55 <div class="label">
56 56 <label for="ldap_dn">${_('LDAP DN')}:</label>
57 57 </div>
58 58 <div class="input">
59 59 ${h.text('ldap_dn',class_='medium')}
60 60 </div>
61 61 </div>
62 62
63 63 <div class="field">
64 64 <div class="label">
65 65 <label for="new_password">${_('New password')}:</label>
66 66 </div>
67 67 <div class="input">
68 ${h.password('new_password',class_='medium')}
68 ${h.password('new_password',class_='medium',autocomplete="off")}
69 69 </div>
70 70 </div>
71 71
72 72 <div class="field">
73 73 <div class="label">
74 74 <label for="name">${_('First Name')}:</label>
75 75 </div>
76 76 <div class="input">
77 77 ${h.text('name',class_='medium')}
78 78 </div>
79 79 </div>
80 80
81 81 <div class="field">
82 82 <div class="label">
83 83 <label for="lastname">${_('Last Name')}:</label>
84 84 </div>
85 85 <div class="input">
86 86 ${h.text('lastname',class_='medium')}
87 87 </div>
88 88 </div>
89 89
90 90 <div class="field">
91 91 <div class="label">
92 92 <label for="email">${_('Email')}:</label>
93 93 </div>
94 94 <div class="input">
95 95 ${h.text('email',class_='medium')}
96 96 </div>
97 97 </div>
98 98
99 99 <div class="field">
100 100 <div class="label label-checkbox">
101 101 <label for="active">${_('Active')}:</label>
102 102 </div>
103 103 <div class="checkboxes">
104 104 ${h.checkbox('active',value=True)}
105 105 </div>
106 106 </div>
107 107
108 108 <div class="field">
109 109 <div class="label label-checkbox">
110 110 <label for="admin">${_('Admin')}:</label>
111 111 </div>
112 112 <div class="checkboxes">
113 113 ${h.checkbox('admin',value=True)}
114 114 </div>
115 115 </div>
116 116 <div class="buttons">
117 117 ${h.submit('save','Save',class_="ui-button")}
118 118 ${h.reset('reset','Reset',class_="ui-button")}
119 119 </div>
120 120 </div>
121 121 </div>
122 122 ${h.end_form()}
123 123 </div>
124 124 <div class="box box-right">
125 125 <!-- box / title -->
126 126 <div class="title">
127 127 <h5>${_('Permissions')}</h5>
128 128 </div>
129 129 ${h.form(url('user_perm', id=c.user.user_id),method='put')}
130 130 <div class="form">
131 131 <!-- fields -->
132 132 <div class="fields">
133 133 <div class="field">
134 134 <div class="label label-checkbox">
135 135 <label for="">${_('Create repositories')}:</label>
136 136 </div>
137 137 <div class="checkboxes">
138 138 ${h.checkbox('create_repo_perm',value=True)}
139 139 </div>
140 140 </div>
141 141 <div class="buttons">
142 142 ${h.submit('save','Save',class_="ui-button")}
143 143 ${h.reset('reset','Reset',class_="ui-button")}
144 144 </div>
145 145 </div>
146 146 </div>
147 147 ${h.end_form()}
148 148 </div>
149 149 </%def>
@@ -1,211 +1,211 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('My account')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${_('My Account')}
10 10 </%def>
11 11
12 12 <%def name="page_nav()">
13 13 ${self.menu('admin')}
14 14 </%def>
15 15
16 16 <%def name="main()">
17 17
18 18 <div class="box box-left">
19 19 <!-- box / title -->
20 20 <div class="title">
21 21 ${self.breadcrumbs()}
22 22 </div>
23 23 <!-- end box / title -->
24 24 <div>
25 25 ${h.form(url('admin_settings_my_account_update'),method='put')}
26 26 <div class="form">
27 27
28 28 <div class="field">
29 29 <div class="gravatar_box">
30 30 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
31 31 <p>
32 32 <strong>Change your avatar at <a href="http://gravatar.com">gravatar.com</a></strong><br/>
33 33 ${_('Using')} ${c.user.email}
34 34 </p>
35 35 </div>
36 36 </div>
37 37 <div class="field">
38 38 <div class="label">
39 39 <label>${_('API key')}</label> ${c.user.api_key}
40 40 </div>
41 41 </div>
42 42 <div class="fields">
43 43 <div class="field">
44 44 <div class="label">
45 45 <label for="username">${_('Username')}:</label>
46 46 </div>
47 47 <div class="input">
48 48 ${h.text('username',class_="medium")}
49 49 </div>
50 50 </div>
51 51
52 52 <div class="field">
53 53 <div class="label">
54 54 <label for="new_password">${_('New password')}:</label>
55 55 </div>
56 56 <div class="input">
57 ${h.password('new_password',class_="medium")}
57 ${h.password('new_password',class_="medium",autocomplete="off")}
58 58 </div>
59 59 </div>
60 60
61 61 <div class="field">
62 62 <div class="label">
63 63 <label for="name">${_('First Name')}:</label>
64 64 </div>
65 65 <div class="input">
66 66 ${h.text('name',class_="medium")}
67 67 </div>
68 68 </div>
69 69
70 70 <div class="field">
71 71 <div class="label">
72 72 <label for="lastname">${_('Last Name')}:</label>
73 73 </div>
74 74 <div class="input">
75 75 ${h.text('lastname',class_="medium")}
76 76 </div>
77 77 </div>
78 78
79 79 <div class="field">
80 80 <div class="label">
81 81 <label for="email">${_('Email')}:</label>
82 82 </div>
83 83 <div class="input">
84 84 ${h.text('email',class_="medium")}
85 85 </div>
86 86 </div>
87 87
88 88 <div class="buttons">
89 89 ${h.submit('save','Save',class_="ui-button")}
90 90 ${h.reset('reset','Reset',class_="ui-button")}
91 91 </div>
92 92 </div>
93 93 </div>
94 94 ${h.end_form()}
95 95 </div>
96 96 </div>
97 97
98 98 <div class="box box-right">
99 99 <!-- box / title -->
100 100 <div class="title">
101 101 <h5>${_('My repositories')}
102 102 <input class="top-right-rounded-corner top-left-rounded-corner bottom-left-rounded-corner bottom-right-rounded-corner" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
103 103 </h5>
104 104 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
105 105 <ul class="links">
106 106 <li>
107 107 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
108 108 </li>
109 109 </ul>
110 110 %endif
111 111 </div>
112 112 <!-- end box / title -->
113 113 <div class="table">
114 114 <table>
115 115 <thead>
116 116 <tr>
117 117 <th class="left">${_('Name')}</th>
118 118 <th class="left">${_('revision')}</th>
119 119 <th colspan="2" class="left">${_('action')}</th>
120 120 </thead>
121 121 <tbody>
122 122 %if c.user_repos:
123 123 %for repo in c.user_repos:
124 124 <tr>
125 125 <td>
126 126 %if repo['dbrepo']['repo_type'] =='hg':
127 127 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url("/images/icons/hgicon.png")}"/>
128 128 %elif repo['dbrepo']['repo_type'] =='git':
129 129 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url("/images/icons/giticon.png")}"/>
130 130 %else:
131 131
132 132 %endif
133 133 %if repo['dbrepo']['private']:
134 134 <img class="icon" alt="${_('private')}" src="${h.url("/images/icons/lock.png")}"/>
135 135 %else:
136 136 <img class="icon" alt="${_('public')}" src="${h.url("/images/icons/lock_open.png")}"/>
137 137 %endif
138 138
139 139 ${h.link_to(repo['name'], h.url('summary_home',repo_name=repo['name']),class_="repo_name")}
140 140 %if repo['dbrepo_fork']:
141 141 <a href="${h.url('summary_home',repo_name=repo['dbrepo_fork']['repo_name'])}">
142 142 <img class="icon" alt="${_('public')}"
143 143 title="${_('Fork of')} ${repo['dbrepo_fork']['repo_name']}"
144 144 src="${h.url("/images/icons/arrow_divide.png")}"/></a>
145 145 %endif
146 146 </td>
147 147 <td><span class="tooltip" title="${repo['repo'].last_change}">${("r%s:%s") % (h.get_changeset_safe(repo['repo'],'tip').revision,h.short_id(h.get_changeset_safe(repo['repo'],'tip').raw_id))}</span></td>
148 148 <td><a href="${h.url('repo_settings_home',repo_name=repo['name'])}" title="${_('edit')}"><img class="icon" alt="${_('private')}" src="${h.url("/images/icons/application_form_edit.png")}"/></a></td>
149 149 <td>
150 150 ${h.form(url('repo_settings_delete', repo_name=repo['name']),method='delete')}
151 151 ${h.submit('remove_%s' % repo['name'],'',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
152 152 ${h.end_form()}
153 153 </td>
154 154 </tr>
155 155 %endfor
156 156 %else:
157 157 ${_('No repositories yet')}
158 158 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
159 159 ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'))}
160 160 %endif
161 161 %endif
162 162 </tbody>
163 163 </table>
164 164 </div>
165 165
166 166 </div>
167 167 <script type="text/javascript">
168 168 var D = YAHOO.util.Dom;
169 169 var E = YAHOO.util.Event;
170 170 var S = YAHOO.util.Selector;
171 171
172 172 var q_filter = D.get('q_filter');
173 173 var F = YAHOO.namespace('q_filter');
174 174
175 175 E.on(q_filter,'click',function(){
176 176 q_filter.value = '';
177 177 });
178 178
179 179 F.filterTimeout = null;
180 180
181 181 F.updateFilter = function() {
182 182 // Reset timeout
183 183 F.filterTimeout = null;
184 184
185 185 var obsolete = [];
186 186 var nodes = S.query('div.table tr td a.repo_name');
187 187 var req = q_filter.value.toLowerCase();
188 188 for (n in nodes){
189 189 D.setStyle(nodes[n].parentNode.parentNode,'display','')
190 190 }
191 191 if (req){
192 192 for (n in nodes){
193 193 if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
194 194 obsolete.push(nodes[n]);
195 195 }
196 196 }
197 197 if(obsolete){
198 198 for (n in obsolete){
199 199 D.setStyle(obsolete[n].parentNode.parentNode,'display','none');
200 200 }
201 201 }
202 202 }
203 203 }
204 204
205 205 E.on(q_filter,'keyup',function(e){
206 206 clearTimeout(F.filterTimeout);
207 207 setTimeout(F.updateFilter,600);
208 208 });
209 209
210 210 </script>
211 211 </%def> No newline at end of file
@@ -1,71 +1,74 b''
1 1 """Pylons application test package
2 2
3 3 This package assumes the Pylons environment is already loaded, such as
4 4 when this script is imported from the `nosetests --with-pylons=test.ini`
5 5 command.
6 6
7 7 This module initializes the application via ``websetup`` (`paster
8 8 setup-app`) and provides the base testing objects.
9 9 """
10 import os
11 from os.path import join as jn
12
10 13 from unittest import TestCase
11 14
12 15 from paste.deploy import loadapp
13 16 from paste.script.appinstall import SetupCommand
14 17 from pylons import config, url
15 18 from routes.util import URLGenerator
16 19 from webtest import TestApp
17 import os
20
18 21 from rhodecode.model import meta
19 22 import logging
20 23
21 24
22 25 log = logging.getLogger(__name__)
23 26
24 27 import pylons.test
25 28
26 29 __all__ = ['environ', 'url', 'TestController', 'TESTS_TMP_PATH', 'HG_REPO',
27 30 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO', 'HG_FORK', 'GIT_FORK', ]
28 31
29 32 # Invoke websetup with the current config file
30 33 #SetupCommand('setup-app').run([config_file])
31 34
32 35 ##RUNNING DESIRED TESTS
33 36 #nosetests -x rhodecode.tests.functional.test_admin_settings:TestSettingsController.test_my_account
34 37
35 38 environ = {}
36 39
37 40 #SOME GLOBALS FOR TESTS
38 TESTS_TMP_PATH = '/tmp'
41 TESTS_TMP_PATH = jn('/', 'tmp')
39 42
40 43 HG_REPO = 'vcs_test_hg'
41 44 GIT_REPO = 'vcs_test_git'
42 45
43 46 NEW_HG_REPO = 'vcs_test_hg_new'
44 47 NEW_GIT_REPO = 'vcs_test_git_new'
45 48
46 49 HG_FORK = 'vcs_test_hg_fork'
47 50 GIT_FORK = 'vcs_test_git_fork'
48 51
49 52 class TestController(TestCase):
50 53
51 54 def __init__(self, *args, **kwargs):
52 55 wsgiapp = pylons.test.pylonsapp
53 56 config = wsgiapp.config
54 57
55 58 self.app = TestApp(wsgiapp)
56 59 url._push_object(URLGenerator(config['routes.map'], environ))
57 60 self.sa = meta.Session
58 61 self.index_location = config['app_conf']['index_dir']
59 62 TestCase.__init__(self, *args, **kwargs)
60 63
61 64 def log_user(self, username='test_admin', password='test12'):
62 65 response = self.app.post(url(controller='login', action='index'),
63 66 {'username':username,
64 67 'password':password})
65 68
66 69 if 'invalid user name' in response.body:
67 assert False, 'could not login using %s %s' % (username, password)
70 self.fail('could not login using %s %s' % (username, password))
68 71
69 assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status
70 assert response.session['rhodecode_user'].username == username, 'wrong logged in user got %s expected %s' % (response.session['rhodecode_user'].username, username)
72 self.assertEqual(response.status, '302 Found')
73 self.assertEqual(response.session['rhodecode_user'].username, username)
71 74 return response.follow()
@@ -1,147 +1,178 b''
1 1 import os
2 2 import vcs
3 3
4 4 from rhodecode.model.db import Repository
5 5 from rhodecode.tests import *
6 6
7 7 class TestAdminReposController(TestController):
8 8
9
10 def __make_repo(self):
11 pass
12
13
9 14 def test_index(self):
10 15 self.log_user()
11 16 response = self.app.get(url('repos'))
12 17 # Test response...
13 18
14 19 def test_index_as_xml(self):
15 20 response = self.app.get(url('formatted_repos', format='xml'))
16 21
17 22 def test_create_hg(self):
18 23 self.log_user()
19 24 repo_name = NEW_HG_REPO
20 25 description = 'description for newly created repo'
21 26 private = False
22 27 response = self.app.post(url('repos'), {'repo_name':repo_name,
23 28 'repo_type':'hg',
29 'clone_uri':'',
30 'repo_group':'',
24 31 'description':description,
25 32 'private':private})
26
33 self.assertTrue('flash' in response.session)
27 34
28 35 #test if we have a message for that repository
29 assert '''created repository %s''' % (repo_name) in response.session['flash'][0], 'No flash message about new repo'
36 self.assertTrue('''created repository %s''' % (repo_name) in
37 response.session['flash'][0])
30 38
31 #test if the fork was created in the database
32 new_repo = self.sa.query(Repository).filter(Repository.repo_name == repo_name).one()
39 #test if the repo was created in the database
40 new_repo = self.sa.query(Repository).filter(Repository.repo_name ==
41 repo_name).one()
33 42
34 assert new_repo.repo_name == repo_name, 'wrong name of repo name in db'
35 assert new_repo.description == description, 'wrong description'
43 self.assertEqual(new_repo.repo_name, repo_name)
44 self.assertEqual(new_repo.description, description)
36 45
37 46 #test if repository is visible in the list ?
38 47 response = response.follow()
39 48
40 assert repo_name in response.body, 'missing new repo from the main repos list'
49 self.assertTrue(repo_name in response.body)
41 50
42 51
43 52 #test if repository was created on filesystem
44 53 try:
45 54 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
46 55 except:
47 assert False , 'no repo in filesystem'
56 self.fail('no repo in filesystem')
57
48 58
59 def test_create_hg_in_group(self):
60 #TODO: write test !
61 pass
49 62
50 63 def test_create_git(self):
51 64 return
52 65 self.log_user()
53 66 repo_name = NEW_GIT_REPO
54 67 description = 'description for newly created repo'
55 68 private = False
56 69 response = self.app.post(url('repos'), {'repo_name':repo_name,
57 70 'repo_type':'git',
71 'clone_uri':'',
72 'repo_group':'',
58 73 'description':description,
59 74 'private':private})
60 75
61 76
62 77 #test if we have a message for that repository
63 78 assert '''created repository %s''' % (repo_name) in response.session['flash'][0], 'No flash message about new repo'
64 79
65 80 #test if the fork was created in the database
66 81 new_repo = self.sa.query(Repository).filter(Repository.repo_name == repo_name).one()
67 82
68 83 assert new_repo.repo_name == repo_name, 'wrong name of repo name in db'
69 84 assert new_repo.description == description, 'wrong description'
70 85
71 86 #test if repository is visible in the list ?
72 87 response = response.follow()
73 88
74 89 assert repo_name in response.body, 'missing new repo from the main repos list'
75 90
76 91 #test if repository was created on filesystem
77 92 try:
78 93 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
79 94 except:
80 95 assert False , 'no repo in filesystem'
81 96
82 97 def test_new(self):
83 98 self.log_user()
84 99 response = self.app.get(url('new_repo'))
85 100
86 101 def test_new_as_xml(self):
87 102 response = self.app.get(url('formatted_new_repo', format='xml'))
88 103
89 104 def test_update(self):
90 105 response = self.app.put(url('repo', repo_name=HG_REPO))
91 106
92 107 def test_update_browser_fakeout(self):
93 response = self.app.post(url('repo', repo_name=HG_REPO), params=dict(_method='put'))
108 response = self.app.post(url('repo', repo_name=HG_REPO),
109 params=dict(_method='put'))
94 110
95 111 def test_delete(self):
96 112 self.log_user()
97 113 repo_name = 'vcs_test_new_to_delete'
98 114 description = 'description for newly created repo'
99 115 private = False
116
100 117 response = self.app.post(url('repos'), {'repo_name':repo_name,
101 118 'repo_type':'hg',
102 'description':description,
103 'private':private})
104
119 'clone_uri':'',
120 'repo_group':'',
121 'description':description,
122 'private':private})
123 self.assertTrue('flash' in response.session)
105 124
106 125 #test if we have a message for that repository
107 assert '''created repository %s''' % (repo_name) in response.session['flash'][0], 'No flash message about new repo'
126 self.assertTrue('''created repository %s''' % (repo_name) in
127 response.session['flash'][0])
108 128
109 129 #test if the repo was created in the database
110 new_repo = self.sa.query(Repository).filter(Repository.repo_name == repo_name).one()
130 new_repo = self.sa.query(Repository).filter(Repository.repo_name ==
131 repo_name).one()
111 132
112 assert new_repo.repo_name == repo_name, 'wrong name of repo name in db'
113 assert new_repo.description == description, 'wrong description'
133 self.assertEqual(new_repo.repo_name, repo_name)
134 self.assertEqual(new_repo.description, description)
114 135
115 136 #test if repository is visible in the list ?
116 137 response = response.follow()
117 138
118 assert repo_name in response.body, 'missing new repo from the main repos list'
139 self.assertTrue(repo_name in response.body)
119 140
120 141
121 142 response = self.app.delete(url('repo', repo_name=repo_name))
122 143
123 assert '''deleted repository %s''' % (repo_name) in response.session['flash'][0], 'No flash message about delete repo'
144 self.assertTrue('''deleted repository %s''' % (repo_name) in
145 response.session['flash'][0])
124 146
125 147 response.follow()
126 148
127 149 #check if repo was deleted from db
128 deleted_repo = self.sa.query(Repository).filter(Repository.repo_name == repo_name).scalar()
150 deleted_repo = self.sa.query(Repository).filter(Repository.repo_name
151 == repo_name).scalar()
152
153 self.assertEqual(deleted_repo, None)
129 154
130 assert deleted_repo is None, 'Deleted repository was found in db'
155
156 def test_delete_repo_with_group(self):
157 #TODO:
158 pass
131 159
132 160
133 161 def test_delete_browser_fakeout(self):
134 response = self.app.post(url('repo', repo_name=HG_REPO), params=dict(_method='delete'))
162 response = self.app.post(url('repo', repo_name=HG_REPO),
163 params=dict(_method='delete'))
135 164
136 165 def test_show(self):
137 166 self.log_user()
138 167 response = self.app.get(url('repo', repo_name=HG_REPO))
139 168
140 169 def test_show_as_xml(self):
141 response = self.app.get(url('formatted_repo', repo_name=HG_REPO, format='xml'))
170 response = self.app.get(url('formatted_repo', repo_name=HG_REPO,
171 format='xml'))
142 172
143 173 def test_edit(self):
144 174 response = self.app.get(url('edit_repo', repo_name=HG_REPO))
145 175
146 176 def test_edit_as_xml(self):
147 response = self.app.get(url('formatted_edit_repo', repo_name=HG_REPO, format='xml'))
177 response = self.app.get(url('formatted_edit_repo', repo_name=HG_REPO,
178 format='xml'))
@@ -1,193 +1,208 b''
1 # -*- coding: utf-8 -*-
2
1 3 from rhodecode.lib.auth import get_crypt_password, check_password
2 4 from rhodecode.model.db import User, RhodeCodeSettings
3 5 from rhodecode.tests import *
4 6
5 7 class TestAdminSettingsController(TestController):
6 8
7 9 def test_index(self):
8 10 response = self.app.get(url('admin_settings'))
9 11 # Test response...
10 12
11 13 def test_index_as_xml(self):
12 14 response = self.app.get(url('formatted_admin_settings', format='xml'))
13 15
14 16 def test_create(self):
15 17 response = self.app.post(url('admin_settings'))
16 18
17 19 def test_new(self):
18 20 response = self.app.get(url('admin_new_setting'))
19 21
20 22 def test_new_as_xml(self):
21 23 response = self.app.get(url('formatted_admin_new_setting', format='xml'))
22 24
23 25 def test_update(self):
24 26 response = self.app.put(url('admin_setting', setting_id=1))
25 27
26 28 def test_update_browser_fakeout(self):
27 29 response = self.app.post(url('admin_setting', setting_id=1), params=dict(_method='put'))
28 30
29 31 def test_delete(self):
30 32 response = self.app.delete(url('admin_setting', setting_id=1))
31 33
32 34 def test_delete_browser_fakeout(self):
33 35 response = self.app.post(url('admin_setting', setting_id=1), params=dict(_method='delete'))
34 36
35 37 def test_show(self):
36 38 response = self.app.get(url('admin_setting', setting_id=1))
37 39
38 40 def test_show_as_xml(self):
39 41 response = self.app.get(url('formatted_admin_setting', setting_id=1, format='xml'))
40 42
41 43 def test_edit(self):
42 44 response = self.app.get(url('admin_edit_setting', setting_id=1))
43 45
44 46 def test_edit_as_xml(self):
45 response = self.app.get(url('formatted_admin_edit_setting', setting_id=1, format='xml'))
47 response = self.app.get(url('formatted_admin_edit_setting',
48 setting_id=1, format='xml'))
46 49
47 50
48 51 def test_ga_code_active(self):
49 52 self.log_user()
50 53 old_title = 'RhodeCode'
51 54 old_realm = 'RhodeCode authentication'
52 55 new_ga_code = 'ga-test-123456789'
53 56 response = self.app.post(url('admin_setting', setting_id='global'),
54 57 params=dict(
55 58 _method='put',
56 59 rhodecode_title=old_title,
57 60 rhodecode_realm=old_realm,
58 61 rhodecode_ga_code=new_ga_code
59 62 ))
60 63
61 assert 'Updated application settings' in response.session['flash'][0][1], 'no flash message about success of change'
62 assert RhodeCodeSettings.get_app_settings()['rhodecode_ga_code'] == new_ga_code, 'change not in database'
64 self.assertTrue('Updated application settings' in
65 response.session['flash'][0][1])
66 self.assertEqual(RhodeCodeSettings
67 .get_app_settings()['rhodecode_ga_code'], new_ga_code)
63 68
64 69 response = response.follow()
65 assert """_gaq.push(['_setAccount', '%s']);""" % new_ga_code in response.body
70 self.assertTrue("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code
71 in response.body)
66 72
67 73 def test_ga_code_inactive(self):
68 74 self.log_user()
69 75 old_title = 'RhodeCode'
70 76 old_realm = 'RhodeCode authentication'
71 77 new_ga_code = ''
72 78 response = self.app.post(url('admin_setting', setting_id='global'),
73 79 params=dict(
74 80 _method='put',
75 81 rhodecode_title=old_title,
76 82 rhodecode_realm=old_realm,
77 83 rhodecode_ga_code=new_ga_code
78 84 ))
79 85
80 assert 'Updated application settings' in response.session['flash'][0][1], 'no flash message about success of change'
81 assert RhodeCodeSettings.get_app_settings()['rhodecode_ga_code'] == new_ga_code, 'change not in database'
86 self.assertTrue('Updated application settings' in
87 response.session['flash'][0][1])
88 self.assertEqual(RhodeCodeSettings
89 .get_app_settings()['rhodecode_ga_code'], new_ga_code)
82 90
83 91 response = response.follow()
84 assert """_gaq.push(['_setAccount', '%s']);""" % new_ga_code not in response.body
92 self.assertTrue("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code
93 not in response.body)
85 94
86 95
87 96 def test_title_change(self):
88 97 self.log_user()
89 98 old_title = 'RhodeCode'
90 99 new_title = old_title + '_changed'
91 100 old_realm = 'RhodeCode authentication'
92 response = self.app.post(url('admin_setting', setting_id='global'),
93 params=dict(
94 _method='put',
95 rhodecode_title=new_title,
96 rhodecode_realm=old_realm,
97 rhodecode_ga_code=''
98 ))
101
102 for new_title in ['Changed', 'Ε»Γ³Ε‚wik', old_title]:
103 response = self.app.post(url('admin_setting', setting_id='global'),
104 params=dict(
105 _method='put',
106 rhodecode_title=new_title,
107 rhodecode_realm=old_realm,
108 rhodecode_ga_code=''
109 ))
99 110
100 111
101 assert 'Updated application settings' in response.session['flash'][0][1], 'no flash message about success of change'
102 assert RhodeCodeSettings.get_app_settings()['rhodecode_title'] == new_title, 'change not in database'
112 self.assertTrue('Updated application settings' in
113 response.session['flash'][0][1])
114 self.assertEqual(RhodeCodeSettings
115 .get_app_settings()['rhodecode_title'],
116 new_title.decode('utf-8'))
103 117
104 response = response.follow()
105 assert """<h1><a href="/">%s</a></h1>""" % new_title in response.body
118 response = response.follow()
119 self.assertTrue("""<h1><a href="/">%s</a></h1>""" % new_title
120 in response.body)
106 121
107 122
108 123 def test_my_account(self):
109 124 self.log_user()
110 125 response = self.app.get(url('admin_settings_my_account'))
111 print response
112 assert 'value="test_admin' in response.body
126
127 self.assertTrue('value="test_admin' in response.body)
113 128
114 129 def test_my_account_update(self):
115 130 self.log_user()
116 131
117 132 new_email = 'new@mail.pl'
118 133 new_name = 'NewName'
119 134 new_lastname = 'NewLastname'
120 135 new_password = 'test123'
121 136
122 137
123 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
124 _method='put',
125 username='test_admin',
126 new_password=new_password,
127 password='',
128 name=new_name,
129 lastname=new_lastname,
130 email=new_email,))
138 response = self.app.post(url('admin_settings_my_account_update'),
139 params=dict(_method='put',
140 username='test_admin',
141 new_password=new_password,
142 password='',
143 name=new_name,
144 lastname=new_lastname,
145 email=new_email,))
131 146 response.follow()
132 147
133 148 assert 'Your account was updated successfully' in response.session['flash'][0][1], 'no flash message about success of change'
134 149 user = self.sa.query(User).filter(User.username == 'test_admin').one()
135 150 assert user.email == new_email , 'incorrect user email after update got %s vs %s' % (user.email, new_email)
136 151 assert user.name == new_name, 'updated field mismatch %s vs %s' % (user.name, new_name)
137 152 assert user.lastname == new_lastname, 'updated field mismatch %s vs %s' % (user.lastname, new_lastname)
138 153 assert check_password(new_password, user.password) is True, 'password field mismatch %s vs %s' % (user.password, new_password)
139 154
140 155 #bring back the admin settings
141 156 old_email = 'test_admin@mail.com'
142 157 old_name = 'RhodeCode'
143 158 old_lastname = 'Admin'
144 159 old_password = 'test12'
145 160
146 161 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
147 162 _method='put',
148 163 username='test_admin',
149 164 new_password=old_password,
150 165 password='',
151 166 name=old_name,
152 167 lastname=old_lastname,
153 168 email=old_email,))
154 169
155 170 response.follow()
156 171 assert 'Your account was updated successfully' in response.session['flash'][0][1], 'no flash message about success of change'
157 172 user = self.sa.query(User).filter(User.username == 'test_admin').one()
158 173 assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email)
159 174
160 175 assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email)
161 176 assert user.name == old_name, 'updated field mismatch %s vs %s' % (user.name, old_name)
162 177 assert user.lastname == old_lastname, 'updated field mismatch %s vs %s' % (user.lastname, old_lastname)
163 178 assert check_password(old_password, user.password) is True , 'password updated field mismatch %s vs %s' % (user.password, old_password)
164 179
165 180
166 181 def test_my_account_update_err_email_exists(self):
167 182 self.log_user()
168 183
169 184 new_email = 'test_regular@mail.com'#already exisitn email
170 185 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
171 186 _method='put',
172 187 username='test_admin',
173 188 new_password='test12',
174 189 name='NewName',
175 190 lastname='NewLastname',
176 191 email=new_email,))
177 192
178 193 assert 'This e-mail address is already taken' in response.body, 'Missing error message about existing email'
179 194
180 195
181 196 def test_my_account_update_err(self):
182 197 self.log_user('test_regular2', 'test12')
183 198
184 199 new_email = 'newmail.pl'
185 200 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
186 201 _method='put',
187 202 username='test_admin',
188 203 new_password='test12',
189 204 name='NewName',
190 205 lastname='NewLastname',
191 206 email=new_email,))
192 207 assert 'An email address must contain a single @' in response.body, 'Missing error message about wrong email'
193 208 assert 'This username already exists' in response.body, 'Missing error message about existing user'
@@ -1,230 +1,230 b''
1 1 # -*- coding: utf-8 -*-
2 2 from rhodecode.tests import *
3 3 from rhodecode.model.db import User
4 4 from rhodecode.lib.auth import check_password
5 5
6 6
7 7 class TestLoginController(TestController):
8 8
9 9 def test_index(self):
10 10 response = self.app.get(url(controller='login', action='index'))
11 11 assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status
12 12 # Test response...
13 13
14 14 def test_login_admin_ok(self):
15 15 response = self.app.post(url(controller='login', action='index'),
16 16 {'username':'test_admin',
17 17 'password':'test12'})
18 18 assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status
19 19 assert response.session['rhodecode_user'].username == 'test_admin', 'wrong logged in user'
20 20 response = response.follow()
21 21 assert '%s repository' % HG_REPO in response.body
22 22
23 23 def test_login_regular_ok(self):
24 24 response = self.app.post(url(controller='login', action='index'),
25 25 {'username':'test_regular',
26 26 'password':'test12'})
27 27 print response
28 28 assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status
29 29 assert response.session['rhodecode_user'].username == 'test_regular', 'wrong logged in user'
30 30 response = response.follow()
31 31 assert '%s repository' % HG_REPO in response.body
32 32 assert '<a title="Admin" href="/_admin">' not in response.body
33 33
34 34 def test_login_ok_came_from(self):
35 35 test_came_from = '/_admin/users'
36 36 response = self.app.post(url(controller='login', action='index', came_from=test_came_from),
37 37 {'username':'test_admin',
38 38 'password':'test12'})
39 39 assert response.status == '302 Found', 'Wrong response code from came from redirection'
40 40 response = response.follow()
41 41
42 42 assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status
43 43 assert 'Users administration' in response.body, 'No proper title in response'
44 44
45 45
46 46 def test_login_short_password(self):
47 47 response = self.app.post(url(controller='login', action='index'),
48 {'username':'error',
49 'password':'test'})
50 assert response.status == '200 OK', 'Wrong response from login page'
48 {'username':'test_admin',
49 'password':'as'})
50 self.assertEqual(response.status, '200 OK')
51 51 print response.body
52 assert 'Enter 6 characters or more' in response.body, 'No error password message in response'
52 self.assertTrue('Enter 3 characters or more' in response.body)
53 53
54 54 def test_login_wrong_username_password(self):
55 55 response = self.app.post(url(controller='login', action='index'),
56 56 {'username':'error',
57 57 'password':'test12'})
58 58 assert response.status == '200 OK', 'Wrong response from login page'
59 59
60 60 assert 'invalid user name' in response.body, 'No error username message in response'
61 61 assert 'invalid password' in response.body, 'No error password message in response'
62 62
63 63 #==========================================================================
64 64 # REGISTRATIONS
65 65 #==========================================================================
66 66 def test_register(self):
67 67 response = self.app.get(url(controller='login', action='register'))
68 68 assert 'Sign Up to RhodeCode' in response.body, 'wrong page for user registration'
69 69
70 70 def test_register_err_same_username(self):
71 71 response = self.app.post(url(controller='login', action='register'),
72 72 {'username':'test_admin',
73 73 'password':'test12',
74 74 'password_confirmation':'test12',
75 75 'email':'goodmail@domain.com',
76 76 'name':'test',
77 77 'lastname':'test'})
78 78
79 79 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
80 80 assert 'This username already exists' in response.body
81 81
82 82 def test_register_err_same_email(self):
83 83 response = self.app.post(url(controller='login', action='register'),
84 84 {'username':'test_admin_0',
85 85 'password':'test12',
86 86 'password_confirmation':'test12',
87 87 'email':'test_admin@mail.com',
88 88 'name':'test',
89 89 'lastname':'test'})
90 90
91 91 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
92 92 assert 'This e-mail address is already taken' in response.body
93 93
94 94 def test_register_err_same_email_case_sensitive(self):
95 95 response = self.app.post(url(controller='login', action='register'),
96 96 {'username':'test_admin_1',
97 97 'password':'test12',
98 98 'password_confirmation':'test12',
99 99 'email':'TesT_Admin@mail.COM',
100 100 'name':'test',
101 101 'lastname':'test'})
102 102 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
103 103 assert 'This e-mail address is already taken' in response.body
104 104
105 105 def test_register_err_wrong_data(self):
106 106 response = self.app.post(url(controller='login', action='register'),
107 107 {'username':'xs',
108 108 'password':'test',
109 109 'password_confirmation':'test',
110 110 'email':'goodmailm',
111 111 'name':'test',
112 112 'lastname':'test'})
113 113 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
114 114 assert 'An email address must contain a single @' in response.body
115 115 assert 'Enter a value 6 characters long or more' in response.body
116 116
117 117
118 118 def test_register_err_username(self):
119 119 response = self.app.post(url(controller='login', action='register'),
120 120 {'username':'error user',
121 121 'password':'test12',
122 122 'password_confirmation':'test12',
123 123 'email':'goodmailm',
124 124 'name':'test',
125 125 'lastname':'test'})
126 126
127 127 print response.body
128 128 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
129 129 assert 'An email address must contain a single @' in response.body
130 130 assert ('Username may only contain '
131 131 'alphanumeric characters underscores, '
132 132 'periods or dashes and must begin with '
133 133 'alphanumeric character') in response.body
134 134
135 135 def test_register_err_case_sensitive(self):
136 136 response = self.app.post(url(controller='login', action='register'),
137 137 {'username':'Test_Admin',
138 138 'password':'test12',
139 139 'password_confirmation':'test12',
140 140 'email':'goodmailm',
141 141 'name':'test',
142 142 'lastname':'test'})
143 143
144 144 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
145 145 assert 'An email address must contain a single @' in response.body
146 146 assert 'This username already exists' in response.body
147 147
148 148
149 149
150 150 def test_register_special_chars(self):
151 151 response = self.app.post(url(controller='login', action='register'),
152 152 {'username':'xxxaxn',
153 153 'password':'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
154 154 'password_confirmation':'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
155 155 'email':'goodmailm@test.plx',
156 156 'name':'test',
157 157 'lastname':'test'})
158 158
159 159 print response.body
160 160 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
161 161 assert 'Invalid characters in password' in response.body
162 162
163 163
164 164 def test_register_password_mismatch(self):
165 165 response = self.app.post(url(controller='login', action='register'),
166 166 {'username':'xs',
167 167 'password':'123qwe',
168 168 'password_confirmation':'qwe123',
169 169 'email':'goodmailm@test.plxa',
170 170 'name':'test',
171 171 'lastname':'test'})
172 172
173 173 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
174 174 print response.body
175 175 assert 'Password do not match' in response.body
176 176
177 177 def test_register_ok(self):
178 178 username = 'test_regular4'
179 179 password = 'qweqwe'
180 180 email = 'marcin@test.com'
181 181 name = 'testname'
182 182 lastname = 'testlastname'
183 183
184 184 response = self.app.post(url(controller='login', action='register'),
185 185 {'username':username,
186 186 'password':password,
187 187 'password_confirmation':password,
188 188 'email':email,
189 189 'name':name,
190 190 'lastname':lastname})
191 191 assert response.status == '302 Found', 'Wrong response from register page got %s' % response.status
192 192 assert 'You have successfully registered into rhodecode' in response.session['flash'][0], 'No flash message about user registration'
193 193
194 194 ret = self.sa.query(User).filter(User.username == 'test_regular4').one()
195 195 assert ret.username == username , 'field mismatch %s %s' % (ret.username, username)
196 196 assert check_password(password, ret.password) == True , 'password mismatch'
197 197 assert ret.email == email , 'field mismatch %s %s' % (ret.email, email)
198 198 assert ret.name == name , 'field mismatch %s %s' % (ret.name, name)
199 199 assert ret.lastname == lastname , 'field mismatch %s %s' % (ret.lastname, lastname)
200 200
201 201
202 202 def test_forgot_password_wrong_mail(self):
203 203 response = self.app.post(url(controller='login', action='password_reset'),
204 204 {'email':'marcin@wrongmail.org', })
205 205
206 206 assert "This e-mail address doesn't exist" in response.body, 'Missing error message about wrong email'
207 207
208 208 def test_forgot_password(self):
209 209 response = self.app.get(url(controller='login', action='password_reset'))
210 210 assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status
211 211
212 212 username = 'test_password_reset_1'
213 213 password = 'qweqwe'
214 214 email = 'marcin@python-works.com'
215 215 name = 'passwd'
216 216 lastname = 'reset'
217 217
218 218 response = self.app.post(url(controller='login', action='register'),
219 219 {'username':username,
220 220 'password':password,
221 221 'password_confirmation':password,
222 222 'email':email,
223 223 'name':name,
224 224 'lastname':lastname})
225 225 #register new user for email test
226 226 response = self.app.post(url(controller='login', action='password_reset'),
227 227 {'email':email, })
228 228 print response.session['flash']
229 229 assert 'You have successfully registered into rhodecode' in response.session['flash'][0], 'No flash message about user registration'
230 230 assert 'Your new password was sent' in response.session['flash'][1], 'No flash message about password reset'
@@ -1,31 +1,47 b''
1 1 from rhodecode.tests import *
2 2 from rhodecode.model.db import Repository
3 3 from rhodecode.lib.utils import invalidate_cache
4 4
5 5 class TestSummaryController(TestController):
6 6
7 7 def test_index(self):
8 8 self.log_user()
9 response = self.app.get(url(controller='summary', action='index', repo_name=HG_REPO))
9 response = self.app.get(url(controller='summary',
10 action='index', repo_name=HG_REPO))
10 11
11 12 #repo type
12 assert """<img style="margin-bottom:2px" class="icon" title="Mercurial repository" alt="Mercurial repository" src="/images/icons/hgicon.png"/>""" in response.body
13 assert """<img style="margin-bottom:2px" class="icon" title="public repository" alt="public repository" src="/images/icons/lock_open.png"/>""" in response.body
13 self.assertTrue("""<img style="margin-bottom:2px" class="icon" """
14 """title="Mercurial repository" alt="Mercurial """
15 """repository" src="/images/icons/hgicon.png"/>"""
16 in response.body)
17 self.assertTrue("""<img style="margin-bottom:2px" class="icon" """
18 """title="public repository" alt="public """
19 """repository" src="/images/icons/lock_open.png"/>"""
20 in response.body)
14 21
15 22 #codes stats
23 self._enable_stats()
16 24
17 25
18 self._enable_stats()
19 26 invalidate_cache('get_repo_cached_%s' % HG_REPO)
20 response = self.app.get(url(controller='summary', action='index', repo_name=HG_REPO))
21 assert """var data = {"Python": 42, "Rst": 11, "Bash": 2, "Makefile": 1, "Batch": 1, "Ini": 1, "Css": 1};""" in response.body, 'wrong info about % of codes stats'
27 response = self.app.get(url(controller='summary', action='index',
28 repo_name=HG_REPO))
29
30 self.assertTrue("""var data = {"py": {"count": 42, "desc": """
31 """["Python"]}, "rst": {"count": 11, "desc": """
32 """["Rst"]}, "sh": {"count": 2, "desc": ["Bash"]}, """
33 """"makefile": {"count": 1, "desc": ["Makefile", """
34 """"Makefile"]}, "cfg": {"count": 1, "desc": ["Ini"]},"""
35 """ "css": {"count": 1, "desc": ["Css"]}, "bat": """
36 """{"count": 1, "desc": ["Batch"]}};"""
37 in response.body)
22 38
23 39 # clone url...
24 assert """<input type="text" id="clone_url" readonly="readonly" value="hg clone http://test_admin@localhost:80/%s" size="70"/>""" % HG_REPO in response.body
40 self.assertTrue("""<input type="text" id="clone_url" readonly="readonly" value="hg clone http://test_admin@localhost:80/%s" size="70"/>""" % HG_REPO in response.body)
25 41
26 42
27 43 def _enable_stats(self):
28 44 r = Repository.by_repo_name(HG_REPO)
29 45 r.enable_statistics = True
30 46 self.sa.add(r)
31 47 self.sa.commit()
@@ -1,206 +1,207 b''
1 1 ################################################################################
2 2 ################################################################################
3 3 # RhodeCode - Pylons environment configuration #
4 4 # #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 ################################################################################
7 7
8 8 [DEFAULT]
9 9 debug = true
10 pdebug = false
10 11 ################################################################################
11 12 ## Uncomment and replace with the address which should receive ##
12 13 ## any error reports after application crash ##
13 14 ## Additionally those settings will be used by RhodeCode mailing system ##
14 15 ################################################################################
15 16 #email_to = admin@localhost
16 17 #error_email_from = paste_error@localhost
17 18 #app_email_from = rhodecode-noreply@localhost
18 19 #error_message =
19 20
20 21 #smtp_server = mail.server.com
21 22 #smtp_username =
22 23 #smtp_password =
23 24 #smtp_port =
24 25 #smtp_use_tls = false
25 26
26 27 [server:main]
27 28 ##nr of threads to spawn
28 29 threadpool_workers = 5
29 30
30 31 ##max request before thread respawn
31 32 threadpool_max_requests = 2
32 33
33 34 ##option to use threads of process
34 35 use_threadpool = true
35 36
36 37 use = egg:Paste#http
37 38 host = 127.0.0.1
38 39 port = 5000
39 40
40 41 [app:main]
41 42 use = egg:rhodecode
42 43 full_stack = true
43 44 static_files = true
44 45 lang=en
45 46 cache_dir = /tmp/data
46 47 index_dir = /tmp/index
47 48 app_instance_uuid = develop-test
48 49 cut_off_limit = 256000
49 50 force_https = false
50 51 commit_parse_limit = 25
51 52
52 53 ####################################
53 54 ### CELERY CONFIG ####
54 55 ####################################
55 56 use_celery = false
56 57 broker.host = localhost
57 58 broker.vhost = rabbitmqhost
58 59 broker.port = 5672
59 60 broker.user = rabbitmq
60 61 broker.password = qweqwe
61 62
62 63 celery.imports = rhodecode.lib.celerylib.tasks
63 64
64 65 celery.result.backend = amqp
65 66 celery.result.dburi = amqp://
66 67 celery.result.serialier = json
67 68
68 69 #celery.send.task.error.emails = true
69 70 #celery.amqp.task.result.expires = 18000
70 71
71 72 celeryd.concurrency = 2
72 73 #celeryd.log.file = celeryd.log
73 74 celeryd.log.level = debug
74 75 celeryd.max.tasks.per.child = 1
75 76
76 77 #tasks will never be sent to the queue, but executed locally instead.
77 78 celery.always.eager = false
78 79
79 80 ####################################
80 81 ### BEAKER CACHE ####
81 82 ####################################
82 83 beaker.cache.data_dir=/tmp/data/cache/data
83 84 beaker.cache.lock_dir=/tmp/data/cache/lock
84 85 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
85 86
86 87 beaker.cache.super_short_term.type=memory
87 88 beaker.cache.super_short_term.expire=10
88 89
89 90 beaker.cache.short_term.type=memory
90 91 beaker.cache.short_term.expire=60
91 92
92 93 beaker.cache.long_term.type=memory
93 94 beaker.cache.long_term.expire=36000
94 95
95 96
96 97 beaker.cache.sql_cache_short.type=memory
97 98 beaker.cache.sql_cache_short.expire=10
98 99
99 100 beaker.cache.sql_cache_med.type=memory
100 101 beaker.cache.sql_cache_med.expire=360
101 102
102 103 beaker.cache.sql_cache_long.type=file
103 104 beaker.cache.sql_cache_long.expire=3600
104 105
105 106 ####################################
106 107 ### BEAKER SESSION ####
107 108 ####################################
108 109 ## Type of storage used for the session, current types are
109 110 ## dbm, file, memcached, database, and memory.
110 111 ## The storage uses the Container API
111 112 ##that is also used by the cache system.
112 113 beaker.session.type = file
113 114
114 115 beaker.session.key = rhodecode
115 116 beaker.session.secret = g654dcno0-9873jhgfreyu
116 117 beaker.session.timeout = 36000
117 118
118 119 ##auto save the session to not to use .save()
119 120 beaker.session.auto = False
120 121
121 122 ##true exire at browser close
122 123 #beaker.session.cookie_expires = 3600
123 124
124 125
125 126 ################################################################################
126 127 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
127 128 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
128 129 ## execute malicious code after an exception is raised. ##
129 130 ################################################################################
130 131 #set debug = false
131 132
132 133 ##################################
133 134 ### LOGVIEW CONFIG ###
134 135 ##################################
135 136 logview.sqlalchemy = #faa
136 137 logview.pylons.templating = #bfb
137 138 logview.pylons.util = #eee
138 139
139 140 #########################################################
140 141 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
141 142 #########################################################
142 143 #sqlalchemy.db1.url = sqlite:///%(here)s/test.db
143 144 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode_tests
144 145 #sqlalchemy.db1.echo = False
145 146 #sqlalchemy.db1.pool_recycle = 3600
146 147 sqlalchemy.convert_unicode = true
147 148
148 149 ################################
149 150 ### LOGGING CONFIGURATION ####
150 151 ################################
151 152 [loggers]
152 153 keys = root, routes, rhodecode, sqlalchemy,beaker,templates
153 154
154 155 [handlers]
155 156 keys = console
156 157
157 158 [formatters]
158 159 keys = generic,color_formatter
159 160
160 161 #############
161 162 ## LOGGERS ##
162 163 #############
163 164 [logger_root]
164 165 level = ERROR
165 166 handlers = console
166 167
167 168 [logger_routes]
168 169 level = ERROR
169 170 handlers = console
170 171 qualname = routes.middleware
171 172 # "level = DEBUG" logs the route matched and routing variables.
172 173
173 174 [logger_rhodecode]
174 175 level = ERROR
175 176 handlers = console
176 177 qualname = rhodecode
177 178 propagate = 0
178 179
179 180 [logger_sqlalchemy]
180 181 level = ERROR
181 182 handlers = console
182 183 qualname = sqlalchemy.engine
183 184 propagate = 0
184 185
185 186 ##############
186 187 ## HANDLERS ##
187 188 ##############
188 189
189 190 [handler_console]
190 191 class = StreamHandler
191 192 args = (sys.stderr,)
192 193 level = NOTSET
193 194 formatter = color_formatter
194 195
195 196 ################
196 197 ## FORMATTERS ##
197 198 ################
198 199
199 200 [formatter_generic]
200 201 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
201 202 datefmt = %Y-%m-%d %H:%M:%S
202 203
203 204 [formatter_color_formatter]
204 205 class=rhodecode.lib.colored_formatter.ColorFormatter
205 206 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
206 207 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now