##// END OF EJS Templates
filter out repo groups choices to only ones that you have write+ access to. Before it was read+ access and you got proper...
marcink -
r3239:a9565b8b beta
parent child Browse files
Show More
@@ -1,490 +1,492 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.repos
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Repositories controller for RhodeCode
7 7
8 8 :created_on: Apr 7, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 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 formencode import htmlfill
30 30
31 31 from webob.exc import HTTPInternalServerError
32 32 from pylons import request, session, tmpl_context as c, url
33 33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 35 from sqlalchemy.exc import IntegrityError
36 36
37 37 import rhodecode
38 38 from rhodecode.lib import helpers as h
39 39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 40 HasPermissionAnyDecorator, HasRepoPermissionAllDecorator
41 41 from rhodecode.lib.base import BaseController, render
42 42 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
43 43 from rhodecode.lib.helpers import get_token
44 44 from rhodecode.model.meta import Session
45 45 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup,\
46 46 RhodeCodeSetting
47 47 from rhodecode.model.forms import RepoForm
48 from rhodecode.model.scm import ScmModel
48 from rhodecode.model.scm import ScmModel, GroupList
49 49 from rhodecode.model.repo import RepoModel
50 50 from rhodecode.lib.compat import json
51 51 from sqlalchemy.sql.expression import func
52 52
53 53 log = logging.getLogger(__name__)
54 54
55 55
56 56 class ReposController(BaseController):
57 57 """
58 58 REST Controller styled on the Atom Publishing Protocol"""
59 59 # To properly map this controller, ensure your config/routing.py
60 60 # file has a resource setup:
61 61 # map.resource('repo', 'repos')
62 62
63 63 @LoginRequired()
64 64 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
65 65 def __before__(self):
66 66 c.admin_user = session.get('admin_user')
67 67 c.admin_username = session.get('admin_username')
68 68 super(ReposController, self).__before__()
69 69
70 70 def __load_defaults(self):
71 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
71 acl_groups = GroupList(RepoGroup.query().all(),
72 perm_set=['group.write', 'group.admin'])
73 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
72 74 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
73 75
74 76 repo_model = RepoModel()
75 77 c.users_array = repo_model.get_users_js()
76 78 c.users_groups_array = repo_model.get_users_groups_js()
77 79 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
78 80 c.landing_revs_choices = choices
79 81
80 82 def __load_data(self, repo_name=None):
81 83 """
82 84 Load defaults settings for edit, and update
83 85
84 86 :param repo_name:
85 87 """
86 88 self.__load_defaults()
87 89
88 90 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
89 91 repo = db_repo.scm_instance
90 92
91 93 if c.repo_info is None:
92 94 h.not_mapped_error(repo_name)
93 95 return redirect(url('repos'))
94 96
95 97 ##override defaults for exact repo info here git/hg etc
96 98 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
97 99 c.landing_revs_choices = choices
98 100
99 101 c.default_user_id = User.get_by_username('default').user_id
100 102 c.in_public_journal = UserFollowing.query()\
101 103 .filter(UserFollowing.user_id == c.default_user_id)\
102 104 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
103 105
104 106 if c.repo_info.stats:
105 107 # this is on what revision we ended up so we add +1 for count
106 108 last_rev = c.repo_info.stats.stat_on_revision + 1
107 109 else:
108 110 last_rev = 0
109 111 c.stats_revision = last_rev
110 112
111 113 c.repo_last_rev = repo.count() if repo.revisions else 0
112 114
113 115 if last_rev == 0 or c.repo_last_rev == 0:
114 116 c.stats_percentage = 0
115 117 else:
116 118 c.stats_percentage = '%.2f' % ((float((last_rev)) /
117 119 c.repo_last_rev) * 100)
118 120
119 121 defaults = RepoModel()._get_defaults(repo_name)
120 122
121 123 c.repos_list = [('', _('--REMOVE FORK--'))]
122 124 c.repos_list += [(x.repo_id, x.repo_name) for x in
123 125 Repository.query().order_by(Repository.repo_name).all()
124 126 if x.repo_id != c.repo_info.repo_id]
125 127
126 128 defaults['id_fork_of'] = db_repo.fork.repo_id if db_repo.fork else ''
127 129 return defaults
128 130
129 131 @HasPermissionAllDecorator('hg.admin')
130 132 def index(self, format='html'):
131 133 """GET /repos: All items in the collection"""
132 134 # url('repos')
133 135
134 136 c.repos_list = Repository.query()\
135 137 .order_by(func.lower(Repository.repo_name))\
136 138 .all()
137 139
138 140 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
139 141 admin=True)
140 142 #json used to render the grid
141 143 c.data = json.dumps(repos_data)
142 144
143 145 return render('admin/repos/repos.html')
144 146
145 147 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
146 148 def create(self):
147 149 """
148 150 POST /repos: Create a new item"""
149 151 # url('repos')
150 152
151 153 self.__load_defaults()
152 154 form_result = {}
153 155 try:
154 156 form_result = RepoForm(repo_groups=c.repo_groups_choices,
155 157 landing_revs=c.landing_revs_choices)()\
156 158 .to_python(dict(request.POST))
157 159 new_repo = RepoModel().create(form_result,
158 160 self.rhodecode_user.user_id)
159 161 if form_result['clone_uri']:
160 162 h.flash(_('created repository %s from %s') \
161 163 % (form_result['repo_name'], form_result['clone_uri']),
162 164 category='success')
163 165 else:
164 166 h.flash(_('created repository %s') % form_result['repo_name'],
165 167 category='success')
166 168
167 169 if request.POST.get('user_created'):
168 170 # created by regular non admin user
169 171 action_logger(self.rhodecode_user, 'user_created_repo',
170 172 form_result['repo_name_full'], self.ip_addr,
171 173 self.sa)
172 174 else:
173 175 action_logger(self.rhodecode_user, 'admin_created_repo',
174 176 form_result['repo_name_full'], self.ip_addr,
175 177 self.sa)
176 178 Session().commit()
177 179 except formencode.Invalid, errors:
178 180
179 181 c.new_repo = errors.value['repo_name']
180 182
181 183 if request.POST.get('user_created'):
182 184 r = render('admin/repos/repo_add_create_repository.html')
183 185 else:
184 186 r = render('admin/repos/repo_add.html')
185 187
186 188 return htmlfill.render(
187 189 r,
188 190 defaults=errors.value,
189 191 errors=errors.error_dict or {},
190 192 prefix_error=False,
191 193 encoding="UTF-8")
192 194
193 195 except Exception:
194 196 log.error(traceback.format_exc())
195 197 msg = _('error occurred during creation of repository %s') \
196 198 % form_result.get('repo_name')
197 199 h.flash(msg, category='error')
198 200 return redirect(url('repos'))
199 201 #redirect to our new repo !
200 202 return redirect(url('summary_home', repo_name=new_repo.repo_name))
201 203
202 204 @HasPermissionAllDecorator('hg.admin')
203 205 def new(self, format='html'):
204 206 """
205 207 WARNING: this function is depracated see settings.create_repo !!
206 208
207 209 GET /repos/new: Form to create a new item
208 210 """
209 211
210 212 new_repo = request.GET.get('repo', '')
211 213 parent_group = request.GET.get('parent_group')
212 214
213 215 c.new_repo = repo_name_slug(new_repo)
214 216 self.__load_defaults()
215 217 ## apply the defaults from defaults page
216 218 defaults = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
217 219 if parent_group:
218 220 defaults.update({'repo_group': parent_group})
219 221
220 222 return htmlfill.render(
221 223 render('admin/repos/repo_add.html'),
222 224 defaults=defaults,
223 225 errors={},
224 226 prefix_error=False,
225 227 encoding="UTF-8"
226 228 )
227 229
228 230 @HasPermissionAllDecorator('hg.admin')
229 231 def update(self, repo_name):
230 232 """
231 233 PUT /repos/repo_name: Update an existing item"""
232 234 # Forms posted to this method should contain a hidden field:
233 235 # <input type="hidden" name="_method" value="PUT" />
234 236 # Or using helpers:
235 237 # h.form(url('repo', repo_name=ID),
236 238 # method='put')
237 239 # url('repo', repo_name=ID)
238 240 self.__load_defaults()
239 241 repo_model = RepoModel()
240 242 changed_name = repo_name
241 243 #override the choices with extracted revisions !
242 244 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
243 245 c.landing_revs_choices = choices
244 246
245 247 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
246 248 repo_groups=c.repo_groups_choices,
247 249 landing_revs=c.landing_revs_choices)()
248 250 try:
249 251 form_result = _form.to_python(dict(request.POST))
250 252 repo = repo_model.update(repo_name, **form_result)
251 253 invalidate_cache('get_repo_cached_%s' % repo_name)
252 254 h.flash(_('Repository %s updated successfully') % repo_name,
253 255 category='success')
254 256 changed_name = repo.repo_name
255 257 action_logger(self.rhodecode_user, 'admin_updated_repo',
256 258 changed_name, self.ip_addr, self.sa)
257 259 Session().commit()
258 260 except formencode.Invalid, errors:
259 261 defaults = self.__load_data(repo_name)
260 262 defaults.update(errors.value)
261 263 return htmlfill.render(
262 264 render('admin/repos/repo_edit.html'),
263 265 defaults=defaults,
264 266 errors=errors.error_dict or {},
265 267 prefix_error=False,
266 268 encoding="UTF-8")
267 269
268 270 except Exception:
269 271 log.error(traceback.format_exc())
270 272 h.flash(_('error occurred during update of repository %s') \
271 273 % repo_name, category='error')
272 274 return redirect(url('edit_repo', repo_name=changed_name))
273 275
274 276 @HasPermissionAllDecorator('hg.admin')
275 277 def delete(self, repo_name):
276 278 """
277 279 DELETE /repos/repo_name: Delete an existing item"""
278 280 # Forms posted to this method should contain a hidden field:
279 281 # <input type="hidden" name="_method" value="DELETE" />
280 282 # Or using helpers:
281 283 # h.form(url('repo', repo_name=ID),
282 284 # method='delete')
283 285 # url('repo', repo_name=ID)
284 286
285 287 repo_model = RepoModel()
286 288 repo = repo_model.get_by_repo_name(repo_name)
287 289 if not repo:
288 290 h.not_mapped_error(repo_name)
289 291 return redirect(url('repos'))
290 292 try:
291 293 action_logger(self.rhodecode_user, 'admin_deleted_repo',
292 294 repo_name, self.ip_addr, self.sa)
293 295 repo_model.delete(repo)
294 296 invalidate_cache('get_repo_cached_%s' % repo_name)
295 297 h.flash(_('deleted repository %s') % repo_name, category='success')
296 298 Session().commit()
297 299 except IntegrityError, e:
298 300 if e.message.find('repositories_fork_id_fkey') != -1:
299 301 log.error(traceback.format_exc())
300 302 h.flash(_('Cannot delete %s it still contains attached '
301 303 'forks') % repo_name,
302 304 category='warning')
303 305 else:
304 306 log.error(traceback.format_exc())
305 307 h.flash(_('An error occurred during '
306 308 'deletion of %s') % repo_name,
307 309 category='error')
308 310
309 311 except Exception, e:
310 312 log.error(traceback.format_exc())
311 313 h.flash(_('An error occurred during deletion of %s') % repo_name,
312 314 category='error')
313 315
314 316 return redirect(url('repos'))
315 317
316 318 @HasRepoPermissionAllDecorator('repository.admin')
317 319 def delete_perm_user(self, repo_name):
318 320 """
319 321 DELETE an existing repository permission user
320 322
321 323 :param repo_name:
322 324 """
323 325 try:
324 326 RepoModel().revoke_user_permission(repo=repo_name,
325 327 user=request.POST['user_id'])
326 328 Session().commit()
327 329 except Exception:
328 330 log.error(traceback.format_exc())
329 331 h.flash(_('An error occurred during deletion of repository user'),
330 332 category='error')
331 333 raise HTTPInternalServerError()
332 334
333 335 @HasRepoPermissionAllDecorator('repository.admin')
334 336 def delete_perm_users_group(self, repo_name):
335 337 """
336 338 DELETE an existing repository permission users group
337 339
338 340 :param repo_name:
339 341 """
340 342
341 343 try:
342 344 RepoModel().revoke_users_group_permission(
343 345 repo=repo_name, group_name=request.POST['users_group_id']
344 346 )
345 347 Session().commit()
346 348 except Exception:
347 349 log.error(traceback.format_exc())
348 350 h.flash(_('An error occurred during deletion of repository'
349 351 ' users groups'),
350 352 category='error')
351 353 raise HTTPInternalServerError()
352 354
353 355 @HasPermissionAllDecorator('hg.admin')
354 356 def repo_stats(self, repo_name):
355 357 """
356 358 DELETE an existing repository statistics
357 359
358 360 :param repo_name:
359 361 """
360 362
361 363 try:
362 364 RepoModel().delete_stats(repo_name)
363 365 Session().commit()
364 366 except Exception, e:
365 367 log.error(traceback.format_exc())
366 368 h.flash(_('An error occurred during deletion of repository stats'),
367 369 category='error')
368 370 return redirect(url('edit_repo', repo_name=repo_name))
369 371
370 372 @HasPermissionAllDecorator('hg.admin')
371 373 def repo_cache(self, repo_name):
372 374 """
373 375 INVALIDATE existing repository cache
374 376
375 377 :param repo_name:
376 378 """
377 379
378 380 try:
379 381 ScmModel().mark_for_invalidation(repo_name)
380 382 Session().commit()
381 383 except Exception, e:
382 384 log.error(traceback.format_exc())
383 385 h.flash(_('An error occurred during cache invalidation'),
384 386 category='error')
385 387 return redirect(url('edit_repo', repo_name=repo_name))
386 388
387 389 @HasPermissionAllDecorator('hg.admin')
388 390 def repo_locking(self, repo_name):
389 391 """
390 392 Unlock repository when it is locked !
391 393
392 394 :param repo_name:
393 395 """
394 396
395 397 try:
396 398 repo = Repository.get_by_repo_name(repo_name)
397 399 if request.POST.get('set_lock'):
398 400 Repository.lock(repo, c.rhodecode_user.user_id)
399 401 elif request.POST.get('set_unlock'):
400 402 Repository.unlock(repo)
401 403 except Exception, e:
402 404 log.error(traceback.format_exc())
403 405 h.flash(_('An error occurred during unlocking'),
404 406 category='error')
405 407 return redirect(url('edit_repo', repo_name=repo_name))
406 408
407 409 @HasPermissionAllDecorator('hg.admin')
408 410 def repo_public_journal(self, repo_name):
409 411 """
410 412 Set's this repository to be visible in public journal,
411 413 in other words assing default user to follow this repo
412 414
413 415 :param repo_name:
414 416 """
415 417
416 418 cur_token = request.POST.get('auth_token')
417 419 token = get_token()
418 420 if cur_token == token:
419 421 try:
420 422 repo_id = Repository.get_by_repo_name(repo_name).repo_id
421 423 user_id = User.get_by_username('default').user_id
422 424 self.scm_model.toggle_following_repo(repo_id, user_id)
423 425 h.flash(_('Updated repository visibility in public journal'),
424 426 category='success')
425 427 Session().commit()
426 428 except:
427 429 h.flash(_('An error occurred during setting this'
428 430 ' repository in public journal'),
429 431 category='error')
430 432
431 433 else:
432 434 h.flash(_('Token mismatch'), category='error')
433 435 return redirect(url('edit_repo', repo_name=repo_name))
434 436
435 437 @HasPermissionAllDecorator('hg.admin')
436 438 def repo_pull(self, repo_name):
437 439 """
438 440 Runs task to update given repository with remote changes,
439 441 ie. make pull on remote location
440 442
441 443 :param repo_name:
442 444 """
443 445 try:
444 446 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
445 447 h.flash(_('Pulled from remote location'), category='success')
446 448 except Exception, e:
447 449 h.flash(_('An error occurred during pull from remote location'),
448 450 category='error')
449 451
450 452 return redirect(url('edit_repo', repo_name=repo_name))
451 453
452 454 @HasPermissionAllDecorator('hg.admin')
453 455 def repo_as_fork(self, repo_name):
454 456 """
455 457 Mark given repository as a fork of another
456 458
457 459 :param repo_name:
458 460 """
459 461 try:
460 462 fork_id = request.POST.get('id_fork_of')
461 463 repo = ScmModel().mark_as_fork(repo_name, fork_id,
462 464 self.rhodecode_user.username)
463 465 fork = repo.fork.repo_name if repo.fork else _('Nothing')
464 466 Session().commit()
465 467 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
466 468 category='success')
467 469 except Exception, e:
468 470 log.error(traceback.format_exc())
469 471 h.flash(_('An error occurred during this operation'),
470 472 category='error')
471 473
472 474 return redirect(url('edit_repo', repo_name=repo_name))
473 475
474 476 @HasPermissionAllDecorator('hg.admin')
475 477 def show(self, repo_name, format='html'):
476 478 """GET /repos/repo_name: Show a specific item"""
477 479 # url('repo', repo_name=ID)
478 480
479 481 @HasPermissionAllDecorator('hg.admin')
480 482 def edit(self, repo_name, format='html'):
481 483 """GET /repos/repo_name/edit: Form to edit an existing item"""
482 484 # url('edit_repo', repo_name=ID)
483 485 defaults = self.__load_data(repo_name)
484 486
485 487 return htmlfill.render(
486 488 render('admin/repos/repo_edit.html'),
487 489 defaults=defaults,
488 490 encoding="UTF-8",
489 491 force_defaults=False
490 492 )
@@ -1,529 +1,531 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) 2010-2012 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 import pkg_resources
30 30 import platform
31 31
32 32 from sqlalchemy import func
33 33 from formencode import htmlfill
34 34 from pylons import request, session, tmpl_context as c, url, config
35 35 from pylons.controllers.util import abort, redirect
36 36 from pylons.i18n.translation import _
37 37
38 38 from rhodecode.lib import helpers as h
39 39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 40 HasPermissionAnyDecorator, NotAnonymous
41 41 from rhodecode.lib.base import BaseController, render
42 42 from rhodecode.lib.celerylib import tasks, run_task
43 43 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
44 44 set_rhodecode_config, repo_name_slug, check_git_version
45 45 from rhodecode.model.db import RhodeCodeUi, Repository, RepoGroup, \
46 46 RhodeCodeSetting, PullRequest, PullRequestReviewers
47 47 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
48 48 ApplicationUiSettingsForm, ApplicationVisualisationForm
49 from rhodecode.model.scm import ScmModel
49 from rhodecode.model.scm import ScmModel, GroupList
50 50 from rhodecode.model.user import UserModel
51 51 from rhodecode.model.repo import RepoModel
52 52 from rhodecode.model.db import User
53 53 from rhodecode.model.notification import EmailNotificationModel
54 54 from rhodecode.model.meta import Session
55 55 from rhodecode.lib.utils2 import str2bool, safe_unicode
56 56 from rhodecode.lib.compat import json
57 57 log = logging.getLogger(__name__)
58 58
59 59
60 60 class SettingsController(BaseController):
61 61 """REST Controller styled on the Atom Publishing Protocol"""
62 62 # To properly map this controller, ensure your config/routing.py
63 63 # file has a resource setup:
64 64 # map.resource('setting', 'settings', controller='admin/settings',
65 65 # path_prefix='/admin', name_prefix='admin_')
66 66
67 67 @LoginRequired()
68 68 def __before__(self):
69 69 c.admin_user = session.get('admin_user')
70 70 c.admin_username = session.get('admin_username')
71 71 c.modules = sorted([(p.project_name, p.version)
72 72 for p in pkg_resources.working_set]
73 73 + [('git', check_git_version())],
74 74 key=lambda k: k[0].lower())
75 75 c.py_version = platform.python_version()
76 76 c.platform = platform.platform()
77 77 super(SettingsController, self).__before__()
78 78
79 79 @HasPermissionAllDecorator('hg.admin')
80 80 def index(self, format='html'):
81 81 """GET /admin/settings: All items in the collection"""
82 82 # url('admin_settings')
83 83
84 84 defaults = RhodeCodeSetting.get_app_settings()
85 85 defaults.update(self._get_hg_ui_settings())
86 86
87 87 return htmlfill.render(
88 88 render('admin/settings/settings.html'),
89 89 defaults=defaults,
90 90 encoding="UTF-8",
91 91 force_defaults=False
92 92 )
93 93
94 94 @HasPermissionAllDecorator('hg.admin')
95 95 def create(self):
96 96 """POST /admin/settings: Create a new item"""
97 97 # url('admin_settings')
98 98
99 99 @HasPermissionAllDecorator('hg.admin')
100 100 def new(self, format='html'):
101 101 """GET /admin/settings/new: Form to create a new item"""
102 102 # url('admin_new_setting')
103 103
104 104 @HasPermissionAllDecorator('hg.admin')
105 105 def update(self, setting_id):
106 106 """PUT /admin/settings/setting_id: Update an existing item"""
107 107 # Forms posted to this method should contain a hidden field:
108 108 # <input type="hidden" name="_method" value="PUT" />
109 109 # Or using helpers:
110 110 # h.form(url('admin_setting', setting_id=ID),
111 111 # method='put')
112 112 # url('admin_setting', setting_id=ID)
113 113
114 114 if setting_id == 'mapping':
115 115 rm_obsolete = request.POST.get('destroy', False)
116 116 log.debug('Rescanning directories with destroy=%s' % rm_obsolete)
117 117 initial = ScmModel().repo_scan()
118 118 log.debug('invalidating all repositories')
119 119 for repo_name in initial.keys():
120 120 invalidate_cache('get_repo_cached_%s' % repo_name)
121 121
122 122 added, removed = repo2db_mapper(initial, rm_obsolete)
123 123 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
124 124 h.flash(_('Repositories successfully '
125 125 'rescanned added: %s ; removed: %s') %
126 126 (_repr(added), _repr(removed)),
127 127 category='success')
128 128
129 129 if setting_id == 'whoosh':
130 130 repo_location = self._get_hg_ui_settings()['paths_root_path']
131 131 full_index = request.POST.get('full_index', False)
132 132 run_task(tasks.whoosh_index, repo_location, full_index)
133 133 h.flash(_('Whoosh reindex task scheduled'), category='success')
134 134
135 135 if setting_id == 'global':
136 136
137 137 application_form = ApplicationSettingsForm()()
138 138 try:
139 139 form_result = application_form.to_python(dict(request.POST))
140 140 except formencode.Invalid, errors:
141 141 return htmlfill.render(
142 142 render('admin/settings/settings.html'),
143 143 defaults=errors.value,
144 144 errors=errors.error_dict or {},
145 145 prefix_error=False,
146 146 encoding="UTF-8"
147 147 )
148 148
149 149 try:
150 150 sett1 = RhodeCodeSetting.get_by_name_or_create('title')
151 151 sett1.app_settings_value = form_result['rhodecode_title']
152 152 Session().add(sett1)
153 153
154 154 sett2 = RhodeCodeSetting.get_by_name_or_create('realm')
155 155 sett2.app_settings_value = form_result['rhodecode_realm']
156 156 Session().add(sett2)
157 157
158 158 sett3 = RhodeCodeSetting.get_by_name_or_create('ga_code')
159 159 sett3.app_settings_value = form_result['rhodecode_ga_code']
160 160 Session().add(sett3)
161 161
162 162 Session().commit()
163 163 set_rhodecode_config(config)
164 164 h.flash(_('Updated application settings'), category='success')
165 165
166 166 except Exception:
167 167 log.error(traceback.format_exc())
168 168 h.flash(_('error occurred during updating '
169 169 'application settings'),
170 170 category='error')
171 171
172 172 if setting_id == 'visual':
173 173
174 174 application_form = ApplicationVisualisationForm()()
175 175 try:
176 176 form_result = application_form.to_python(dict(request.POST))
177 177 except formencode.Invalid, errors:
178 178 return htmlfill.render(
179 179 render('admin/settings/settings.html'),
180 180 defaults=errors.value,
181 181 errors=errors.error_dict or {},
182 182 prefix_error=False,
183 183 encoding="UTF-8"
184 184 )
185 185
186 186 try:
187 187 sett1 = RhodeCodeSetting.get_by_name_or_create('show_public_icon')
188 188 sett1.app_settings_value = \
189 189 form_result['rhodecode_show_public_icon']
190 190 Session().add(sett1)
191 191
192 192 sett2 = RhodeCodeSetting.get_by_name_or_create('show_private_icon')
193 193 sett2.app_settings_value = \
194 194 form_result['rhodecode_show_private_icon']
195 195 Session().add(sett2)
196 196
197 197 sett3 = RhodeCodeSetting.get_by_name_or_create('stylify_metatags')
198 198 sett3.app_settings_value = \
199 199 form_result['rhodecode_stylify_metatags']
200 200 Session().add(sett3)
201 201
202 202 sett4 = RhodeCodeSetting.get_by_name_or_create('lightweight_dashboard')
203 203 sett4.app_settings_value = \
204 204 form_result['rhodecode_lightweight_dashboard']
205 205 Session().add(sett4)
206 206
207 207 Session().commit()
208 208 set_rhodecode_config(config)
209 209 h.flash(_('Updated visualisation settings'),
210 210 category='success')
211 211
212 212 except Exception:
213 213 log.error(traceback.format_exc())
214 214 h.flash(_('error occurred during updating '
215 215 'visualisation settings'),
216 216 category='error')
217 217
218 218 if setting_id == 'vcs':
219 219 application_form = ApplicationUiSettingsForm()()
220 220 try:
221 221 form_result = application_form.to_python(dict(request.POST))
222 222 except formencode.Invalid, errors:
223 223 return htmlfill.render(
224 224 render('admin/settings/settings.html'),
225 225 defaults=errors.value,
226 226 errors=errors.error_dict or {},
227 227 prefix_error=False,
228 228 encoding="UTF-8"
229 229 )
230 230
231 231 try:
232 232 # fix namespaces for hooks and extensions
233 233 _f = lambda s: s.replace('.', '_')
234 234
235 235 sett = RhodeCodeUi.get_by_key('push_ssl')
236 236 sett.ui_value = form_result['web_push_ssl']
237 237 Session().add(sett)
238 238
239 239 sett = RhodeCodeUi.get_by_key('/')
240 240 sett.ui_value = form_result['paths_root_path']
241 241 Session().add(sett)
242 242
243 243 #HOOKS
244 244 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_UPDATE)
245 245 sett.ui_active = form_result[_f('hooks_%s' %
246 246 RhodeCodeUi.HOOK_UPDATE)]
247 247 Session().add(sett)
248 248
249 249 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_REPO_SIZE)
250 250 sett.ui_active = form_result[_f('hooks_%s' %
251 251 RhodeCodeUi.HOOK_REPO_SIZE)]
252 252 Session().add(sett)
253 253
254 254 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PUSH)
255 255 sett.ui_active = form_result[_f('hooks_%s' %
256 256 RhodeCodeUi.HOOK_PUSH)]
257 257 Session().add(sett)
258 258
259 259 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PULL)
260 260 sett.ui_active = form_result[_f('hooks_%s' %
261 261 RhodeCodeUi.HOOK_PULL)]
262 262
263 263 Session().add(sett)
264 264
265 265 ## EXTENSIONS
266 266 sett = RhodeCodeUi.get_by_key('largefiles')
267 267 if not sett:
268 268 #make one if it's not there !
269 269 sett = RhodeCodeUi()
270 270 sett.ui_key = 'largefiles'
271 271 sett.ui_section = 'extensions'
272 272 sett.ui_active = form_result[_f('extensions_largefiles')]
273 273 Session().add(sett)
274 274
275 275 sett = RhodeCodeUi.get_by_key('hgsubversion')
276 276 if not sett:
277 277 #make one if it's not there !
278 278 sett = RhodeCodeUi()
279 279 sett.ui_key = 'hgsubversion'
280 280 sett.ui_section = 'extensions'
281 281
282 282 sett.ui_active = form_result[_f('extensions_hgsubversion')]
283 283 Session().add(sett)
284 284
285 285 # sett = RhodeCodeUi.get_by_key('hggit')
286 286 # if not sett:
287 287 # #make one if it's not there !
288 288 # sett = RhodeCodeUi()
289 289 # sett.ui_key = 'hggit'
290 290 # sett.ui_section = 'extensions'
291 291 #
292 292 # sett.ui_active = form_result[_f('extensions_hggit')]
293 293 # Session().add(sett)
294 294
295 295 Session().commit()
296 296
297 297 h.flash(_('Updated VCS settings'), category='success')
298 298
299 299 except Exception:
300 300 log.error(traceback.format_exc())
301 301 h.flash(_('error occurred during updating '
302 302 'application settings'), category='error')
303 303
304 304 if setting_id == 'hooks':
305 305 ui_key = request.POST.get('new_hook_ui_key')
306 306 ui_value = request.POST.get('new_hook_ui_value')
307 307 try:
308 308
309 309 if ui_value and ui_key:
310 310 RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
311 311 h.flash(_('Added new hook'),
312 312 category='success')
313 313
314 314 # check for edits
315 315 update = False
316 316 _d = request.POST.dict_of_lists()
317 317 for k, v in zip(_d.get('hook_ui_key', []),
318 318 _d.get('hook_ui_value_new', [])):
319 319 RhodeCodeUi.create_or_update_hook(k, v)
320 320 update = True
321 321
322 322 if update:
323 323 h.flash(_('Updated hooks'), category='success')
324 324 Session().commit()
325 325 except Exception:
326 326 log.error(traceback.format_exc())
327 327 h.flash(_('error occurred during hook creation'),
328 328 category='error')
329 329
330 330 return redirect(url('admin_edit_setting', setting_id='hooks'))
331 331
332 332 if setting_id == 'email':
333 333 test_email = request.POST.get('test_email')
334 334 test_email_subj = 'RhodeCode TestEmail'
335 335 test_email_body = 'RhodeCode Email test'
336 336
337 337 test_email_html_body = EmailNotificationModel()\
338 338 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
339 339 body=test_email_body)
340 340
341 341 recipients = [test_email] if test_email else None
342 342
343 343 run_task(tasks.send_email, recipients, test_email_subj,
344 344 test_email_body, test_email_html_body)
345 345
346 346 h.flash(_('Email task created'), category='success')
347 347 return redirect(url('admin_settings'))
348 348
349 349 @HasPermissionAllDecorator('hg.admin')
350 350 def delete(self, setting_id):
351 351 """DELETE /admin/settings/setting_id: Delete an existing item"""
352 352 # Forms posted to this method should contain a hidden field:
353 353 # <input type="hidden" name="_method" value="DELETE" />
354 354 # Or using helpers:
355 355 # h.form(url('admin_setting', setting_id=ID),
356 356 # method='delete')
357 357 # url('admin_setting', setting_id=ID)
358 358 if setting_id == 'hooks':
359 359 hook_id = request.POST.get('hook_id')
360 360 RhodeCodeUi.delete(hook_id)
361 361 Session().commit()
362 362
363 363 @HasPermissionAllDecorator('hg.admin')
364 364 def show(self, setting_id, format='html'):
365 365 """
366 366 GET /admin/settings/setting_id: Show a specific item"""
367 367 # url('admin_setting', setting_id=ID)
368 368
369 369 @HasPermissionAllDecorator('hg.admin')
370 370 def edit(self, setting_id, format='html'):
371 371 """
372 372 GET /admin/settings/setting_id/edit: Form to
373 373 edit an existing item"""
374 374 # url('admin_edit_setting', setting_id=ID)
375 375
376 376 c.hooks = RhodeCodeUi.get_builtin_hooks()
377 377 c.custom_hooks = RhodeCodeUi.get_custom_hooks()
378 378
379 379 return htmlfill.render(
380 380 render('admin/settings/hooks.html'),
381 381 defaults={},
382 382 encoding="UTF-8",
383 383 force_defaults=False
384 384 )
385 385
386 386 def _load_my_repos_data(self):
387 387 repos_list = Session().query(Repository)\
388 388 .filter(Repository.user_id ==
389 389 self.rhodecode_user.user_id)\
390 390 .order_by(func.lower(Repository.repo_name)).all()
391 391
392 392 repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
393 393 admin=True)
394 394 #json used to render the grid
395 395 return json.dumps(repos_data)
396 396
397 397 @NotAnonymous()
398 398 def my_account(self):
399 399 """
400 400 GET /_admin/my_account Displays info about my account
401 401 """
402 402 # url('admin_settings_my_account')
403 403
404 404 c.user = User.get(self.rhodecode_user.user_id)
405 405 c.ldap_dn = c.user.ldap_dn
406 406
407 407 if c.user.username == 'default':
408 408 h.flash(_("You can't edit this user since it's"
409 409 " crucial for entire application"), category='warning')
410 410 return redirect(url('users'))
411 411
412 412 #json used to render the grid
413 413 c.data = self._load_my_repos_data()
414 414
415 415 defaults = c.user.get_dict()
416 416
417 417 c.form = htmlfill.render(
418 418 render('admin/users/user_edit_my_account_form.html'),
419 419 defaults=defaults,
420 420 encoding="UTF-8",
421 421 force_defaults=False
422 422 )
423 423 return render('admin/users/user_edit_my_account.html')
424 424
425 425 @NotAnonymous()
426 426 def my_account_update(self):
427 427 """PUT /_admin/my_account_update: Update an existing item"""
428 428 # Forms posted to this method should contain a hidden field:
429 429 # <input type="hidden" name="_method" value="PUT" />
430 430 # Or using helpers:
431 431 # h.form(url('admin_settings_my_account_update'),
432 432 # method='put')
433 433 # url('admin_settings_my_account_update', id=ID)
434 434 uid = self.rhodecode_user.user_id
435 435 c.user = User.get(self.rhodecode_user.user_id)
436 436 c.ldap_dn = c.user.ldap_dn
437 437 email = self.rhodecode_user.email
438 438 _form = UserForm(edit=True,
439 439 old_data={'user_id': uid, 'email': email})()
440 440 form_result = {}
441 441 try:
442 442 form_result = _form.to_python(dict(request.POST))
443 443 skip_attrs = ['admin', 'active'] # skip attr for my account
444 444 if c.ldap_dn:
445 445 #forbid updating username for ldap accounts
446 446 skip_attrs.append('username')
447 447 UserModel().update(uid, form_result, skip_attrs=skip_attrs)
448 448 h.flash(_('Your account was updated successfully'),
449 449 category='success')
450 450 Session().commit()
451 451 except formencode.Invalid, errors:
452 452 #json used to render the grid
453 453 c.data = self._load_my_repos_data()
454 454 c.form = htmlfill.render(
455 455 render('admin/users/user_edit_my_account_form.html'),
456 456 defaults=errors.value,
457 457 errors=errors.error_dict or {},
458 458 prefix_error=False,
459 459 encoding="UTF-8")
460 460 return render('admin/users/user_edit_my_account.html')
461 461 except Exception:
462 462 log.error(traceback.format_exc())
463 463 h.flash(_('error occurred during update of user %s') \
464 464 % form_result.get('username'), category='error')
465 465
466 466 return redirect(url('my_account'))
467 467
468 468 @NotAnonymous()
469 469 def my_account_my_pullrequests(self):
470 470 c.my_pull_requests = PullRequest.query()\
471 471 .filter(PullRequest.user_id ==
472 472 self.rhodecode_user.user_id)\
473 473 .all()
474 474 c.participate_in_pull_requests = \
475 475 [x.pull_request for x in PullRequestReviewers.query()\
476 476 .filter(PullRequestReviewers.user_id ==
477 477 self.rhodecode_user.user_id)\
478 478 .all()]
479 479 return render('admin/users/user_edit_my_account_pullrequests.html')
480 480
481 481 @NotAnonymous()
482 482 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
483 483 def create_repository(self):
484 484 """GET /_admin/create_repository: Form to create a new item"""
485 485
486 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
486 acl_groups = GroupList(RepoGroup.query().all(),
487 perm_set=['group.write', 'group.admin'])
488 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
487 489 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
488 490 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
489 491
490 492 new_repo = request.GET.get('repo', '')
491 493 parent_group = request.GET.get('parent_group')
492 494 c.new_repo = repo_name_slug(new_repo)
493 495
494 496 ## apply the defaults from defaults page
495 497 defaults = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
496 498 if parent_group:
497 499 defaults.update({'repo_group': parent_group})
498 500
499 501 return htmlfill.render(
500 502 render('admin/repos/repo_add_create_repository.html'),
501 503 defaults=defaults,
502 504 errors={},
503 505 prefix_error=False,
504 506 encoding="UTF-8"
505 507 )
506 508
507 509 def _get_hg_ui_settings(self):
508 510 ret = RhodeCodeUi.query().all()
509 511
510 512 if not ret:
511 513 raise Exception('Could not get application ui settings !')
512 514 settings = {}
513 515 for each in ret:
514 516 k = each.ui_key
515 517 v = each.ui_value
516 518 if k == '/':
517 519 k = 'root_path'
518 520
519 521 if k == 'push_ssl':
520 522 v = str2bool(v)
521 523
522 524 if k.find('.') != -1:
523 525 k = k.replace('.', '_')
524 526
525 527 if each.ui_section in ['hooks', 'extensions']:
526 528 v = each.ui_active
527 529
528 530 settings[each.ui_section + '_' + k] = v
529 531 return settings
@@ -1,180 +1,182 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.forks
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 forks controller for rhodecode
7 7
8 8 :created_on: Apr 23, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2011-2012 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 logging
26 26 import formencode
27 27 import traceback
28 28 from formencode import htmlfill
29 29
30 30 from pylons import tmpl_context as c, request, url
31 31 from pylons.controllers.util import redirect
32 32 from pylons.i18n.translation import _
33 33
34 34 import rhodecode.lib.helpers as h
35 35
36 36 from rhodecode.lib.helpers import Page
37 37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
38 38 NotAnonymous, HasRepoPermissionAny, HasPermissionAllDecorator,\
39 39 HasPermissionAnyDecorator
40 40 from rhodecode.lib.base import BaseRepoController, render
41 41 from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User
42 42 from rhodecode.model.repo import RepoModel
43 43 from rhodecode.model.forms import RepoForkForm
44 from rhodecode.model.scm import ScmModel
44 from rhodecode.model.scm import ScmModel, GroupList
45 45 from rhodecode.lib.utils2 import safe_int
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class ForksController(BaseRepoController):
51 51
52 52 @LoginRequired()
53 53 def __before__(self):
54 54 super(ForksController, self).__before__()
55 55
56 56 def __load_defaults(self):
57 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
57 acl_groups = GroupList(RepoGroup.query().all(),
58 perm_set=['group.write', 'group.admin'])
59 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
58 60 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
59 61 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
60 62 c.landing_revs_choices = choices
61 63
62 64 def __load_data(self, repo_name=None):
63 65 """
64 66 Load defaults settings for edit, and update
65 67
66 68 :param repo_name:
67 69 """
68 70 self.__load_defaults()
69 71
70 72 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
71 73 repo = db_repo.scm_instance
72 74
73 75 if c.repo_info is None:
74 76 h.not_mapped_error(repo_name)
75 77 return redirect(url('repos'))
76 78
77 79 c.default_user_id = User.get_by_username('default').user_id
78 80 c.in_public_journal = UserFollowing.query()\
79 81 .filter(UserFollowing.user_id == c.default_user_id)\
80 82 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
81 83
82 84 if c.repo_info.stats:
83 85 last_rev = c.repo_info.stats.stat_on_revision+1
84 86 else:
85 87 last_rev = 0
86 88 c.stats_revision = last_rev
87 89
88 90 c.repo_last_rev = repo.count() if repo.revisions else 0
89 91
90 92 if last_rev == 0 or c.repo_last_rev == 0:
91 93 c.stats_percentage = 0
92 94 else:
93 95 c.stats_percentage = '%.2f' % ((float((last_rev)) /
94 96 c.repo_last_rev) * 100)
95 97
96 98 defaults = RepoModel()._get_defaults(repo_name)
97 99 # alter the description to indicate a fork
98 100 defaults['description'] = ('fork of repository: %s \n%s'
99 101 % (defaults['repo_name'],
100 102 defaults['description']))
101 103 # add suffix to fork
102 104 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
103 105
104 106 return defaults
105 107
106 108 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
107 109 'repository.admin')
108 110 def forks(self, repo_name):
109 111 p = safe_int(request.params.get('page', 1), 1)
110 112 repo_id = c.rhodecode_db_repo.repo_id
111 113 d = []
112 114 for r in Repository.get_repo_forks(repo_id):
113 115 if not HasRepoPermissionAny(
114 116 'repository.read', 'repository.write', 'repository.admin'
115 117 )(r.repo_name, 'get forks check'):
116 118 continue
117 119 d.append(r)
118 120 c.forks_pager = Page(d, page=p, items_per_page=20)
119 121
120 122 c.forks_data = render('/forks/forks_data.html')
121 123
122 124 if request.environ.get('HTTP_X_PARTIAL_XHR'):
123 125 return c.forks_data
124 126
125 127 return render('/forks/forks.html')
126 128
127 129 @NotAnonymous()
128 130 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
129 131 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
130 132 'repository.admin')
131 133 def fork(self, repo_name):
132 134 c.repo_info = Repository.get_by_repo_name(repo_name)
133 135 if not c.repo_info:
134 136 h.not_mapped_error(repo_name)
135 137 return redirect(url('home'))
136 138
137 139 defaults = self.__load_data(repo_name)
138 140
139 141 return htmlfill.render(
140 142 render('forks/fork.html'),
141 143 defaults=defaults,
142 144 encoding="UTF-8",
143 145 force_defaults=False
144 146 )
145 147
146 148 @NotAnonymous()
147 149 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
148 150 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
149 151 'repository.admin')
150 152 def fork_create(self, repo_name):
151 153 self.__load_defaults()
152 154 c.repo_info = Repository.get_by_repo_name(repo_name)
153 155 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
154 156 repo_groups=c.repo_groups_choices,
155 157 landing_revs=c.landing_revs_choices)()
156 158 form_result = {}
157 159 try:
158 160 form_result = _form.to_python(dict(request.POST))
159 161
160 162 # create fork is done sometimes async on celery, db transaction
161 163 # management is handled there.
162 164 RepoModel().create_fork(form_result, self.rhodecode_user.user_id)
163 165 h.flash(_('forked %s repository as %s') \
164 166 % (repo_name, form_result['repo_name']),
165 167 category='success')
166 168 except formencode.Invalid, errors:
167 169 c.new_repo = errors.value['repo_name']
168 170
169 171 return htmlfill.render(
170 172 render('forks/fork.html'),
171 173 defaults=errors.value,
172 174 errors=errors.error_dict or {},
173 175 prefix_error=False,
174 176 encoding="UTF-8")
175 177 except Exception:
176 178 log.error(traceback.format_exc())
177 179 h.flash(_('An error occurred during repository forking %s') %
178 180 repo_name, category='error')
179 181
180 182 return redirect(url('home'))
@@ -1,196 +1,197 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) 2010-2012 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
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
44 44 from rhodecode.model.repo import RepoModel
45 45 from rhodecode.model.db import RepoGroup, Repository
46 46 from rhodecode.model.meta import Session
47 from rhodecode.model.scm import ScmModel
47 from rhodecode.model.scm import ScmModel, GroupList
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 class SettingsController(BaseRepoController):
53 53
54 54 @LoginRequired()
55 55 def __before__(self):
56 56 super(SettingsController, self).__before__()
57 57
58 58 def __load_defaults(self):
59 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
59 acl_groups = GroupList(RepoGroup.query().all(),
60 perm_set=['group.write', 'group.admin'])
61 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
60 62 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
61 63
62 64 repo_model = RepoModel()
63 65 c.users_array = repo_model.get_users_js()
64 66 c.users_groups_array = repo_model.get_users_groups_js()
65 67 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
66 68 c.landing_revs_choices = choices
67 69
68 70 def __load_data(self, repo_name=None):
69 71 """
70 72 Load defaults settings for edit, and update
71 73
72 74 :param repo_name:
73 75 """
74 76 self.__load_defaults()
75 77
76 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
77 repo = db_repo.scm_instance
78 c.repo_info = Repository.get_by_repo_name(repo_name)
78 79
79 80 if c.repo_info is None:
80 81 h.not_mapped_error(repo_name)
81 82 return redirect(url('home'))
82 83
83 84 ##override defaults for exact repo info here git/hg etc
84 85 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
85 86 c.landing_revs_choices = choices
86 87
87 88 defaults = RepoModel()._get_defaults(repo_name)
88 89
89 90 return defaults
90 91
91 92 @HasRepoPermissionAllDecorator('repository.admin')
92 93 def index(self, repo_name):
93 94 defaults = self.__load_data(repo_name)
94 95
95 96 return htmlfill.render(
96 97 render('settings/repo_settings.html'),
97 98 defaults=defaults,
98 99 encoding="UTF-8",
99 100 force_defaults=False
100 101 )
101 102
102 103 @HasRepoPermissionAllDecorator('repository.admin')
103 104 def update(self, repo_name):
104 105 self.__load_defaults()
105 106 repo_model = RepoModel()
106 107 changed_name = repo_name
107 108 #override the choices with extracted revisions !
108 109 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
109 110 c.landing_revs_choices = choices
110 111
111 112 _form = RepoSettingsForm(edit=True,
112 113 old_data={'repo_name': repo_name},
113 114 repo_groups=c.repo_groups_choices,
114 115 landing_revs=c.landing_revs_choices)()
115 116 try:
116 117 form_result = _form.to_python(dict(request.POST))
117 118 repo_model.update(repo_name, **form_result)
118 119 invalidate_cache('get_repo_cached_%s' % repo_name)
119 120 h.flash(_('Repository %s updated successfully') % repo_name,
120 121 category='success')
121 122 changed_name = form_result['repo_name_full']
122 123 action_logger(self.rhodecode_user, 'user_updated_repo',
123 124 changed_name, self.ip_addr, self.sa)
124 125 Session().commit()
125 126 except formencode.Invalid, errors:
126 127 defaults = self.__load_data(repo_name)
127 128 defaults.update(errors.value)
128 129 return htmlfill.render(
129 130 render('settings/repo_settings.html'),
130 131 defaults=errors.value,
131 132 errors=errors.error_dict or {},
132 133 prefix_error=False,
133 134 encoding="UTF-8")
134 135
135 136 except Exception:
136 137 log.error(traceback.format_exc())
137 138 h.flash(_('error occurred during update of repository %s') \
138 139 % repo_name, category='error')
139 140
140 141 return redirect(url('repo_settings_home', repo_name=changed_name))
141 142
142 143 @HasRepoPermissionAllDecorator('repository.admin')
143 144 def delete(self, repo_name):
144 145 """DELETE /repos/repo_name: Delete an existing item"""
145 146 # Forms posted to this method should contain a hidden field:
146 147 # <input type="hidden" name="_method" value="DELETE" />
147 148 # Or using helpers:
148 149 # h.form(url('repo_settings_delete', repo_name=ID),
149 150 # method='delete')
150 151 # url('repo_settings_delete', repo_name=ID)
151 152
152 153 repo_model = RepoModel()
153 154 repo = repo_model.get_by_repo_name(repo_name)
154 155 if not repo:
155 156 h.not_mapped_error(repo_name)
156 157 return redirect(url('home'))
157 158 try:
158 159 action_logger(self.rhodecode_user, 'user_deleted_repo',
159 160 repo_name, self.ip_addr, self.sa)
160 161 repo_model.delete(repo)
161 162 invalidate_cache('get_repo_cached_%s' % repo_name)
162 163 h.flash(_('deleted repository %s') % repo_name, category='success')
163 164 Session().commit()
164 165 except Exception:
165 166 log.error(traceback.format_exc())
166 167 h.flash(_('An error occurred during deletion of %s') % repo_name,
167 168 category='error')
168 169
169 170 return redirect(url('admin_settings_my_account', anchor='my'))
170 171
171 172 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
172 173 def toggle_locking(self, repo_name):
173 174 """
174 175 Toggle locking of repository by simple GET call to url
175 176
176 177 :param repo_name:
177 178 """
178 179
179 180 try:
180 181 repo = Repository.get_by_repo_name(repo_name)
181 182
182 183 if repo.enable_locking:
183 184 if repo.locked[0]:
184 185 Repository.unlock(repo)
185 186 action = _('unlocked')
186 187 else:
187 188 Repository.lock(repo, c.rhodecode_user.user_id)
188 189 action = _('locked')
189 190
190 191 h.flash(_('Repository has been %s') % action,
191 192 category='success')
192 193 except Exception, e:
193 194 log.error(traceback.format_exc())
194 195 h.flash(_('An error occurred during unlocking'),
195 196 category='error')
196 197 return redirect(url('summary_home', repo_name=repo_name))
@@ -1,1979 +1,1975 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) 2010-2012 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 hashlib
31 31 import time
32 32 from collections import defaultdict
33 33
34 34 from sqlalchemy import *
35 35 from sqlalchemy.ext.hybrid import hybrid_property
36 36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 37 from sqlalchemy.exc import DatabaseError
38 38 from beaker.cache import cache_region, region_invalidate
39 39 from webob.exc import HTTPNotFound
40 40
41 41 from pylons.i18n.translation import lazy_ugettext as _
42 42
43 43 from rhodecode.lib.vcs import get_backend
44 44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 45 from rhodecode.lib.vcs.exceptions import VCSError
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47
48 48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 49 safe_unicode, remove_suffix, remove_prefix
50 50 from rhodecode.lib.compat import json
51 51 from rhodecode.lib.caching_query import FromCache
52 52
53 53 from rhodecode.model.meta import Base, Session
54 54
55 55 URL_SEP = '/'
56 56 log = logging.getLogger(__name__)
57 57
58 58 #==============================================================================
59 59 # BASE CLASSES
60 60 #==============================================================================
61 61
62 62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63 63
64 64
65 65 class BaseModel(object):
66 66 """
67 67 Base Model for all classess
68 68 """
69 69
70 70 @classmethod
71 71 def _get_keys(cls):
72 72 """return column names for this model """
73 73 return class_mapper(cls).c.keys()
74 74
75 75 def get_dict(self):
76 76 """
77 77 return dict with keys and values corresponding
78 78 to this model data """
79 79
80 80 d = {}
81 81 for k in self._get_keys():
82 82 d[k] = getattr(self, k)
83 83
84 84 # also use __json__() if present to get additional fields
85 85 _json_attr = getattr(self, '__json__', None)
86 86 if _json_attr:
87 87 # update with attributes from __json__
88 88 if callable(_json_attr):
89 89 _json_attr = _json_attr()
90 90 for k, val in _json_attr.iteritems():
91 91 d[k] = val
92 92 return d
93 93
94 94 def get_appstruct(self):
95 95 """return list with keys and values tupples corresponding
96 96 to this model data """
97 97
98 98 l = []
99 99 for k in self._get_keys():
100 100 l.append((k, getattr(self, k),))
101 101 return l
102 102
103 103 def populate_obj(self, populate_dict):
104 104 """populate model with data from given populate_dict"""
105 105
106 106 for k in self._get_keys():
107 107 if k in populate_dict:
108 108 setattr(self, k, populate_dict[k])
109 109
110 110 @classmethod
111 111 def query(cls):
112 112 return Session().query(cls)
113 113
114 114 @classmethod
115 115 def get(cls, id_):
116 116 if id_:
117 117 return cls.query().get(id_)
118 118
119 119 @classmethod
120 120 def get_or_404(cls, id_):
121 121 try:
122 122 id_ = int(id_)
123 123 except (TypeError, ValueError):
124 124 raise HTTPNotFound
125 125
126 126 res = cls.query().get(id_)
127 127 if not res:
128 128 raise HTTPNotFound
129 129 return res
130 130
131 131 @classmethod
132 132 def getAll(cls):
133 133 return cls.query().all()
134 134
135 135 @classmethod
136 136 def delete(cls, id_):
137 137 obj = cls.query().get(id_)
138 138 Session().delete(obj)
139 139
140 140 def __repr__(self):
141 141 if hasattr(self, '__unicode__'):
142 142 # python repr needs to return str
143 143 return safe_str(self.__unicode__())
144 144 return '<DB:%s>' % (self.__class__.__name__)
145 145
146 146
147 147 class RhodeCodeSetting(Base, BaseModel):
148 148 __tablename__ = 'rhodecode_settings'
149 149 __table_args__ = (
150 150 UniqueConstraint('app_settings_name'),
151 151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
152 152 'mysql_charset': 'utf8'}
153 153 )
154 154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
155 155 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
156 156 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157 157
158 158 def __init__(self, k='', v=''):
159 159 self.app_settings_name = k
160 160 self.app_settings_value = v
161 161
162 162 @validates('_app_settings_value')
163 163 def validate_settings_value(self, key, val):
164 164 assert type(val) == unicode
165 165 return val
166 166
167 167 @hybrid_property
168 168 def app_settings_value(self):
169 169 v = self._app_settings_value
170 170 if self.app_settings_name in ["ldap_active",
171 171 "default_repo_enable_statistics",
172 172 "default_repo_enable_locking",
173 173 "default_repo_private",
174 174 "default_repo_enable_downloads"]:
175 175 v = str2bool(v)
176 176 return v
177 177
178 178 @app_settings_value.setter
179 179 def app_settings_value(self, val):
180 180 """
181 181 Setter that will always make sure we use unicode in app_settings_value
182 182
183 183 :param val:
184 184 """
185 185 self._app_settings_value = safe_unicode(val)
186 186
187 187 def __unicode__(self):
188 188 return u"<%s('%s:%s')>" % (
189 189 self.__class__.__name__,
190 190 self.app_settings_name, self.app_settings_value
191 191 )
192 192
193 193 @classmethod
194 194 def get_by_name(cls, key):
195 195 return cls.query()\
196 196 .filter(cls.app_settings_name == key).scalar()
197 197
198 198 @classmethod
199 199 def get_by_name_or_create(cls, key):
200 200 res = cls.get_by_name(key)
201 201 if not res:
202 202 res = cls(key)
203 203 return res
204 204
205 205 @classmethod
206 206 def get_app_settings(cls, cache=False):
207 207
208 208 ret = cls.query()
209 209
210 210 if cache:
211 211 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
212 212
213 213 if not ret:
214 214 raise Exception('Could not get application settings !')
215 215 settings = {}
216 216 for each in ret:
217 217 settings['rhodecode_' + each.app_settings_name] = \
218 218 each.app_settings_value
219 219
220 220 return settings
221 221
222 222 @classmethod
223 223 def get_ldap_settings(cls, cache=False):
224 224 ret = cls.query()\
225 225 .filter(cls.app_settings_name.startswith('ldap_')).all()
226 226 fd = {}
227 227 for row in ret:
228 228 fd.update({row.app_settings_name: row.app_settings_value})
229 229
230 230 return fd
231 231
232 232 @classmethod
233 233 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
234 234 ret = cls.query()\
235 235 .filter(cls.app_settings_name.startswith('default_')).all()
236 236 fd = {}
237 237 for row in ret:
238 238 key = row.app_settings_name
239 239 if strip_prefix:
240 240 key = remove_prefix(key, prefix='default_')
241 241 fd.update({key: row.app_settings_value})
242 242
243 243 return fd
244 244
245 245
246 246 class RhodeCodeUi(Base, BaseModel):
247 247 __tablename__ = 'rhodecode_ui'
248 248 __table_args__ = (
249 249 UniqueConstraint('ui_key'),
250 250 {'extend_existing': True, 'mysql_engine': 'InnoDB',
251 251 'mysql_charset': 'utf8'}
252 252 )
253 253
254 254 HOOK_UPDATE = 'changegroup.update'
255 255 HOOK_REPO_SIZE = 'changegroup.repo_size'
256 256 HOOK_PUSH = 'changegroup.push_logger'
257 257 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
258 258 HOOK_PULL = 'outgoing.pull_logger'
259 259 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
260 260
261 261 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
262 262 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
263 263 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 264 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 265 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
266 266
267 267 @classmethod
268 268 def get_by_key(cls, key):
269 269 return cls.query().filter(cls.ui_key == key).scalar()
270 270
271 271 @classmethod
272 272 def get_builtin_hooks(cls):
273 273 q = cls.query()
274 274 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
275 275 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
276 276 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
277 277 return q.all()
278 278
279 279 @classmethod
280 280 def get_custom_hooks(cls):
281 281 q = cls.query()
282 282 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
283 283 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
284 284 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
285 285 q = q.filter(cls.ui_section == 'hooks')
286 286 return q.all()
287 287
288 288 @classmethod
289 289 def get_repos_location(cls):
290 290 return cls.get_by_key('/').ui_value
291 291
292 292 @classmethod
293 293 def create_or_update_hook(cls, key, val):
294 294 new_ui = cls.get_by_key(key) or cls()
295 295 new_ui.ui_section = 'hooks'
296 296 new_ui.ui_active = True
297 297 new_ui.ui_key = key
298 298 new_ui.ui_value = val
299 299
300 300 Session().add(new_ui)
301 301
302 302 def __repr__(self):
303 303 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
304 304 self.ui_value)
305 305
306 306
307 307 class User(Base, BaseModel):
308 308 __tablename__ = 'users'
309 309 __table_args__ = (
310 310 UniqueConstraint('username'), UniqueConstraint('email'),
311 311 Index('u_username_idx', 'username'),
312 312 Index('u_email_idx', 'email'),
313 313 {'extend_existing': True, 'mysql_engine': 'InnoDB',
314 314 'mysql_charset': 'utf8'}
315 315 )
316 316 DEFAULT_USER = 'default'
317 317 DEFAULT_PERMISSIONS = [
318 318 'hg.register.manual_activate', 'hg.create.repository',
319 319 'hg.fork.repository', 'repository.read', 'group.read'
320 320 ]
321 321 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
322 322 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
323 323 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
324 324 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
325 325 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
326 326 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 327 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 328 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 329 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
330 330 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 331 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 332 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
333 333
334 334 user_log = relationship('UserLog')
335 335 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
336 336
337 337 repositories = relationship('Repository')
338 338 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
339 339 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
340 340
341 341 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
342 342 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
343 343
344 344 group_member = relationship('UsersGroupMember', cascade='all')
345 345
346 346 notifications = relationship('UserNotification', cascade='all')
347 347 # notifications assigned to this user
348 348 user_created_notifications = relationship('Notification', cascade='all')
349 349 # comments created by this user
350 350 user_comments = relationship('ChangesetComment', cascade='all')
351 351 #extra emails for this user
352 352 user_emails = relationship('UserEmailMap', cascade='all')
353 353
354 354 @hybrid_property
355 355 def email(self):
356 356 return self._email
357 357
358 358 @email.setter
359 359 def email(self, val):
360 360 self._email = val.lower() if val else None
361 361
362 362 @property
363 363 def firstname(self):
364 364 # alias for future
365 365 return self.name
366 366
367 367 @property
368 368 def emails(self):
369 369 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
370 370 return [self.email] + [x.email for x in other]
371 371
372 372 @property
373 373 def ip_addresses(self):
374 374 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
375 375 return [x.ip_addr for x in ret]
376 376
377 377 @property
378 378 def username_and_name(self):
379 379 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
380 380
381 381 @property
382 382 def full_name(self):
383 383 return '%s %s' % (self.firstname, self.lastname)
384 384
385 385 @property
386 386 def full_name_or_username(self):
387 387 return ('%s %s' % (self.firstname, self.lastname)
388 388 if (self.firstname and self.lastname) else self.username)
389 389
390 390 @property
391 391 def full_contact(self):
392 392 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
393 393
394 394 @property
395 395 def short_contact(self):
396 396 return '%s %s' % (self.firstname, self.lastname)
397 397
398 398 @property
399 399 def is_admin(self):
400 400 return self.admin
401 401
402 402 def __unicode__(self):
403 403 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
404 404 self.user_id, self.username)
405 405
406 406 @classmethod
407 407 def get_by_username(cls, username, case_insensitive=False, cache=False):
408 408 if case_insensitive:
409 409 q = cls.query().filter(cls.username.ilike(username))
410 410 else:
411 411 q = cls.query().filter(cls.username == username)
412 412
413 413 if cache:
414 414 q = q.options(FromCache(
415 415 "sql_cache_short",
416 416 "get_user_%s" % _hash_key(username)
417 417 )
418 418 )
419 419 return q.scalar()
420 420
421 421 @classmethod
422 422 def get_by_api_key(cls, api_key, cache=False):
423 423 q = cls.query().filter(cls.api_key == api_key)
424 424
425 425 if cache:
426 426 q = q.options(FromCache("sql_cache_short",
427 427 "get_api_key_%s" % api_key))
428 428 return q.scalar()
429 429
430 430 @classmethod
431 431 def get_by_email(cls, email, case_insensitive=False, cache=False):
432 432 if case_insensitive:
433 433 q = cls.query().filter(cls.email.ilike(email))
434 434 else:
435 435 q = cls.query().filter(cls.email == email)
436 436
437 437 if cache:
438 438 q = q.options(FromCache("sql_cache_short",
439 439 "get_email_key_%s" % email))
440 440
441 441 ret = q.scalar()
442 442 if ret is None:
443 443 q = UserEmailMap.query()
444 444 # try fetching in alternate email map
445 445 if case_insensitive:
446 446 q = q.filter(UserEmailMap.email.ilike(email))
447 447 else:
448 448 q = q.filter(UserEmailMap.email == email)
449 449 q = q.options(joinedload(UserEmailMap.user))
450 450 if cache:
451 451 q = q.options(FromCache("sql_cache_short",
452 452 "get_email_map_key_%s" % email))
453 453 ret = getattr(q.scalar(), 'user', None)
454 454
455 455 return ret
456 456
457 457 @classmethod
458 458 def get_from_cs_author(cls, author):
459 459 """
460 460 Tries to get User objects out of commit author string
461 461
462 462 :param author:
463 463 """
464 464 from rhodecode.lib.helpers import email, author_name
465 465 # Valid email in the attribute passed, see if they're in the system
466 466 _email = email(author)
467 467 if _email:
468 468 user = cls.get_by_email(_email, case_insensitive=True)
469 469 if user:
470 470 return user
471 471 # Maybe we can match by username?
472 472 _author = author_name(author)
473 473 user = cls.get_by_username(_author, case_insensitive=True)
474 474 if user:
475 475 return user
476 476
477 477 def update_lastlogin(self):
478 478 """Update user lastlogin"""
479 479 self.last_login = datetime.datetime.now()
480 480 Session().add(self)
481 481 log.debug('updated user %s lastlogin' % self.username)
482 482
483 483 def get_api_data(self):
484 484 """
485 485 Common function for generating user related data for API
486 486 """
487 487 user = self
488 488 data = dict(
489 489 user_id=user.user_id,
490 490 username=user.username,
491 491 firstname=user.name,
492 492 lastname=user.lastname,
493 493 email=user.email,
494 494 emails=user.emails,
495 495 api_key=user.api_key,
496 496 active=user.active,
497 497 admin=user.admin,
498 498 ldap_dn=user.ldap_dn,
499 499 last_login=user.last_login,
500 500 ip_addresses=user.ip_addresses
501 501 )
502 502 return data
503 503
504 504 def __json__(self):
505 505 data = dict(
506 506 full_name=self.full_name,
507 507 full_name_or_username=self.full_name_or_username,
508 508 short_contact=self.short_contact,
509 509 full_contact=self.full_contact
510 510 )
511 511 data.update(self.get_api_data())
512 512 return data
513 513
514 514
515 515 class UserEmailMap(Base, BaseModel):
516 516 __tablename__ = 'user_email_map'
517 517 __table_args__ = (
518 518 Index('uem_email_idx', 'email'),
519 519 UniqueConstraint('email'),
520 520 {'extend_existing': True, 'mysql_engine': 'InnoDB',
521 521 'mysql_charset': 'utf8'}
522 522 )
523 523 __mapper_args__ = {}
524 524
525 525 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
526 526 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
527 527 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
528 528 user = relationship('User', lazy='joined')
529 529
530 530 @validates('_email')
531 531 def validate_email(self, key, email):
532 532 # check if this email is not main one
533 533 main_email = Session().query(User).filter(User.email == email).scalar()
534 534 if main_email is not None:
535 535 raise AttributeError('email %s is present is user table' % email)
536 536 return email
537 537
538 538 @hybrid_property
539 539 def email(self):
540 540 return self._email
541 541
542 542 @email.setter
543 543 def email(self, val):
544 544 self._email = val.lower() if val else None
545 545
546 546
547 547 class UserIpMap(Base, BaseModel):
548 548 __tablename__ = 'user_ip_map'
549 549 __table_args__ = (
550 550 UniqueConstraint('user_id', 'ip_addr'),
551 551 {'extend_existing': True, 'mysql_engine': 'InnoDB',
552 552 'mysql_charset': 'utf8'}
553 553 )
554 554 __mapper_args__ = {}
555 555
556 556 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
557 557 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
558 558 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
559 559 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
560 560 user = relationship('User', lazy='joined')
561 561
562 562 @classmethod
563 563 def _get_ip_range(cls, ip_addr):
564 564 from rhodecode.lib import ipaddr
565 565 net = ipaddr.IPNetwork(address=ip_addr)
566 566 return [str(net.network), str(net.broadcast)]
567 567
568 568 def __json__(self):
569 569 return dict(
570 570 ip_addr=self.ip_addr,
571 571 ip_range=self._get_ip_range(self.ip_addr)
572 572 )
573 573
574 574
575 575 class UserLog(Base, BaseModel):
576 576 __tablename__ = 'user_logs'
577 577 __table_args__ = (
578 578 {'extend_existing': True, 'mysql_engine': 'InnoDB',
579 579 'mysql_charset': 'utf8'},
580 580 )
581 581 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
582 582 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
583 583 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
584 584 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
585 585 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
586 586 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
587 587 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
588 588 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
589 589
590 590 @property
591 591 def action_as_day(self):
592 592 return datetime.date(*self.action_date.timetuple()[:3])
593 593
594 594 user = relationship('User')
595 595 repository = relationship('Repository', cascade='')
596 596
597 597
598 598 class UsersGroup(Base, BaseModel):
599 599 __tablename__ = 'users_groups'
600 600 __table_args__ = (
601 601 {'extend_existing': True, 'mysql_engine': 'InnoDB',
602 602 'mysql_charset': 'utf8'},
603 603 )
604 604
605 605 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
606 606 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
607 607 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
608 608 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
609 609
610 610 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
611 611 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
612 612 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
613 613
614 614 def __unicode__(self):
615 615 return u'<userGroup(%s)>' % (self.users_group_name)
616 616
617 617 @classmethod
618 618 def get_by_group_name(cls, group_name, cache=False,
619 619 case_insensitive=False):
620 620 if case_insensitive:
621 621 q = cls.query().filter(cls.users_group_name.ilike(group_name))
622 622 else:
623 623 q = cls.query().filter(cls.users_group_name == group_name)
624 624 if cache:
625 625 q = q.options(FromCache(
626 626 "sql_cache_short",
627 627 "get_user_%s" % _hash_key(group_name)
628 628 )
629 629 )
630 630 return q.scalar()
631 631
632 632 @classmethod
633 633 def get(cls, users_group_id, cache=False):
634 634 users_group = cls.query()
635 635 if cache:
636 636 users_group = users_group.options(FromCache("sql_cache_short",
637 637 "get_users_group_%s" % users_group_id))
638 638 return users_group.get(users_group_id)
639 639
640 640 def get_api_data(self):
641 641 users_group = self
642 642
643 643 data = dict(
644 644 users_group_id=users_group.users_group_id,
645 645 group_name=users_group.users_group_name,
646 646 active=users_group.users_group_active,
647 647 )
648 648
649 649 return data
650 650
651 651
652 652 class UsersGroupMember(Base, BaseModel):
653 653 __tablename__ = 'users_groups_members'
654 654 __table_args__ = (
655 655 {'extend_existing': True, 'mysql_engine': 'InnoDB',
656 656 'mysql_charset': 'utf8'},
657 657 )
658 658
659 659 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
660 660 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
661 661 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
662 662
663 663 user = relationship('User', lazy='joined')
664 664 users_group = relationship('UsersGroup')
665 665
666 666 def __init__(self, gr_id='', u_id=''):
667 667 self.users_group_id = gr_id
668 668 self.user_id = u_id
669 669
670 670
671 671 class Repository(Base, BaseModel):
672 672 __tablename__ = 'repositories'
673 673 __table_args__ = (
674 674 UniqueConstraint('repo_name'),
675 675 Index('r_repo_name_idx', 'repo_name'),
676 676 {'extend_existing': True, 'mysql_engine': 'InnoDB',
677 677 'mysql_charset': 'utf8'},
678 678 )
679 679
680 680 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
681 681 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
682 682 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
683 683 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
684 684 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
685 685 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
686 686 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
687 687 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
688 688 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
689 689 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
690 690 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
691 691 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
692 692 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
693 693 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
694 694 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
695 695
696 696 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
697 697 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
698 698
699 699 user = relationship('User')
700 700 fork = relationship('Repository', remote_side=repo_id)
701 701 group = relationship('RepoGroup')
702 702 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
703 703 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
704 704 stats = relationship('Statistics', cascade='all', uselist=False)
705 705
706 706 followers = relationship('UserFollowing',
707 707 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
708 708 cascade='all')
709 709
710 710 logs = relationship('UserLog')
711 711 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
712 712
713 713 pull_requests_org = relationship('PullRequest',
714 714 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
715 715 cascade="all, delete, delete-orphan")
716 716
717 717 pull_requests_other = relationship('PullRequest',
718 718 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
719 719 cascade="all, delete, delete-orphan")
720 720
721 721 def __unicode__(self):
722 722 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
723 723 self.repo_name)
724 724
725 725 @hybrid_property
726 726 def locked(self):
727 727 # always should return [user_id, timelocked]
728 728 if self._locked:
729 729 _lock_info = self._locked.split(':')
730 730 return int(_lock_info[0]), _lock_info[1]
731 731 return [None, None]
732 732
733 733 @locked.setter
734 734 def locked(self, val):
735 735 if val and isinstance(val, (list, tuple)):
736 736 self._locked = ':'.join(map(str, val))
737 737 else:
738 738 self._locked = None
739 739
740 740 @hybrid_property
741 741 def changeset_cache(self):
742 742 from rhodecode.lib.vcs.backends.base import EmptyChangeset
743 743 dummy = EmptyChangeset().__json__()
744 744 if not self._changeset_cache:
745 745 return dummy
746 746 try:
747 747 return json.loads(self._changeset_cache)
748 748 except TypeError:
749 749 return dummy
750 750
751 751 @changeset_cache.setter
752 752 def changeset_cache(self, val):
753 753 try:
754 754 self._changeset_cache = json.dumps(val)
755 755 except:
756 756 log.error(traceback.format_exc())
757 757
758 758 @classmethod
759 759 def url_sep(cls):
760 760 return URL_SEP
761 761
762 762 @classmethod
763 763 def normalize_repo_name(cls, repo_name):
764 764 """
765 765 Normalizes os specific repo_name to the format internally stored inside
766 766 dabatabase using URL_SEP
767 767
768 768 :param cls:
769 769 :param repo_name:
770 770 """
771 771 return cls.url_sep().join(repo_name.split(os.sep))
772 772
773 773 @classmethod
774 774 def get_by_repo_name(cls, repo_name):
775 775 q = Session().query(cls).filter(cls.repo_name == repo_name)
776 776 q = q.options(joinedload(Repository.fork))\
777 777 .options(joinedload(Repository.user))\
778 778 .options(joinedload(Repository.group))
779 779 return q.scalar()
780 780
781 781 @classmethod
782 782 def get_by_full_path(cls, repo_full_path):
783 783 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
784 784 repo_name = cls.normalize_repo_name(repo_name)
785 785 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
786 786
787 787 @classmethod
788 788 def get_repo_forks(cls, repo_id):
789 789 return cls.query().filter(Repository.fork_id == repo_id)
790 790
791 791 @classmethod
792 792 def base_path(cls):
793 793 """
794 794 Returns base path when all repos are stored
795 795
796 796 :param cls:
797 797 """
798 798 q = Session().query(RhodeCodeUi)\
799 799 .filter(RhodeCodeUi.ui_key == cls.url_sep())
800 800 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
801 801 return q.one().ui_value
802 802
803 803 @property
804 804 def forks(self):
805 805 """
806 806 Return forks of this repo
807 807 """
808 808 return Repository.get_repo_forks(self.repo_id)
809 809
810 810 @property
811 811 def parent(self):
812 812 """
813 813 Returns fork parent
814 814 """
815 815 return self.fork
816 816
817 817 @property
818 818 def just_name(self):
819 819 return self.repo_name.split(Repository.url_sep())[-1]
820 820
821 821 @property
822 822 def groups_with_parents(self):
823 823 groups = []
824 824 if self.group is None:
825 825 return groups
826 826
827 827 cur_gr = self.group
828 828 groups.insert(0, cur_gr)
829 829 while 1:
830 830 gr = getattr(cur_gr, 'parent_group', None)
831 831 cur_gr = cur_gr.parent_group
832 832 if gr is None:
833 833 break
834 834 groups.insert(0, gr)
835 835
836 836 return groups
837 837
838 838 @property
839 839 def groups_and_repo(self):
840 840 return self.groups_with_parents, self.just_name
841 841
842 842 @LazyProperty
843 843 def repo_path(self):
844 844 """
845 845 Returns base full path for that repository means where it actually
846 846 exists on a filesystem
847 847 """
848 848 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
849 849 Repository.url_sep())
850 850 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
851 851 return q.one().ui_value
852 852
853 853 @property
854 854 def repo_full_path(self):
855 855 p = [self.repo_path]
856 856 # we need to split the name by / since this is how we store the
857 857 # names in the database, but that eventually needs to be converted
858 858 # into a valid system path
859 859 p += self.repo_name.split(Repository.url_sep())
860 860 return os.path.join(*p)
861 861
862 862 @property
863 863 def cache_keys(self):
864 864 """
865 865 Returns associated cache keys for that repo
866 866 """
867 867 return CacheInvalidation.query()\
868 868 .filter(CacheInvalidation.cache_args == self.repo_name)\
869 869 .order_by(CacheInvalidation.cache_key)\
870 870 .all()
871 871
872 872 def get_new_name(self, repo_name):
873 873 """
874 874 returns new full repository name based on assigned group and new new
875 875
876 876 :param group_name:
877 877 """
878 878 path_prefix = self.group.full_path_splitted if self.group else []
879 879 return Repository.url_sep().join(path_prefix + [repo_name])
880 880
881 881 @property
882 882 def _ui(self):
883 883 """
884 884 Creates an db based ui object for this repository
885 885 """
886 886 from rhodecode.lib.utils import make_ui
887 887 return make_ui('db', clear_session=False)
888 888
889 889 @classmethod
890 890 def inject_ui(cls, repo, extras={}):
891 891 from rhodecode.lib.vcs.backends.hg import MercurialRepository
892 892 from rhodecode.lib.vcs.backends.git import GitRepository
893 893 required = (MercurialRepository, GitRepository)
894 894 if not isinstance(repo, required):
895 895 raise Exception('repo must be instance of %s' % required)
896 896
897 897 # inject ui extra param to log this action via push logger
898 898 for k, v in extras.items():
899 899 repo._repo.ui.setconfig('rhodecode_extras', k, v)
900 900
901 901 @classmethod
902 902 def is_valid(cls, repo_name):
903 903 """
904 904 returns True if given repo name is a valid filesystem repository
905 905
906 906 :param cls:
907 907 :param repo_name:
908 908 """
909 909 from rhodecode.lib.utils import is_valid_repo
910 910
911 911 return is_valid_repo(repo_name, cls.base_path())
912 912
913 913 def get_api_data(self):
914 914 """
915 915 Common function for generating repo api data
916 916
917 917 """
918 918 repo = self
919 919 data = dict(
920 920 repo_id=repo.repo_id,
921 921 repo_name=repo.repo_name,
922 922 repo_type=repo.repo_type,
923 923 clone_uri=repo.clone_uri,
924 924 private=repo.private,
925 925 created_on=repo.created_on,
926 926 description=repo.description,
927 927 landing_rev=repo.landing_rev,
928 928 owner=repo.user.username,
929 929 fork_of=repo.fork.repo_name if repo.fork else None,
930 930 enable_statistics=repo.enable_statistics,
931 931 enable_locking=repo.enable_locking,
932 932 enable_downloads=repo.enable_downloads,
933 933 last_changeset=repo.changeset_cache
934 934 )
935 935
936 936 return data
937 937
938 938 @classmethod
939 939 def lock(cls, repo, user_id):
940 940 repo.locked = [user_id, time.time()]
941 941 Session().add(repo)
942 942 Session().commit()
943 943
944 944 @classmethod
945 945 def unlock(cls, repo):
946 946 repo.locked = None
947 947 Session().add(repo)
948 948 Session().commit()
949 949
950 950 @property
951 951 def last_db_change(self):
952 952 return self.updated_on
953 953
954 954 def clone_url(self, **override):
955 955 from pylons import url
956 956 from urlparse import urlparse
957 957 import urllib
958 958 parsed_url = urlparse(url('home', qualified=True))
959 959 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
960 960 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
961 961 args = {
962 962 'user': '',
963 963 'pass': '',
964 964 'scheme': parsed_url.scheme,
965 965 'netloc': parsed_url.netloc,
966 966 'prefix': decoded_path,
967 967 'path': self.repo_name
968 968 }
969 969
970 970 args.update(override)
971 971 return default_clone_uri % args
972 972
973 973 #==========================================================================
974 974 # SCM PROPERTIES
975 975 #==========================================================================
976 976
977 977 def get_changeset(self, rev=None):
978 978 return get_changeset_safe(self.scm_instance, rev)
979 979
980 980 def get_landing_changeset(self):
981 981 """
982 982 Returns landing changeset, or if that doesn't exist returns the tip
983 983 """
984 984 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
985 985 return cs
986 986
987 987 def update_changeset_cache(self, cs_cache=None):
988 988 """
989 989 Update cache of last changeset for repository, keys should be::
990 990
991 991 short_id
992 992 raw_id
993 993 revision
994 994 message
995 995 date
996 996 author
997 997
998 998 :param cs_cache:
999 999 """
1000 1000 from rhodecode.lib.vcs.backends.base import BaseChangeset
1001 1001 if cs_cache is None:
1002 1002 cs_cache = self.get_changeset()
1003 1003 if isinstance(cs_cache, BaseChangeset):
1004 1004 cs_cache = cs_cache.__json__()
1005 1005
1006 1006 if (cs_cache != self.changeset_cache
1007 1007 or not self.last_change
1008 1008 or not self.changeset_cache):
1009 1009 _default = datetime.datetime.fromtimestamp(0)
1010 1010 last_change = cs_cache.get('date') or self.last_change or _default
1011 1011 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
1012 1012 self.updated_on = last_change
1013 1013 self.changeset_cache = cs_cache
1014 1014 Session().add(self)
1015 1015 Session().commit()
1016 1016 else:
1017 1017 log.debug('Skipping repo:%s already with latest changes' % self)
1018 1018
1019 1019 @property
1020 1020 def tip(self):
1021 1021 return self.get_changeset('tip')
1022 1022
1023 1023 @property
1024 1024 def author(self):
1025 1025 return self.tip.author
1026 1026
1027 1027 @property
1028 1028 def last_change(self):
1029 1029 return self.scm_instance.last_change
1030 1030
1031 1031 def get_comments(self, revisions=None):
1032 1032 """
1033 1033 Returns comments for this repository grouped by revisions
1034 1034
1035 1035 :param revisions: filter query by revisions only
1036 1036 """
1037 1037 cmts = ChangesetComment.query()\
1038 1038 .filter(ChangesetComment.repo == self)
1039 1039 if revisions:
1040 1040 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1041 1041 grouped = defaultdict(list)
1042 1042 for cmt in cmts.all():
1043 1043 grouped[cmt.revision].append(cmt)
1044 1044 return grouped
1045 1045
1046 1046 def statuses(self, revisions=None):
1047 1047 """
1048 1048 Returns statuses for this repository
1049 1049
1050 1050 :param revisions: list of revisions to get statuses for
1051 1051 :type revisions: list
1052 1052 """
1053 1053
1054 1054 statuses = ChangesetStatus.query()\
1055 1055 .filter(ChangesetStatus.repo == self)\
1056 1056 .filter(ChangesetStatus.version == 0)
1057 1057 if revisions:
1058 1058 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1059 1059 grouped = {}
1060 1060
1061 1061 #maybe we have open new pullrequest without a status ?
1062 1062 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1063 1063 status_lbl = ChangesetStatus.get_status_lbl(stat)
1064 1064 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1065 1065 for rev in pr.revisions:
1066 1066 pr_id = pr.pull_request_id
1067 1067 pr_repo = pr.other_repo.repo_name
1068 1068 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1069 1069
1070 1070 for stat in statuses.all():
1071 1071 pr_id = pr_repo = None
1072 1072 if stat.pull_request:
1073 1073 pr_id = stat.pull_request.pull_request_id
1074 1074 pr_repo = stat.pull_request.other_repo.repo_name
1075 1075 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1076 1076 pr_id, pr_repo]
1077 1077 return grouped
1078 1078
1079 1079 #==========================================================================
1080 1080 # SCM CACHE INSTANCE
1081 1081 #==========================================================================
1082 1082
1083 1083 @property
1084 1084 def invalidate(self):
1085 1085 return CacheInvalidation.invalidate(self.repo_name)
1086 1086
1087 1087 def set_invalidate(self):
1088 1088 """
1089 1089 set a cache for invalidation for this instance
1090 1090 """
1091 1091 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1092 1092
1093 1093 @LazyProperty
1094 1094 def scm_instance(self):
1095 1095 import rhodecode
1096 1096 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1097 1097 if full_cache:
1098 1098 return self.scm_instance_cached()
1099 1099 return self.__get_instance()
1100 1100
1101 1101 def scm_instance_cached(self, cache_map=None):
1102 1102 @cache_region('long_term')
1103 1103 def _c(repo_name):
1104 1104 return self.__get_instance()
1105 1105 rn = self.repo_name
1106 1106 log.debug('Getting cached instance of repo')
1107 1107
1108 1108 if cache_map:
1109 1109 # get using prefilled cache_map
1110 1110 invalidate_repo = cache_map[self.repo_name]
1111 1111 if invalidate_repo:
1112 1112 invalidate_repo = (None if invalidate_repo.cache_active
1113 1113 else invalidate_repo)
1114 1114 else:
1115 1115 # get from invalidate
1116 1116 invalidate_repo = self.invalidate
1117 1117
1118 1118 if invalidate_repo is not None:
1119 1119 region_invalidate(_c, None, rn)
1120 1120 # update our cache
1121 1121 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1122 1122 return _c(rn)
1123 1123
1124 1124 def __get_instance(self):
1125 1125 repo_full_path = self.repo_full_path
1126 1126 try:
1127 1127 alias = get_scm(repo_full_path)[0]
1128 1128 log.debug('Creating instance of %s repository' % alias)
1129 1129 backend = get_backend(alias)
1130 1130 except VCSError:
1131 1131 log.error(traceback.format_exc())
1132 1132 log.error('Perhaps this repository is in db and not in '
1133 1133 'filesystem run rescan repositories with '
1134 1134 '"destroy old data " option from admin panel')
1135 1135 return
1136 1136
1137 1137 if alias == 'hg':
1138 1138
1139 1139 repo = backend(safe_str(repo_full_path), create=False,
1140 1140 baseui=self._ui)
1141 1141 # skip hidden web repository
1142 1142 if repo._get_hidden():
1143 1143 return
1144 1144 else:
1145 1145 repo = backend(repo_full_path, create=False)
1146 1146
1147 1147 return repo
1148 1148
1149 1149
1150 1150 class RepoGroup(Base, BaseModel):
1151 1151 __tablename__ = 'groups'
1152 1152 __table_args__ = (
1153 1153 UniqueConstraint('group_name', 'group_parent_id'),
1154 1154 CheckConstraint('group_id != group_parent_id'),
1155 1155 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1156 1156 'mysql_charset': 'utf8'},
1157 1157 )
1158 1158 __mapper_args__ = {'order_by': 'group_name'}
1159 1159
1160 1160 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1161 1161 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1162 1162 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1163 1163 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1164 1164 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1165 1165
1166 1166 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1167 1167 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1168 1168
1169 1169 parent_group = relationship('RepoGroup', remote_side=group_id)
1170 1170
1171 1171 def __init__(self, group_name='', parent_group=None):
1172 1172 self.group_name = group_name
1173 1173 self.parent_group = parent_group
1174 1174
1175 1175 def __unicode__(self):
1176 1176 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1177 1177 self.group_name)
1178 1178
1179 1179 @classmethod
1180 def groups_choices(cls, groups=None, check_perms=False, show_empty_group=True):
1180 def groups_choices(cls, groups=None, show_empty_group=True):
1181 1181 from webhelpers.html import literal as _literal
1182 from rhodecode.model.scm import ScmModel
1183 1182 if not groups:
1184 1183 groups = cls.query().all()
1185 if check_perms:
1186 #filter group user have access to, it's done
1187 #magically inside ScmModel based on current user
1188 groups = ScmModel().get_repos_groups(groups)
1184
1189 1185 repo_groups = []
1190 1186 if show_empty_group:
1191 1187 repo_groups = [('-1', '-- no parent --')]
1192 1188 sep = ' &raquo; '
1193 1189 _name = lambda k: _literal(sep.join(k))
1194 1190
1195 1191 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1196 1192 for x in groups])
1197 1193
1198 1194 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1199 1195 return repo_groups
1200 1196
1201 1197 @classmethod
1202 1198 def url_sep(cls):
1203 1199 return URL_SEP
1204 1200
1205 1201 @classmethod
1206 1202 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1207 1203 if case_insensitive:
1208 1204 gr = cls.query()\
1209 1205 .filter(cls.group_name.ilike(group_name))
1210 1206 else:
1211 1207 gr = cls.query()\
1212 1208 .filter(cls.group_name == group_name)
1213 1209 if cache:
1214 1210 gr = gr.options(FromCache(
1215 1211 "sql_cache_short",
1216 1212 "get_group_%s" % _hash_key(group_name)
1217 1213 )
1218 1214 )
1219 1215 return gr.scalar()
1220 1216
1221 1217 @property
1222 1218 def parents(self):
1223 1219 parents_recursion_limit = 5
1224 1220 groups = []
1225 1221 if self.parent_group is None:
1226 1222 return groups
1227 1223 cur_gr = self.parent_group
1228 1224 groups.insert(0, cur_gr)
1229 1225 cnt = 0
1230 1226 while 1:
1231 1227 cnt += 1
1232 1228 gr = getattr(cur_gr, 'parent_group', None)
1233 1229 cur_gr = cur_gr.parent_group
1234 1230 if gr is None:
1235 1231 break
1236 1232 if cnt == parents_recursion_limit:
1237 1233 # this will prevent accidental infinit loops
1238 1234 log.error('group nested more than %s' %
1239 1235 parents_recursion_limit)
1240 1236 break
1241 1237
1242 1238 groups.insert(0, gr)
1243 1239 return groups
1244 1240
1245 1241 @property
1246 1242 def children(self):
1247 1243 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1248 1244
1249 1245 @property
1250 1246 def name(self):
1251 1247 return self.group_name.split(RepoGroup.url_sep())[-1]
1252 1248
1253 1249 @property
1254 1250 def full_path(self):
1255 1251 return self.group_name
1256 1252
1257 1253 @property
1258 1254 def full_path_splitted(self):
1259 1255 return self.group_name.split(RepoGroup.url_sep())
1260 1256
1261 1257 @property
1262 1258 def repositories(self):
1263 1259 return Repository.query()\
1264 1260 .filter(Repository.group == self)\
1265 1261 .order_by(Repository.repo_name)
1266 1262
1267 1263 @property
1268 1264 def repositories_recursive_count(self):
1269 1265 cnt = self.repositories.count()
1270 1266
1271 1267 def children_count(group):
1272 1268 cnt = 0
1273 1269 for child in group.children:
1274 1270 cnt += child.repositories.count()
1275 1271 cnt += children_count(child)
1276 1272 return cnt
1277 1273
1278 1274 return cnt + children_count(self)
1279 1275
1280 1276 def recursive_groups_and_repos(self):
1281 1277 """
1282 1278 Recursive return all groups, with repositories in those groups
1283 1279 """
1284 1280 all_ = []
1285 1281
1286 1282 def _get_members(root_gr):
1287 1283 for r in root_gr.repositories:
1288 1284 all_.append(r)
1289 1285 childs = root_gr.children.all()
1290 1286 if childs:
1291 1287 for gr in childs:
1292 1288 all_.append(gr)
1293 1289 _get_members(gr)
1294 1290
1295 1291 _get_members(self)
1296 1292 return [self] + all_
1297 1293
1298 1294 def get_new_name(self, group_name):
1299 1295 """
1300 1296 returns new full group name based on parent and new name
1301 1297
1302 1298 :param group_name:
1303 1299 """
1304 1300 path_prefix = (self.parent_group.full_path_splitted if
1305 1301 self.parent_group else [])
1306 1302 return RepoGroup.url_sep().join(path_prefix + [group_name])
1307 1303
1308 1304
1309 1305 class Permission(Base, BaseModel):
1310 1306 __tablename__ = 'permissions'
1311 1307 __table_args__ = (
1312 1308 Index('p_perm_name_idx', 'permission_name'),
1313 1309 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1314 1310 'mysql_charset': 'utf8'},
1315 1311 )
1316 1312 PERMS = [
1317 1313 ('repository.none', _('Repository no access')),
1318 1314 ('repository.read', _('Repository read access')),
1319 1315 ('repository.write', _('Repository write access')),
1320 1316 ('repository.admin', _('Repository admin access')),
1321 1317
1322 1318 ('group.none', _('Repositories Group no access')),
1323 1319 ('group.read', _('Repositories Group read access')),
1324 1320 ('group.write', _('Repositories Group write access')),
1325 1321 ('group.admin', _('Repositories Group admin access')),
1326 1322
1327 1323 ('hg.admin', _('RhodeCode Administrator')),
1328 1324 ('hg.create.none', _('Repository creation disabled')),
1329 1325 ('hg.create.repository', _('Repository creation enabled')),
1330 1326 ('hg.fork.none', _('Repository forking disabled')),
1331 1327 ('hg.fork.repository', _('Repository forking enabled')),
1332 1328 ('hg.register.none', _('Register disabled')),
1333 1329 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1334 1330 'with manual activation')),
1335 1331
1336 1332 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1337 1333 'with auto activation')),
1338 1334 ]
1339 1335
1340 1336 # defines which permissions are more important higher the more important
1341 1337 PERM_WEIGHTS = {
1342 1338 'repository.none': 0,
1343 1339 'repository.read': 1,
1344 1340 'repository.write': 3,
1345 1341 'repository.admin': 4,
1346 1342
1347 1343 'group.none': 0,
1348 1344 'group.read': 1,
1349 1345 'group.write': 3,
1350 1346 'group.admin': 4,
1351 1347
1352 1348 'hg.fork.none': 0,
1353 1349 'hg.fork.repository': 1,
1354 1350 'hg.create.none': 0,
1355 1351 'hg.create.repository':1
1356 1352 }
1357 1353
1358 1354 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1359 1355 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1360 1356 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1361 1357
1362 1358 def __unicode__(self):
1363 1359 return u"<%s('%s:%s')>" % (
1364 1360 self.__class__.__name__, self.permission_id, self.permission_name
1365 1361 )
1366 1362
1367 1363 @classmethod
1368 1364 def get_by_key(cls, key):
1369 1365 return cls.query().filter(cls.permission_name == key).scalar()
1370 1366
1371 1367 @classmethod
1372 1368 def get_default_perms(cls, default_user_id):
1373 1369 q = Session().query(UserRepoToPerm, Repository, cls)\
1374 1370 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1375 1371 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1376 1372 .filter(UserRepoToPerm.user_id == default_user_id)
1377 1373
1378 1374 return q.all()
1379 1375
1380 1376 @classmethod
1381 1377 def get_default_group_perms(cls, default_user_id):
1382 1378 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1383 1379 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1384 1380 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1385 1381 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1386 1382
1387 1383 return q.all()
1388 1384
1389 1385
1390 1386 class UserRepoToPerm(Base, BaseModel):
1391 1387 __tablename__ = 'repo_to_perm'
1392 1388 __table_args__ = (
1393 1389 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1394 1390 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1395 1391 'mysql_charset': 'utf8'}
1396 1392 )
1397 1393 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1398 1394 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1399 1395 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1400 1396 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1401 1397
1402 1398 user = relationship('User')
1403 1399 repository = relationship('Repository')
1404 1400 permission = relationship('Permission')
1405 1401
1406 1402 @classmethod
1407 1403 def create(cls, user, repository, permission):
1408 1404 n = cls()
1409 1405 n.user = user
1410 1406 n.repository = repository
1411 1407 n.permission = permission
1412 1408 Session().add(n)
1413 1409 return n
1414 1410
1415 1411 def __unicode__(self):
1416 1412 return u'<user:%s => %s >' % (self.user, self.repository)
1417 1413
1418 1414
1419 1415 class UserToPerm(Base, BaseModel):
1420 1416 __tablename__ = 'user_to_perm'
1421 1417 __table_args__ = (
1422 1418 UniqueConstraint('user_id', 'permission_id'),
1423 1419 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1424 1420 'mysql_charset': 'utf8'}
1425 1421 )
1426 1422 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1427 1423 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1428 1424 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1429 1425
1430 1426 user = relationship('User')
1431 1427 permission = relationship('Permission', lazy='joined')
1432 1428
1433 1429
1434 1430 class UsersGroupRepoToPerm(Base, BaseModel):
1435 1431 __tablename__ = 'users_group_repo_to_perm'
1436 1432 __table_args__ = (
1437 1433 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1438 1434 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1439 1435 'mysql_charset': 'utf8'}
1440 1436 )
1441 1437 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1442 1438 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1443 1439 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1444 1440 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1445 1441
1446 1442 users_group = relationship('UsersGroup')
1447 1443 permission = relationship('Permission')
1448 1444 repository = relationship('Repository')
1449 1445
1450 1446 @classmethod
1451 1447 def create(cls, users_group, repository, permission):
1452 1448 n = cls()
1453 1449 n.users_group = users_group
1454 1450 n.repository = repository
1455 1451 n.permission = permission
1456 1452 Session().add(n)
1457 1453 return n
1458 1454
1459 1455 def __unicode__(self):
1460 1456 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1461 1457
1462 1458
1463 1459 class UsersGroupToPerm(Base, BaseModel):
1464 1460 __tablename__ = 'users_group_to_perm'
1465 1461 __table_args__ = (
1466 1462 UniqueConstraint('users_group_id', 'permission_id',),
1467 1463 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1468 1464 'mysql_charset': 'utf8'}
1469 1465 )
1470 1466 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1471 1467 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1472 1468 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1473 1469
1474 1470 users_group = relationship('UsersGroup')
1475 1471 permission = relationship('Permission')
1476 1472
1477 1473
1478 1474 class UserRepoGroupToPerm(Base, BaseModel):
1479 1475 __tablename__ = 'user_repo_group_to_perm'
1480 1476 __table_args__ = (
1481 1477 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1482 1478 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1483 1479 'mysql_charset': 'utf8'}
1484 1480 )
1485 1481
1486 1482 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1487 1483 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1488 1484 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1489 1485 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1490 1486
1491 1487 user = relationship('User')
1492 1488 group = relationship('RepoGroup')
1493 1489 permission = relationship('Permission')
1494 1490
1495 1491
1496 1492 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1497 1493 __tablename__ = 'users_group_repo_group_to_perm'
1498 1494 __table_args__ = (
1499 1495 UniqueConstraint('users_group_id', 'group_id'),
1500 1496 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1501 1497 'mysql_charset': 'utf8'}
1502 1498 )
1503 1499
1504 1500 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1505 1501 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1506 1502 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1507 1503 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1508 1504
1509 1505 users_group = relationship('UsersGroup')
1510 1506 permission = relationship('Permission')
1511 1507 group = relationship('RepoGroup')
1512 1508
1513 1509
1514 1510 class Statistics(Base, BaseModel):
1515 1511 __tablename__ = 'statistics'
1516 1512 __table_args__ = (
1517 1513 UniqueConstraint('repository_id'),
1518 1514 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1519 1515 'mysql_charset': 'utf8'}
1520 1516 )
1521 1517 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1522 1518 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1523 1519 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1524 1520 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1525 1521 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1526 1522 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1527 1523
1528 1524 repository = relationship('Repository', single_parent=True)
1529 1525
1530 1526
1531 1527 class UserFollowing(Base, BaseModel):
1532 1528 __tablename__ = 'user_followings'
1533 1529 __table_args__ = (
1534 1530 UniqueConstraint('user_id', 'follows_repository_id'),
1535 1531 UniqueConstraint('user_id', 'follows_user_id'),
1536 1532 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1537 1533 'mysql_charset': 'utf8'}
1538 1534 )
1539 1535
1540 1536 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1541 1537 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1542 1538 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1543 1539 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1544 1540 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1545 1541
1546 1542 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1547 1543
1548 1544 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1549 1545 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1550 1546
1551 1547 @classmethod
1552 1548 def get_repo_followers(cls, repo_id):
1553 1549 return cls.query().filter(cls.follows_repo_id == repo_id)
1554 1550
1555 1551
1556 1552 class CacheInvalidation(Base, BaseModel):
1557 1553 __tablename__ = 'cache_invalidation'
1558 1554 __table_args__ = (
1559 1555 UniqueConstraint('cache_key'),
1560 1556 Index('key_idx', 'cache_key'),
1561 1557 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1562 1558 'mysql_charset': 'utf8'},
1563 1559 )
1564 1560 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1565 1561 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1566 1562 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1567 1563 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1568 1564
1569 1565 def __init__(self, cache_key, cache_args=''):
1570 1566 self.cache_key = cache_key
1571 1567 self.cache_args = cache_args
1572 1568 self.cache_active = False
1573 1569
1574 1570 def __unicode__(self):
1575 1571 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1576 1572 self.cache_id, self.cache_key)
1577 1573
1578 1574 @property
1579 1575 def prefix(self):
1580 1576 _split = self.cache_key.split(self.cache_args, 1)
1581 1577 if _split and len(_split) == 2:
1582 1578 return _split[0]
1583 1579 return ''
1584 1580
1585 1581 @classmethod
1586 1582 def clear_cache(cls):
1587 1583 cls.query().delete()
1588 1584
1589 1585 @classmethod
1590 1586 def _get_key(cls, key):
1591 1587 """
1592 1588 Wrapper for generating a key, together with a prefix
1593 1589
1594 1590 :param key:
1595 1591 """
1596 1592 import rhodecode
1597 1593 prefix = ''
1598 1594 org_key = key
1599 1595 iid = rhodecode.CONFIG.get('instance_id')
1600 1596 if iid:
1601 1597 prefix = iid
1602 1598
1603 1599 return "%s%s" % (prefix, key), prefix, org_key
1604 1600
1605 1601 @classmethod
1606 1602 def get_by_key(cls, key):
1607 1603 return cls.query().filter(cls.cache_key == key).scalar()
1608 1604
1609 1605 @classmethod
1610 1606 def get_by_repo_name(cls, repo_name):
1611 1607 return cls.query().filter(cls.cache_args == repo_name).all()
1612 1608
1613 1609 @classmethod
1614 1610 def _get_or_create_key(cls, key, repo_name, commit=True):
1615 1611 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1616 1612 if not inv_obj:
1617 1613 try:
1618 1614 inv_obj = CacheInvalidation(key, repo_name)
1619 1615 Session().add(inv_obj)
1620 1616 if commit:
1621 1617 Session().commit()
1622 1618 except Exception:
1623 1619 log.error(traceback.format_exc())
1624 1620 Session().rollback()
1625 1621 return inv_obj
1626 1622
1627 1623 @classmethod
1628 1624 def invalidate(cls, key):
1629 1625 """
1630 1626 Returns Invalidation object if this given key should be invalidated
1631 1627 None otherwise. `cache_active = False` means that this cache
1632 1628 state is not valid and needs to be invalidated
1633 1629
1634 1630 :param key:
1635 1631 """
1636 1632 repo_name = key
1637 1633 repo_name = remove_suffix(repo_name, '_README')
1638 1634 repo_name = remove_suffix(repo_name, '_RSS')
1639 1635 repo_name = remove_suffix(repo_name, '_ATOM')
1640 1636
1641 1637 # adds instance prefix
1642 1638 key, _prefix, _org_key = cls._get_key(key)
1643 1639 inv = cls._get_or_create_key(key, repo_name)
1644 1640
1645 1641 if inv and inv.cache_active is False:
1646 1642 return inv
1647 1643
1648 1644 @classmethod
1649 1645 def set_invalidate(cls, key=None, repo_name=None):
1650 1646 """
1651 1647 Mark this Cache key for invalidation, either by key or whole
1652 1648 cache sets based on repo_name
1653 1649
1654 1650 :param key:
1655 1651 """
1656 1652 invalidated_keys = []
1657 1653 if key:
1658 1654 key, _prefix, _org_key = cls._get_key(key)
1659 1655 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1660 1656 elif repo_name:
1661 1657 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1662 1658
1663 1659 try:
1664 1660 for inv_obj in inv_objs:
1665 1661 inv_obj.cache_active = False
1666 1662 log.debug('marking %s key for invalidation based on key=%s,repo_name=%s'
1667 1663 % (inv_obj, key, repo_name))
1668 1664 invalidated_keys.append(inv_obj.cache_key)
1669 1665 Session().add(inv_obj)
1670 1666 Session().commit()
1671 1667 except Exception:
1672 1668 log.error(traceback.format_exc())
1673 1669 Session().rollback()
1674 1670 return invalidated_keys
1675 1671
1676 1672 @classmethod
1677 1673 def set_valid(cls, key):
1678 1674 """
1679 1675 Mark this cache key as active and currently cached
1680 1676
1681 1677 :param key:
1682 1678 """
1683 1679 inv_obj = cls.get_by_key(key)
1684 1680 inv_obj.cache_active = True
1685 1681 Session().add(inv_obj)
1686 1682 Session().commit()
1687 1683
1688 1684 @classmethod
1689 1685 def get_cache_map(cls):
1690 1686
1691 1687 class cachemapdict(dict):
1692 1688
1693 1689 def __init__(self, *args, **kwargs):
1694 1690 fixkey = kwargs.get('fixkey')
1695 1691 if fixkey:
1696 1692 del kwargs['fixkey']
1697 1693 self.fixkey = fixkey
1698 1694 super(cachemapdict, self).__init__(*args, **kwargs)
1699 1695
1700 1696 def __getattr__(self, name):
1701 1697 key = name
1702 1698 if self.fixkey:
1703 1699 key, _prefix, _org_key = cls._get_key(key)
1704 1700 if key in self.__dict__:
1705 1701 return self.__dict__[key]
1706 1702 else:
1707 1703 return self[key]
1708 1704
1709 1705 def __getitem__(self, key):
1710 1706 if self.fixkey:
1711 1707 key, _prefix, _org_key = cls._get_key(key)
1712 1708 try:
1713 1709 return super(cachemapdict, self).__getitem__(key)
1714 1710 except KeyError:
1715 1711 return
1716 1712
1717 1713 cache_map = cachemapdict(fixkey=True)
1718 1714 for obj in cls.query().all():
1719 1715 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1720 1716 return cache_map
1721 1717
1722 1718
1723 1719 class ChangesetComment(Base, BaseModel):
1724 1720 __tablename__ = 'changeset_comments'
1725 1721 __table_args__ = (
1726 1722 Index('cc_revision_idx', 'revision'),
1727 1723 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1728 1724 'mysql_charset': 'utf8'},
1729 1725 )
1730 1726 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1731 1727 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1732 1728 revision = Column('revision', String(40), nullable=True)
1733 1729 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1734 1730 line_no = Column('line_no', Unicode(10), nullable=True)
1735 1731 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1736 1732 f_path = Column('f_path', Unicode(1000), nullable=True)
1737 1733 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1738 1734 text = Column('text', UnicodeText(25000), nullable=False)
1739 1735 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1740 1736 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1741 1737
1742 1738 author = relationship('User', lazy='joined')
1743 1739 repo = relationship('Repository')
1744 1740 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1745 1741 pull_request = relationship('PullRequest', lazy='joined')
1746 1742
1747 1743 @classmethod
1748 1744 def get_users(cls, revision=None, pull_request_id=None):
1749 1745 """
1750 1746 Returns user associated with this ChangesetComment. ie those
1751 1747 who actually commented
1752 1748
1753 1749 :param cls:
1754 1750 :param revision:
1755 1751 """
1756 1752 q = Session().query(User)\
1757 1753 .join(ChangesetComment.author)
1758 1754 if revision:
1759 1755 q = q.filter(cls.revision == revision)
1760 1756 elif pull_request_id:
1761 1757 q = q.filter(cls.pull_request_id == pull_request_id)
1762 1758 return q.all()
1763 1759
1764 1760
1765 1761 class ChangesetStatus(Base, BaseModel):
1766 1762 __tablename__ = 'changeset_statuses'
1767 1763 __table_args__ = (
1768 1764 Index('cs_revision_idx', 'revision'),
1769 1765 Index('cs_version_idx', 'version'),
1770 1766 UniqueConstraint('repo_id', 'revision', 'version'),
1771 1767 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1772 1768 'mysql_charset': 'utf8'}
1773 1769 )
1774 1770 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1775 1771 STATUS_APPROVED = 'approved'
1776 1772 STATUS_REJECTED = 'rejected'
1777 1773 STATUS_UNDER_REVIEW = 'under_review'
1778 1774
1779 1775 STATUSES = [
1780 1776 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1781 1777 (STATUS_APPROVED, _("Approved")),
1782 1778 (STATUS_REJECTED, _("Rejected")),
1783 1779 (STATUS_UNDER_REVIEW, _("Under Review")),
1784 1780 ]
1785 1781
1786 1782 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1787 1783 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1788 1784 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1789 1785 revision = Column('revision', String(40), nullable=False)
1790 1786 status = Column('status', String(128), nullable=False, default=DEFAULT)
1791 1787 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1792 1788 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1793 1789 version = Column('version', Integer(), nullable=False, default=0)
1794 1790 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1795 1791
1796 1792 author = relationship('User', lazy='joined')
1797 1793 repo = relationship('Repository')
1798 1794 comment = relationship('ChangesetComment', lazy='joined')
1799 1795 pull_request = relationship('PullRequest', lazy='joined')
1800 1796
1801 1797 def __unicode__(self):
1802 1798 return u"<%s('%s:%s')>" % (
1803 1799 self.__class__.__name__,
1804 1800 self.status, self.author
1805 1801 )
1806 1802
1807 1803 @classmethod
1808 1804 def get_status_lbl(cls, value):
1809 1805 return dict(cls.STATUSES).get(value)
1810 1806
1811 1807 @property
1812 1808 def status_lbl(self):
1813 1809 return ChangesetStatus.get_status_lbl(self.status)
1814 1810
1815 1811
1816 1812 class PullRequest(Base, BaseModel):
1817 1813 __tablename__ = 'pull_requests'
1818 1814 __table_args__ = (
1819 1815 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1820 1816 'mysql_charset': 'utf8'},
1821 1817 )
1822 1818
1823 1819 STATUS_NEW = u'new'
1824 1820 STATUS_OPEN = u'open'
1825 1821 STATUS_CLOSED = u'closed'
1826 1822
1827 1823 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1828 1824 title = Column('title', Unicode(256), nullable=True)
1829 1825 description = Column('description', UnicodeText(10240), nullable=True)
1830 1826 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1831 1827 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1832 1828 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1833 1829 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1834 1830 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1835 1831 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1836 1832 org_ref = Column('org_ref', Unicode(256), nullable=False)
1837 1833 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1838 1834 other_ref = Column('other_ref', Unicode(256), nullable=False)
1839 1835
1840 1836 @hybrid_property
1841 1837 def revisions(self):
1842 1838 return self._revisions.split(':')
1843 1839
1844 1840 @revisions.setter
1845 1841 def revisions(self, val):
1846 1842 self._revisions = ':'.join(val)
1847 1843
1848 1844 @property
1849 1845 def org_ref_parts(self):
1850 1846 return self.org_ref.split(':')
1851 1847
1852 1848 @property
1853 1849 def other_ref_parts(self):
1854 1850 return self.other_ref.split(':')
1855 1851
1856 1852 author = relationship('User', lazy='joined')
1857 1853 reviewers = relationship('PullRequestReviewers',
1858 1854 cascade="all, delete, delete-orphan")
1859 1855 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1860 1856 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1861 1857 statuses = relationship('ChangesetStatus')
1862 1858 comments = relationship('ChangesetComment',
1863 1859 cascade="all, delete, delete-orphan")
1864 1860
1865 1861 def is_closed(self):
1866 1862 return self.status == self.STATUS_CLOSED
1867 1863
1868 1864 def __json__(self):
1869 1865 return dict(
1870 1866 revisions=self.revisions
1871 1867 )
1872 1868
1873 1869
1874 1870 class PullRequestReviewers(Base, BaseModel):
1875 1871 __tablename__ = 'pull_request_reviewers'
1876 1872 __table_args__ = (
1877 1873 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1878 1874 'mysql_charset': 'utf8'},
1879 1875 )
1880 1876
1881 1877 def __init__(self, user=None, pull_request=None):
1882 1878 self.user = user
1883 1879 self.pull_request = pull_request
1884 1880
1885 1881 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1886 1882 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1887 1883 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1888 1884
1889 1885 user = relationship('User')
1890 1886 pull_request = relationship('PullRequest')
1891 1887
1892 1888
1893 1889 class Notification(Base, BaseModel):
1894 1890 __tablename__ = 'notifications'
1895 1891 __table_args__ = (
1896 1892 Index('notification_type_idx', 'type'),
1897 1893 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1898 1894 'mysql_charset': 'utf8'},
1899 1895 )
1900 1896
1901 1897 TYPE_CHANGESET_COMMENT = u'cs_comment'
1902 1898 TYPE_MESSAGE = u'message'
1903 1899 TYPE_MENTION = u'mention'
1904 1900 TYPE_REGISTRATION = u'registration'
1905 1901 TYPE_PULL_REQUEST = u'pull_request'
1906 1902 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1907 1903
1908 1904 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1909 1905 subject = Column('subject', Unicode(512), nullable=True)
1910 1906 body = Column('body', UnicodeText(50000), nullable=True)
1911 1907 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1912 1908 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1913 1909 type_ = Column('type', Unicode(256))
1914 1910
1915 1911 created_by_user = relationship('User')
1916 1912 notifications_to_users = relationship('UserNotification', lazy='joined',
1917 1913 cascade="all, delete, delete-orphan")
1918 1914
1919 1915 @property
1920 1916 def recipients(self):
1921 1917 return [x.user for x in UserNotification.query()\
1922 1918 .filter(UserNotification.notification == self)\
1923 1919 .order_by(UserNotification.user_id.asc()).all()]
1924 1920
1925 1921 @classmethod
1926 1922 def create(cls, created_by, subject, body, recipients, type_=None):
1927 1923 if type_ is None:
1928 1924 type_ = Notification.TYPE_MESSAGE
1929 1925
1930 1926 notification = cls()
1931 1927 notification.created_by_user = created_by
1932 1928 notification.subject = subject
1933 1929 notification.body = body
1934 1930 notification.type_ = type_
1935 1931 notification.created_on = datetime.datetime.now()
1936 1932
1937 1933 for u in recipients:
1938 1934 assoc = UserNotification()
1939 1935 assoc.notification = notification
1940 1936 u.notifications.append(assoc)
1941 1937 Session().add(notification)
1942 1938 return notification
1943 1939
1944 1940 @property
1945 1941 def description(self):
1946 1942 from rhodecode.model.notification import NotificationModel
1947 1943 return NotificationModel().make_description(self)
1948 1944
1949 1945
1950 1946 class UserNotification(Base, BaseModel):
1951 1947 __tablename__ = 'user_to_notification'
1952 1948 __table_args__ = (
1953 1949 UniqueConstraint('user_id', 'notification_id'),
1954 1950 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1955 1951 'mysql_charset': 'utf8'}
1956 1952 )
1957 1953 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1958 1954 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1959 1955 read = Column('read', Boolean, default=False)
1960 1956 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1961 1957
1962 1958 user = relationship('User', lazy="joined")
1963 1959 notification = relationship('Notification', lazy="joined",
1964 1960 order_by=lambda: Notification.created_on.desc(),)
1965 1961
1966 1962 def mark_as_read(self):
1967 1963 self.read = True
1968 1964 Session().add(self)
1969 1965
1970 1966
1971 1967 class DbMigrateVersion(Base, BaseModel):
1972 1968 __tablename__ = 'db_migrate_version'
1973 1969 __table_args__ = (
1974 1970 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1975 1971 'mysql_charset': 'utf8'},
1976 1972 )
1977 1973 repository_id = Column('repository_id', String(250), primary_key=True)
1978 1974 repository_path = Column('repository_path', Text)
1979 1975 version = Column('version', Integer)
General Comments 0
You need to be logged in to leave comments. Login now