##// END OF EJS Templates
Added form for controlling mercurial extensions...
marcink -
r2708:9bce679a beta
parent child Browse files
Show More
@@ -1,479 +1,482 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
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 49 from rhodecode.model.scm import ScmModel
50 50 from rhodecode.model.user import UserModel
51 51 from rhodecode.model.db import User
52 52 from rhodecode.model.notification import EmailNotificationModel
53 53 from rhodecode.model.meta import Session
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class SettingsController(BaseController):
59 59 """REST Controller styled on the Atom Publishing Protocol"""
60 60 # To properly map this controller, ensure your config/routing.py
61 61 # file has a resource setup:
62 62 # map.resource('setting', 'settings', controller='admin/settings',
63 63 # path_prefix='/admin', name_prefix='admin_')
64 64
65 65 @LoginRequired()
66 66 def __before__(self):
67 67 c.admin_user = session.get('admin_user')
68 68 c.admin_username = session.get('admin_username')
69 69 c.modules = sorted([(p.project_name, p.version)
70 70 for p in pkg_resources.working_set],
71 71 key=lambda k: k[0].lower())
72 72 c.py_version = platform.python_version()
73 73 c.platform = platform.platform()
74 74 super(SettingsController, self).__before__()
75 75
76 76 @HasPermissionAllDecorator('hg.admin')
77 77 def index(self, format='html'):
78 78 """GET /admin/settings: All items in the collection"""
79 79 # url('admin_settings')
80 80
81 81 defaults = RhodeCodeSetting.get_app_settings()
82 defaults.update(self.get_hg_ui_settings())
82 defaults.update(self._get_hg_ui_settings())
83 83
84 84 return htmlfill.render(
85 85 render('admin/settings/settings.html'),
86 86 defaults=defaults,
87 87 encoding="UTF-8",
88 88 force_defaults=False
89 89 )
90 90
91 91 @HasPermissionAllDecorator('hg.admin')
92 92 def create(self):
93 93 """POST /admin/settings: Create a new item"""
94 94 # url('admin_settings')
95 95
96 96 @HasPermissionAllDecorator('hg.admin')
97 97 def new(self, format='html'):
98 98 """GET /admin/settings/new: Form to create a new item"""
99 99 # url('admin_new_setting')
100 100
101 101 @HasPermissionAllDecorator('hg.admin')
102 102 def update(self, setting_id):
103 103 """PUT /admin/settings/setting_id: Update an existing item"""
104 104 # Forms posted to this method should contain a hidden field:
105 105 # <input type="hidden" name="_method" value="PUT" />
106 106 # Or using helpers:
107 107 # h.form(url('admin_setting', setting_id=ID),
108 108 # method='put')
109 109 # url('admin_setting', setting_id=ID)
110 110
111 111 if setting_id == 'mapping':
112 112 rm_obsolete = request.POST.get('destroy', False)
113 113 log.debug('Rescanning directories with destroy=%s' % rm_obsolete)
114 114 initial = ScmModel().repo_scan()
115 115 log.debug('invalidating all repositories')
116 116 for repo_name in initial.keys():
117 117 invalidate_cache('get_repo_cached_%s' % repo_name)
118 118
119 119 added, removed = repo2db_mapper(initial, rm_obsolete)
120 120
121 121 h.flash(_('Repositories successfully'
122 122 ' rescanned added: %s,removed: %s') % (added, removed),
123 123 category='success')
124 124
125 125 if setting_id == 'whoosh':
126 repo_location = self.get_hg_ui_settings()['paths_root_path']
126 repo_location = self._get_hg_ui_settings()['paths_root_path']
127 127 full_index = request.POST.get('full_index', False)
128 128 run_task(tasks.whoosh_index, repo_location, full_index)
129 129 h.flash(_('Whoosh reindex task scheduled'), category='success')
130 130
131 131 if setting_id == 'global':
132 132
133 133 application_form = ApplicationSettingsForm()()
134 134 try:
135 135 form_result = application_form.to_python(dict(request.POST))
136 136 except formencode.Invalid, errors:
137 137 return htmlfill.render(
138 138 render('admin/settings/settings.html'),
139 139 defaults=errors.value,
140 140 errors=errors.error_dict or {},
141 141 prefix_error=False,
142 142 encoding="UTF-8"
143 143 )
144 144
145 145 try:
146 146 sett1 = RhodeCodeSetting.get_by_name_or_create('title')
147 147 sett1.app_settings_value = form_result['rhodecode_title']
148 148 Session().add(sett1)
149 149
150 150 sett2 = RhodeCodeSetting.get_by_name_or_create('realm')
151 151 sett2.app_settings_value = form_result['rhodecode_realm']
152 152 Session().add(sett2)
153 153
154 154 sett3 = RhodeCodeSetting.get_by_name_or_create('ga_code')
155 155 sett3.app_settings_value = form_result['rhodecode_ga_code']
156 156 Session().add(sett3)
157 157
158 158 Session().commit()
159 159 set_rhodecode_config(config)
160 160 h.flash(_('Updated application settings'), category='success')
161 161
162 162 except Exception:
163 163 log.error(traceback.format_exc())
164 164 h.flash(_('error occurred during updating '
165 165 'application settings'),
166 166 category='error')
167 167
168 168 if setting_id == 'visual':
169 169
170 170 application_form = ApplicationVisualisationForm()()
171 171 try:
172 172 form_result = application_form.to_python(dict(request.POST))
173 173 except formencode.Invalid, errors:
174 174 return htmlfill.render(
175 175 render('admin/settings/settings.html'),
176 176 defaults=errors.value,
177 177 errors=errors.error_dict or {},
178 178 prefix_error=False,
179 179 encoding="UTF-8"
180 180 )
181 181
182 182 try:
183 183 sett1 = RhodeCodeSetting.get_by_name_or_create('show_public_icon')
184 184 sett1.app_settings_value = \
185 185 form_result['rhodecode_show_public_icon']
186 186
187 187 sett2 = RhodeCodeSetting.get_by_name_or_create('show_private_icon')
188 188 sett2.app_settings_value = \
189 189 form_result['rhodecode_show_private_icon']
190 190
191 191 sett3 = RhodeCodeSetting.get_by_name_or_create('stylify_metatags')
192 192 sett3.app_settings_value = \
193 193 form_result['rhodecode_stylify_metatags']
194 194
195 195 Session().add(sett1)
196 196 Session().add(sett2)
197 197 Session().add(sett3)
198 198 Session().commit()
199 199 set_rhodecode_config(config)
200 200 h.flash(_('Updated visualisation settings'),
201 201 category='success')
202 202
203 203 except Exception:
204 204 log.error(traceback.format_exc())
205 205 h.flash(_('error occurred during updating '
206 206 'visualisation settings'),
207 207 category='error')
208 208
209 209 if setting_id == 'vcs':
210 210 application_form = ApplicationUiSettingsForm()()
211 211 try:
212 212 form_result = application_form.to_python(dict(request.POST))
213 213 except formencode.Invalid, errors:
214 214 return htmlfill.render(
215 215 render('admin/settings/settings.html'),
216 216 defaults=errors.value,
217 217 errors=errors.error_dict or {},
218 218 prefix_error=False,
219 219 encoding="UTF-8"
220 220 )
221 221
222 222 try:
223 # fix namespaces for hooks
223 # fix namespaces for hooks and extensions
224 224 _f = lambda s: s.replace('.', '_')
225 225
226 sett1 = RhodeCodeUi.query()\
227 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
228 sett1.ui_value = form_result['web_push_ssl']
226 sett = RhodeCodeUi.get_by_key('push_ssl')
227 sett.ui_value = form_result['web_push_ssl']
228 Session().add(sett)
229 229
230 sett2 = RhodeCodeUi.query()\
231 .filter(RhodeCodeUi.ui_key == '/').one()
232 sett2.ui_value = form_result['paths_root_path']
230 sett = RhodeCodeUi.get_by_key('/')
231 sett.ui_value = form_result['paths_root_path']
232 Session().add(sett)
233 233
234 234 #HOOKS
235 sett3 = RhodeCodeUi.query()\
236 .filter(RhodeCodeUi.ui_key == RhodeCodeUi.HOOK_UPDATE)\
237 .one()
238 sett3.ui_active = bool(form_result[_f('hooks_%s' %
239 RhodeCodeUi.HOOK_UPDATE)])
235 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_UPDATE)
236 sett.ui_active = form_result[_f('hooks_%s' %
237 RhodeCodeUi.HOOK_UPDATE)]
238 Session().add(sett)
240 239
241 sett4 = RhodeCodeUi.query()\
242 .filter(RhodeCodeUi.ui_key == RhodeCodeUi.HOOK_REPO_SIZE)\
243 .one()
244 sett4.ui_active = bool(form_result[_f('hooks_%s' %
245 RhodeCodeUi.HOOK_REPO_SIZE)])
240 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_REPO_SIZE)
241 sett.ui_active = form_result[_f('hooks_%s' %
242 RhodeCodeUi.HOOK_REPO_SIZE)]
243 Session().add(sett)
244
245 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PUSH)
246 sett.ui_active = form_result[_f('hooks_%s' %
247 RhodeCodeUi.HOOK_PUSH)]
248 Session().add(sett)
246 249
247 sett5 = RhodeCodeUi.query()\
248 .filter(RhodeCodeUi.ui_key == RhodeCodeUi.HOOK_PUSH)\
249 .one()
250 sett5.ui_active = bool(form_result[_f('hooks_%s' %
251 RhodeCodeUi.HOOK_PUSH)])
250 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PULL)
251 sett.ui_active = form_result[_f('hooks_%s' %
252 RhodeCodeUi.HOOK_PULL)]
253
254 Session().add(sett)
252 255
253 sett6 = RhodeCodeUi.query()\
254 .filter(RhodeCodeUi.ui_key == RhodeCodeUi.HOOK_PULL)\
255 .one()
256 sett6.ui_active = bool(form_result[_f('hooks_%s' %
257 RhodeCodeUi.HOOK_PULL)])
256 ## EXTENSIONS
257 sett = RhodeCodeUi.get_by_key('largefiles')
258 sett.ui_active = form_result[_f('extensions_largefiles')]
259 Session().add(sett)
258 260
259 Session().add(sett1)
260 Session().add(sett2)
261 Session().add(sett3)
262 Session().add(sett4)
263 Session().add(sett5)
264 Session().add(sett6)
261 sett = RhodeCodeUi.get_by_key('hgsubversion')
262 sett.ui_active = form_result[_f('extensions_hgsubversion')]
263 Session().add(sett)
264
265 # sett = RhodeCodeUi.get_by_key('hggit')
266 # sett.ui_active = form_result[_f('extensions_hggit')]
267 # Session().add(sett)
268
265 269 Session().commit()
266 270
267 h.flash(_('Updated mercurial settings'), category='success')
271 h.flash(_('Updated VCS settings'), category='success')
268 272
269 273 except Exception:
270 274 log.error(traceback.format_exc())
271 275 h.flash(_('error occurred during updating '
272 276 'application settings'), category='error')
273 277
274 278 if setting_id == 'hooks':
275 279 ui_key = request.POST.get('new_hook_ui_key')
276 280 ui_value = request.POST.get('new_hook_ui_value')
277 281 try:
278 282
279 283 if ui_value and ui_key:
280 284 RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
281 285 h.flash(_('Added new hook'),
282 286 category='success')
283 287
284 288 # check for edits
285 289 update = False
286 290 _d = request.POST.dict_of_lists()
287 291 for k, v in zip(_d.get('hook_ui_key', []),
288 292 _d.get('hook_ui_value_new', [])):
289 293 RhodeCodeUi.create_or_update_hook(k, v)
290 294 update = True
291 295
292 296 if update:
293 297 h.flash(_('Updated hooks'), category='success')
294 298 Session().commit()
295 299 except Exception:
296 300 log.error(traceback.format_exc())
297 301 h.flash(_('error occurred during hook creation'),
298 302 category='error')
299 303
300 304 return redirect(url('admin_edit_setting', setting_id='hooks'))
301 305
302 306 if setting_id == 'email':
303 307 test_email = request.POST.get('test_email')
304 308 test_email_subj = 'RhodeCode TestEmail'
305 309 test_email_body = 'RhodeCode Email test'
306 310
307 311 test_email_html_body = EmailNotificationModel()\
308 312 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
309 313 body=test_email_body)
310 314
311 315 recipients = [test_email] if [test_email] else None
312 316
313 317 run_task(tasks.send_email, recipients, test_email_subj,
314 318 test_email_body, test_email_html_body)
315 319
316 320 h.flash(_('Email task created'), category='success')
317 321 return redirect(url('admin_settings'))
318 322
319 323 @HasPermissionAllDecorator('hg.admin')
320 324 def delete(self, setting_id):
321 325 """DELETE /admin/settings/setting_id: Delete an existing item"""
322 326 # Forms posted to this method should contain a hidden field:
323 327 # <input type="hidden" name="_method" value="DELETE" />
324 328 # Or using helpers:
325 329 # h.form(url('admin_setting', setting_id=ID),
326 330 # method='delete')
327 331 # url('admin_setting', setting_id=ID)
328 332 if setting_id == 'hooks':
329 333 hook_id = request.POST.get('hook_id')
330 334 RhodeCodeUi.delete(hook_id)
331 335 Session().commit()
332 336
333 337 @HasPermissionAllDecorator('hg.admin')
334 338 def show(self, setting_id, format='html'):
335 339 """
336 340 GET /admin/settings/setting_id: Show a specific item"""
337 341 # url('admin_setting', setting_id=ID)
338 342
339 343 @HasPermissionAllDecorator('hg.admin')
340 344 def edit(self, setting_id, format='html'):
341 345 """
342 346 GET /admin/settings/setting_id/edit: Form to
343 347 edit an existing item"""
344 348 # url('admin_edit_setting', setting_id=ID)
345 349
346 350 c.hooks = RhodeCodeUi.get_builtin_hooks()
347 351 c.custom_hooks = RhodeCodeUi.get_custom_hooks()
348 352
349 353 return htmlfill.render(
350 354 render('admin/settings/hooks.html'),
351 355 defaults={},
352 356 encoding="UTF-8",
353 357 force_defaults=False
354 358 )
355 359
356 360 @NotAnonymous()
357 361 def my_account(self):
358 362 """
359 363 GET /_admin/my_account Displays info about my account
360 364 """
361 365 # url('admin_settings_my_account')
362 366
363 367 c.user = User.get(self.rhodecode_user.user_id)
364 368 all_repos = Session().query(Repository)\
365 369 .filter(Repository.user_id == c.user.user_id)\
366 370 .order_by(func.lower(Repository.repo_name)).all()
367 371
368 372 c.user_repos = ScmModel().get_repos(all_repos)
369 373
370 374 if c.user.username == 'default':
371 375 h.flash(_("You can't edit this user since it's"
372 376 " crucial for entire application"), category='warning')
373 377 return redirect(url('users'))
374 378
375 379 defaults = c.user.get_dict()
376 380
377 381 c.form = htmlfill.render(
378 382 render('admin/users/user_edit_my_account_form.html'),
379 383 defaults=defaults,
380 384 encoding="UTF-8",
381 385 force_defaults=False
382 386 )
383 387 return render('admin/users/user_edit_my_account.html')
384 388
385 389 @NotAnonymous()
386 390 def my_account_update(self):
387 391 """PUT /_admin/my_account_update: Update an existing item"""
388 392 # Forms posted to this method should contain a hidden field:
389 393 # <input type="hidden" name="_method" value="PUT" />
390 394 # Or using helpers:
391 395 # h.form(url('admin_settings_my_account_update'),
392 396 # method='put')
393 397 # url('admin_settings_my_account_update', id=ID)
394 398 uid = self.rhodecode_user.user_id
395 399 email = self.rhodecode_user.email
396 400 _form = UserForm(edit=True,
397 401 old_data={'user_id': uid, 'email': email})()
398 402 form_result = {}
399 403 try:
400 404 form_result = _form.to_python(dict(request.POST))
401 405 UserModel().update_my_account(uid, form_result)
402 406 h.flash(_('Your account was updated successfully'),
403 407 category='success')
404 408 Session().commit()
405 409 except formencode.Invalid, errors:
406 410 c.user = User.get(self.rhodecode_user.user_id)
407 411
408 412 c.form = htmlfill.render(
409 413 render('admin/users/user_edit_my_account_form.html'),
410 414 defaults=errors.value,
411 415 errors=errors.error_dict or {},
412 416 prefix_error=False,
413 417 encoding="UTF-8")
414 418 return render('admin/users/user_edit_my_account.html')
415 419 except Exception:
416 420 log.error(traceback.format_exc())
417 421 h.flash(_('error occurred during update of user %s') \
418 422 % form_result.get('username'), category='error')
419 423
420 424 return redirect(url('my_account'))
421 425
422 426 @NotAnonymous()
423 427 def my_account_my_repos(self):
424 428 all_repos = Session().query(Repository)\
425 429 .filter(Repository.user_id == self.rhodecode_user.user_id)\
426 430 .order_by(func.lower(Repository.repo_name))\
427 431 .all()
428 432 c.user_repos = ScmModel().get_repos(all_repos)
429 433 return render('admin/users/user_edit_my_account_repos.html')
430 434
431 435 @NotAnonymous()
432 436 def my_account_my_pullrequests(self):
433 437 c.my_pull_requests = PullRequest.query()\
434 438 .filter(PullRequest.user_id==
435 439 self.rhodecode_user.user_id)\
436 440 .all()
437 441 c.participate_in_pull_requests = \
438 442 [x.pull_request for x in PullRequestReviewers.query()\
439 443 .filter(PullRequestReviewers.user_id==
440 444 self.rhodecode_user.user_id)\
441 445 .all()]
442 446 return render('admin/users/user_edit_my_account_pullrequests.html')
443 447
444 448 @NotAnonymous()
445 449 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
446 450 def create_repository(self):
447 451 """GET /_admin/create_repository: Form to create a new item"""
448 452
449 453 c.repo_groups = RepoGroup.groups_choices()
450 454 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
451 455 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
452 456
453 457 new_repo = request.GET.get('repo', '')
454 458 c.new_repo = repo_name_slug(new_repo)
455 459
456 460 return render('admin/repos/repo_add_create_repository.html')
457 461
458 @NotAnonymous()
459 def get_hg_ui_settings(self):
462 def _get_hg_ui_settings(self):
460 463 ret = RhodeCodeUi.query().all()
461 464
462 465 if not ret:
463 466 raise Exception('Could not get application ui settings !')
464 467 settings = {}
465 468 for each in ret:
466 469 k = each.ui_key
467 470 v = each.ui_value
468 471 if k == '/':
469 472 k = 'root_path'
470 473
471 474 if k.find('.') != -1:
472 475 k = k.replace('.', '_')
473 476
474 if each.ui_section == 'hooks':
477 if each.ui_section in ['hooks', 'extensions']:
475 478 v = each.ui_active
476 479
477 480 settings[each.ui_section + '_' + k] = v
478 481
479 482 return settings
@@ -1,257 +1,256 b''
1 1 """The base Controller API
2 2
3 3 Provides the BaseController class for subclassing.
4 4 """
5 5 import logging
6 6 import time
7 7 import traceback
8 8
9 9 from paste.auth.basic import AuthBasicAuthenticator
10 10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
11 11 from paste.httpheaders import WWW_AUTHENTICATE
12 12
13 13 from pylons import config, tmpl_context as c, request, session, url
14 14 from pylons.controllers import WSGIController
15 15 from pylons.controllers.util import redirect
16 16 from pylons.templating import render_mako as render
17 17
18 18 from rhodecode import __version__, BACKENDS
19 19
20 20 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
21 21 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
22 22 HasPermissionAnyMiddleware, CookieStoreWrapper
23 23 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
24 24 from rhodecode.model import meta
25 25
26 26 from rhodecode.model.db import Repository, RhodeCodeUi
27 27 from rhodecode.model.notification import NotificationModel
28 28 from rhodecode.model.scm import ScmModel
29 29
30 30 log = logging.getLogger(__name__)
31 31
32 32
33 33 def _get_ip_addr(environ):
34 34 proxy_key = 'HTTP_X_REAL_IP'
35 35 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
36 36 def_key = 'REMOTE_ADDR'
37 37
38 38 ip = environ.get(proxy_key2)
39 39 if ip:
40 40 return ip
41 41
42 42 ip = environ.get(proxy_key)
43 43
44 44 if ip:
45 45 return ip
46 46
47 47 ip = environ.get(def_key, '0.0.0.0')
48 48 return ip
49 49
50 50
51 51 def _get_access_path(environ):
52 52 path = environ.get('PATH_INFO')
53 53 org_req = environ.get('pylons.original_request')
54 54 if org_req:
55 55 path = org_req.environ.get('PATH_INFO')
56 56 return path
57 57
58 58
59 59 class BasicAuth(AuthBasicAuthenticator):
60 60
61 61 def __init__(self, realm, authfunc, auth_http_code=None):
62 62 self.realm = realm
63 63 self.authfunc = authfunc
64 64 self._rc_auth_http_code = auth_http_code
65 65
66 66 def build_authentication(self):
67 67 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
68 68 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
69 69 # return 403 if alternative http return code is specified in
70 70 # RhodeCode config
71 71 return HTTPForbidden(headers=head)
72 72 return HTTPUnauthorized(headers=head)
73 73
74 74
75 75 class BaseVCSController(object):
76 76
77 77 def __init__(self, application, config):
78 78 self.application = application
79 79 self.config = config
80 80 # base path of repo locations
81 81 self.basepath = self.config['base_path']
82 82 #authenticate this mercurial request using authfunc
83 83 self.authenticate = BasicAuth('', authfunc,
84 84 config.get('auth_ret_code'))
85 85 self.ipaddr = '0.0.0.0'
86 86
87 87 def _handle_request(self, environ, start_response):
88 88 raise NotImplementedError()
89 89
90 90 def _get_by_id(self, repo_name):
91 91 """
92 92 Get's a special pattern _<ID> from clone url and tries to replace it
93 93 with a repository_name for support of _<ID> non changable urls
94 94
95 95 :param repo_name:
96 96 """
97 97 try:
98 98 data = repo_name.split('/')
99 99 if len(data) >= 2:
100 100 by_id = data[1].split('_')
101 101 if len(by_id) == 2 and by_id[1].isdigit():
102 102 _repo_name = Repository.get(by_id[1]).repo_name
103 103 data[1] = _repo_name
104 104 except:
105 105 log.debug('Failed to extract repo_name from id %s' % (
106 106 traceback.format_exc()
107 107 )
108 108 )
109 109
110 110 return '/'.join(data)
111 111
112 112 def _invalidate_cache(self, repo_name):
113 113 """
114 114 Set's cache for this repository for invalidation on next access
115 115
116 116 :param repo_name: full repo name, also a cache key
117 117 """
118 118 invalidate_cache('get_repo_cached_%s' % repo_name)
119 119
120 120 def _check_permission(self, action, user, repo_name):
121 121 """
122 122 Checks permissions using action (push/pull) user and repository
123 123 name
124 124
125 125 :param action: push or pull action
126 126 :param user: user instance
127 127 :param repo_name: repository name
128 128 """
129 129 if action == 'push':
130 130 if not HasPermissionAnyMiddleware('repository.write',
131 131 'repository.admin')(user,
132 132 repo_name):
133 133 return False
134 134
135 135 else:
136 136 #any other action need at least read permission
137 137 if not HasPermissionAnyMiddleware('repository.read',
138 138 'repository.write',
139 139 'repository.admin')(user,
140 140 repo_name):
141 141 return False
142 142
143 143 return True
144 144
145 145 def _get_ip_addr(self, environ):
146 146 return _get_ip_addr(environ)
147 147
148 148 def _check_ssl(self, environ, start_response):
149 149 """
150 150 Checks the SSL check flag and returns False if SSL is not present
151 151 and required True otherwise
152 152 """
153 153 org_proto = environ['wsgi._org_proto']
154 154 #check if we have SSL required ! if not it's a bad request !
155 require_ssl = str2bool(RhodeCodeUi.get_by_key('push_ssl')\
156 .scalar().ui_value)
155 require_ssl = str2bool(RhodeCodeUi.get_by_key('push_ssl').ui_value)
157 156 if require_ssl and org_proto == 'http':
158 157 log.debug('proto is %s and SSL is required BAD REQUEST !'
159 158 % org_proto)
160 159 return False
161 160 return True
162 161
163 162 def __call__(self, environ, start_response):
164 163 start = time.time()
165 164 try:
166 165 return self._handle_request(environ, start_response)
167 166 finally:
168 167 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
169 168 log.debug('Request time: %.3fs' % (time.time() - start))
170 169 meta.Session.remove()
171 170
172 171
173 172 class BaseController(WSGIController):
174 173
175 174 def __before__(self):
176 175 c.rhodecode_version = __version__
177 176 c.rhodecode_instanceid = config.get('instance_id')
178 177 c.rhodecode_name = config.get('rhodecode_title')
179 178 c.use_gravatar = str2bool(config.get('use_gravatar'))
180 179 c.ga_code = config.get('rhodecode_ga_code')
181 180 # Visual options
182 181 c.visual = AttributeDict({})
183 182 c.visual.show_public_icon = str2bool(config.get('rhodecode_show_public_icon'))
184 183 c.visual.show_private_icon = str2bool(config.get('rhodecode_show_private_icon'))
185 184 c.visual.stylify_metatags = str2bool(config.get('rhodecode_stylify_metatags'))
186 185
187 186 c.repo_name = get_repo_slug(request)
188 187 c.backends = BACKENDS.keys()
189 188 c.unread_notifications = NotificationModel()\
190 189 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
191 190 self.cut_off_limit = int(config.get('cut_off_limit'))
192 191
193 192 self.sa = meta.Session
194 193 self.scm_model = ScmModel(self.sa)
195 194 self.ip_addr = ''
196 195
197 196 def __call__(self, environ, start_response):
198 197 """Invoke the Controller"""
199 198 # WSGIController.__call__ dispatches to the Controller method
200 199 # the request is routed to. This routing information is
201 200 # available in environ['pylons.routes_dict']
202 201 start = time.time()
203 202 try:
204 203 self.ip_addr = _get_ip_addr(environ)
205 204 # make sure that we update permissions each time we call controller
206 205 api_key = request.GET.get('api_key')
207 206 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
208 207 user_id = cookie_store.get('user_id', None)
209 208 username = get_container_username(environ, config)
210 209 auth_user = AuthUser(user_id, api_key, username)
211 210 request.user = auth_user
212 211 self.rhodecode_user = c.rhodecode_user = auth_user
213 212 if not self.rhodecode_user.is_authenticated and \
214 213 self.rhodecode_user.user_id is not None:
215 214 self.rhodecode_user.set_authenticated(
216 215 cookie_store.get('is_authenticated')
217 216 )
218 217 log.info('IP: %s User: %s accessed %s' % (
219 218 self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
220 219 )
221 220 return WSGIController.__call__(self, environ, start_response)
222 221 finally:
223 222 log.info('IP: %s Request to %s time: %.3fs' % (
224 223 _get_ip_addr(environ),
225 224 safe_unicode(_get_access_path(environ)), time.time() - start)
226 225 )
227 226 meta.Session.remove()
228 227
229 228
230 229 class BaseRepoController(BaseController):
231 230 """
232 231 Base class for controllers responsible for loading all needed data for
233 232 repository loaded items are
234 233
235 234 c.rhodecode_repo: instance of scm repository
236 235 c.rhodecode_db_repo: instance of db
237 236 c.repository_followers: number of followers
238 237 c.repository_forks: number of forks
239 238 """
240 239
241 240 def __before__(self):
242 241 super(BaseRepoController, self).__before__()
243 242 if c.repo_name:
244 243
245 244 dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
246 245 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
247 246
248 247 if c.rhodecode_repo is None:
249 248 log.error('%s this repository is present in database but it '
250 249 'cannot be created as an scm instance', c.repo_name)
251 250
252 251 redirect(url('home'))
253 252
254 253 # some globals counter for menu
255 254 c.repository_followers = self.scm_model.get_followers(dbr)
256 255 c.repository_forks = self.scm_model.get_forks(dbr)
257 256 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
@@ -1,531 +1,539 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.db_manage
4 4 ~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Database creation, and setup module for RhodeCode. Used for creation
7 7 of database as well as for migration operations
8 8
9 9 :created_on: Apr 10, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import sys
29 29 import uuid
30 30 import logging
31 31 from os.path import dirname as dn, join as jn
32 32
33 33 from rhodecode import __dbversion__
34 34 from rhodecode.model import meta
35 35
36 36 from rhodecode.model.user import UserModel
37 37 from rhodecode.lib.utils import ask_ok
38 38 from rhodecode.model import init_model
39 39 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
40 40 RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup,\
41 41 UserRepoGroupToPerm
42 42
43 43 from sqlalchemy.engine import create_engine
44 44 from rhodecode.model.repos_group import ReposGroupModel
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class DbManage(object):
50 50 def __init__(self, log_sql, dbconf, root, tests=False):
51 51 self.dbname = dbconf.split('/')[-1]
52 52 self.tests = tests
53 53 self.root = root
54 54 self.dburi = dbconf
55 55 self.log_sql = log_sql
56 56 self.db_exists = False
57 57 self.init_db()
58 58
59 59 def init_db(self):
60 60 engine = create_engine(self.dburi, echo=self.log_sql)
61 61 init_model(engine)
62 62 self.sa = meta.Session()
63 63
64 64 def create_tables(self, override=False, defaults={}):
65 65 """
66 66 Create a auth database
67 67 """
68 68 quiet = defaults.get('quiet')
69 69 log.info("Any existing database is going to be destroyed")
70 70 if self.tests or quiet:
71 71 destroy = True
72 72 else:
73 73 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
74 74 if not destroy:
75 75 sys.exit()
76 76 if destroy:
77 77 meta.Base.metadata.drop_all()
78 78
79 79 checkfirst = not override
80 80 meta.Base.metadata.create_all(checkfirst=checkfirst)
81 81 log.info('Created tables for %s' % self.dbname)
82 82
83 83 def set_db_version(self):
84 84 ver = DbMigrateVersion()
85 85 ver.version = __dbversion__
86 86 ver.repository_id = 'rhodecode_db_migrations'
87 87 ver.repository_path = 'versions'
88 88 self.sa.add(ver)
89 89 log.info('db version set to: %s' % __dbversion__)
90 90
91 91 def upgrade(self):
92 92 """
93 93 Upgrades given database schema to given revision following
94 94 all needed steps, to perform the upgrade
95 95
96 96 """
97 97
98 98 from rhodecode.lib.dbmigrate.migrate.versioning import api
99 99 from rhodecode.lib.dbmigrate.migrate.exceptions import \
100 100 DatabaseNotControlledError
101 101
102 102 if 'sqlite' in self.dburi:
103 103 print (
104 104 '********************** WARNING **********************\n'
105 105 'Make sure your version of sqlite is at least 3.7.X. \n'
106 106 'Earlier versions are known to fail on some migrations\n'
107 107 '*****************************************************\n'
108 108 )
109 109 upgrade = ask_ok('You are about to perform database upgrade, make '
110 110 'sure You backed up your database before. '
111 111 'Continue ? [y/n]')
112 112 if not upgrade:
113 113 sys.exit('Nothing done')
114 114
115 115 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
116 116 'rhodecode/lib/dbmigrate')
117 117 db_uri = self.dburi
118 118
119 119 try:
120 120 curr_version = api.db_version(db_uri, repository_path)
121 121 msg = ('Found current database under version'
122 122 ' control with version %s' % curr_version)
123 123
124 124 except (RuntimeError, DatabaseNotControlledError):
125 125 curr_version = 1
126 126 msg = ('Current database is not under version control. Setting'
127 127 ' as version %s' % curr_version)
128 128 api.version_control(db_uri, repository_path, curr_version)
129 129
130 130 print (msg)
131 131
132 132 if curr_version == __dbversion__:
133 133 sys.exit('This database is already at the newest version')
134 134
135 135 #======================================================================
136 136 # UPGRADE STEPS
137 137 #======================================================================
138 138 class UpgradeSteps(object):
139 139 """
140 140 Those steps follow schema versions so for example schema
141 141 for example schema with seq 002 == step_2 and so on.
142 142 """
143 143
144 144 def __init__(self, klass):
145 145 self.klass = klass
146 146
147 147 def step_0(self):
148 148 # step 0 is the schema upgrade, and than follow proper upgrades
149 149 print ('attempting to do database upgrade to version %s' \
150 150 % __dbversion__)
151 151 api.upgrade(db_uri, repository_path, __dbversion__)
152 152 print ('Schema upgrade completed')
153 153
154 154 def step_1(self):
155 155 pass
156 156
157 157 def step_2(self):
158 158 print ('Patching repo paths for newer version of RhodeCode')
159 159 self.klass.fix_repo_paths()
160 160
161 161 print ('Patching default user of RhodeCode')
162 162 self.klass.fix_default_user()
163 163
164 164 log.info('Changing ui settings')
165 165 self.klass.create_ui_settings()
166 166
167 167 def step_3(self):
168 168 print ('Adding additional settings into RhodeCode db')
169 169 self.klass.fix_settings()
170 170 print ('Adding ldap defaults')
171 171 self.klass.create_ldap_options(skip_existing=True)
172 172
173 173 def step_4(self):
174 174 print ('create permissions and fix groups')
175 175 self.klass.create_permissions()
176 176 self.klass.fixup_groups()
177 177
178 178 def step_5(self):
179 179 pass
180 180
181 181 def step_6(self):
182 182 pass
183 183 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
184 184
185 185 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
186 186 for step in upgrade_steps:
187 187 print ('performing upgrade step %s' % step)
188 188 getattr(UpgradeSteps(self), 'step_%s' % step)()
189 189 self.sa.commit()
190 190
191 191 def fix_repo_paths(self):
192 192 """
193 193 Fixes a old rhodecode version path into new one without a '*'
194 194 """
195 195
196 196 paths = self.sa.query(RhodeCodeUi)\
197 197 .filter(RhodeCodeUi.ui_key == '/')\
198 198 .scalar()
199 199
200 200 paths.ui_value = paths.ui_value.replace('*', '')
201 201
202 202 try:
203 203 self.sa.add(paths)
204 204 self.sa.commit()
205 205 except:
206 206 self.sa.rollback()
207 207 raise
208 208
209 209 def fix_default_user(self):
210 210 """
211 211 Fixes a old default user with some 'nicer' default values,
212 212 used mostly for anonymous access
213 213 """
214 214 def_user = self.sa.query(User)\
215 215 .filter(User.username == 'default')\
216 216 .one()
217 217
218 218 def_user.name = 'Anonymous'
219 219 def_user.lastname = 'User'
220 220 def_user.email = 'anonymous@rhodecode.org'
221 221
222 222 try:
223 223 self.sa.add(def_user)
224 224 self.sa.commit()
225 225 except:
226 226 self.sa.rollback()
227 227 raise
228 228
229 229 def fix_settings(self):
230 230 """
231 231 Fixes rhodecode settings adds ga_code key for google analytics
232 232 """
233 233
234 234 hgsettings3 = RhodeCodeSetting('ga_code', '')
235 235
236 236 try:
237 237 self.sa.add(hgsettings3)
238 238 self.sa.commit()
239 239 except:
240 240 self.sa.rollback()
241 241 raise
242 242
243 243 def admin_prompt(self, second=False, defaults={}):
244 244 if not self.tests:
245 245 import getpass
246 246
247 247 # defaults
248 248 username = defaults.get('username')
249 249 password = defaults.get('password')
250 250 email = defaults.get('email')
251 251
252 252 def get_password():
253 253 password = getpass.getpass('Specify admin password '
254 254 '(min 6 chars):')
255 255 confirm = getpass.getpass('Confirm password:')
256 256
257 257 if password != confirm:
258 258 log.error('passwords mismatch')
259 259 return False
260 260 if len(password) < 6:
261 261 log.error('password is to short use at least 6 characters')
262 262 return False
263 263
264 264 return password
265 265 if username is None:
266 266 username = raw_input('Specify admin username:')
267 267 if password is None:
268 268 password = get_password()
269 269 if not password:
270 270 #second try
271 271 password = get_password()
272 272 if not password:
273 273 sys.exit()
274 274 if email is None:
275 275 email = raw_input('Specify admin email:')
276 276 self.create_user(username, password, email, True)
277 277 else:
278 278 log.info('creating admin and regular test users')
279 279 from rhodecode.tests import TEST_USER_ADMIN_LOGIN,\
280 280 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL,\
281 281 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,\
282 282 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
283 283 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
284 284
285 285 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
286 286 TEST_USER_ADMIN_EMAIL, True)
287 287
288 288 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
289 289 TEST_USER_REGULAR_EMAIL, False)
290 290
291 291 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
292 292 TEST_USER_REGULAR2_EMAIL, False)
293 293
294 294 def create_ui_settings(self):
295 295 """
296 296 Creates ui settings, fills out hooks
297 297 and disables dotencode
298 298 """
299 299
300 300 #HOOKS
301 301 hooks1_key = RhodeCodeUi.HOOK_UPDATE
302 302 hooks1_ = self.sa.query(RhodeCodeUi)\
303 303 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
304 304
305 305 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
306 306 hooks1.ui_section = 'hooks'
307 307 hooks1.ui_key = hooks1_key
308 308 hooks1.ui_value = 'hg update >&2'
309 309 hooks1.ui_active = False
310 310
311 311 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
312 312 hooks2_ = self.sa.query(RhodeCodeUi)\
313 313 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
314 314
315 315 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
316 316 hooks2.ui_section = 'hooks'
317 317 hooks2.ui_key = hooks2_key
318 318 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
319 319
320 320 hooks3 = RhodeCodeUi()
321 321 hooks3.ui_section = 'hooks'
322 322 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
323 323 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
324 324
325 325 hooks4 = RhodeCodeUi()
326 326 hooks4.ui_section = 'hooks'
327 327 hooks4.ui_key = RhodeCodeUi.HOOK_PULL
328 328 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
329 329
330 330 # For mercurial 1.7 set backward comapatibility with format
331 331 dotencode_disable = RhodeCodeUi()
332 332 dotencode_disable.ui_section = 'format'
333 333 dotencode_disable.ui_key = 'dotencode'
334 334 dotencode_disable.ui_value = 'false'
335 335
336 336 # enable largefiles
337 337 largefiles = RhodeCodeUi()
338 338 largefiles.ui_section = 'extensions'
339 339 largefiles.ui_key = 'largefiles'
340 340 largefiles.ui_value = ''
341 341
342 342 # enable hgsubversion disabled by default
343 343 hgsubversion = RhodeCodeUi()
344 344 hgsubversion.ui_section = 'extensions'
345 345 hgsubversion.ui_key = 'hgsubversion'
346 346 hgsubversion.ui_value = ''
347 347 hgsubversion.ui_active = False
348 348
349 # enable hggit disabled by default
350 hggit = RhodeCodeUi()
351 hggit.ui_section = 'extensions'
352 hggit.ui_key = 'hggit'
353 hggit.ui_value = ''
354 hggit.ui_active = False
355
349 356 self.sa.add(hooks1)
350 357 self.sa.add(hooks2)
351 358 self.sa.add(hooks3)
352 359 self.sa.add(hooks4)
353 360 self.sa.add(largefiles)
354 361 self.sa.add(hgsubversion)
362 self.sa.add(hggit)
355 363
356 364 def create_ldap_options(self, skip_existing=False):
357 365 """Creates ldap settings"""
358 366
359 367 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
360 368 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
361 369 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
362 370 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
363 371 ('ldap_filter', ''), ('ldap_search_scope', ''),
364 372 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
365 373 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
366 374
367 375 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
368 376 log.debug('Skipping option %s' % k)
369 377 continue
370 378 setting = RhodeCodeSetting(k, v)
371 379 self.sa.add(setting)
372 380
373 381 def fixup_groups(self):
374 382 def_usr = User.get_by_username('default')
375 383 for g in RepoGroup.query().all():
376 384 g.group_name = g.get_new_name(g.name)
377 385 self.sa.add(g)
378 386 # get default perm
379 387 default = UserRepoGroupToPerm.query()\
380 388 .filter(UserRepoGroupToPerm.group == g)\
381 389 .filter(UserRepoGroupToPerm.user == def_usr)\
382 390 .scalar()
383 391
384 392 if default is None:
385 393 log.debug('missing default permission for group %s adding' % g)
386 394 ReposGroupModel()._create_default_perms(g)
387 395
388 396 def config_prompt(self, test_repo_path='', retries=3, defaults={}):
389 397 _path = defaults.get('repos_location')
390 398 if retries == 3:
391 399 log.info('Setting up repositories config')
392 400
393 401 if _path is not None:
394 402 path = _path
395 403 elif not self.tests and not test_repo_path:
396 404 path = raw_input(
397 405 'Enter a valid absolute path to store repositories. '
398 406 'All repositories in that path will be added automatically:'
399 407 )
400 408 else:
401 409 path = test_repo_path
402 410 path_ok = True
403 411
404 412 # check proper dir
405 413 if not os.path.isdir(path):
406 414 path_ok = False
407 415 log.error('Given path %s is not a valid directory' % path)
408 416
409 417 elif not os.path.isabs(path):
410 418 path_ok = False
411 419 log.error('Given path %s is not an absolute path' % path)
412 420
413 421 # check write access
414 422 elif not os.access(path, os.W_OK) and path_ok:
415 423 path_ok = False
416 424 log.error('No write permission to given path %s' % path)
417 425
418 426 if retries == 0:
419 427 sys.exit('max retries reached')
420 428 if path_ok is False:
421 429 retries -= 1
422 430 return self.config_prompt(test_repo_path, retries)
423 431
424 432 return path
425 433
426 434 def create_settings(self, path):
427 435
428 436 self.create_ui_settings()
429 437
430 438 #HG UI OPTIONS
431 439 web1 = RhodeCodeUi()
432 440 web1.ui_section = 'web'
433 441 web1.ui_key = 'push_ssl'
434 442 web1.ui_value = 'false'
435 443
436 444 web2 = RhodeCodeUi()
437 445 web2.ui_section = 'web'
438 446 web2.ui_key = 'allow_archive'
439 447 web2.ui_value = 'gz zip bz2'
440 448
441 449 web3 = RhodeCodeUi()
442 450 web3.ui_section = 'web'
443 451 web3.ui_key = 'allow_push'
444 452 web3.ui_value = '*'
445 453
446 454 web4 = RhodeCodeUi()
447 455 web4.ui_section = 'web'
448 456 web4.ui_key = 'baseurl'
449 457 web4.ui_value = '/'
450 458
451 459 paths = RhodeCodeUi()
452 460 paths.ui_section = 'paths'
453 461 paths.ui_key = '/'
454 462 paths.ui_value = path
455 463
456 464 sett1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
457 465 sett2 = RhodeCodeSetting('title', 'RhodeCode')
458 466 sett3 = RhodeCodeSetting('ga_code', '')
459 467
460 468 sett4 = RhodeCodeSetting('show_public_icon', True)
461 469 sett5 = RhodeCodeSetting('show_private_icon', True)
462 470 sett6 = RhodeCodeSetting('stylify_metatags', False)
463 471
464 472 self.sa.add(web1)
465 473 self.sa.add(web2)
466 474 self.sa.add(web3)
467 475 self.sa.add(web4)
468 476 self.sa.add(paths)
469 477 self.sa.add(sett1)
470 478 self.sa.add(sett2)
471 479 self.sa.add(sett3)
472 480 self.sa.add(sett4)
473 481 self.sa.add(sett5)
474 482 self.sa.add(sett6)
475 483
476 484 self.create_ldap_options()
477 485
478 486 log.info('created ui config')
479 487
480 488 def create_user(self, username, password, email='', admin=False):
481 489 log.info('creating user %s' % username)
482 490 UserModel().create_or_update(username, password, email,
483 491 firstname='RhodeCode', lastname='Admin',
484 492 active=True, admin=admin)
485 493
486 494 def create_default_user(self):
487 495 log.info('creating default user')
488 496 # create default user for handling default permissions.
489 497 UserModel().create_or_update(username='default',
490 498 password=str(uuid.uuid1())[:8],
491 499 email='anonymous@rhodecode.org',
492 500 firstname='Anonymous', lastname='User')
493 501
494 502 def create_permissions(self):
495 503 # module.(access|create|change|delete)_[name]
496 504 # module.(none|read|write|admin)
497 505
498 506 for p in Permission.PERMS:
499 507 if not Permission.get_by_key(p[0]):
500 508 new_perm = Permission()
501 509 new_perm.permission_name = p[0]
502 510 new_perm.permission_longname = p[0]
503 511 self.sa.add(new_perm)
504 512
505 513 def populate_default_permissions(self):
506 514 log.info('creating default user permissions')
507 515
508 516 default_user = self.sa.query(User)\
509 517 .filter(User.username == 'default').scalar()
510 518
511 519 reg_perm = UserToPerm()
512 520 reg_perm.user = default_user
513 521 reg_perm.permission = self.sa.query(Permission)\
514 522 .filter(Permission.permission_name == 'hg.register.manual_activate')\
515 523 .scalar()
516 524
517 525 create_repo_perm = UserToPerm()
518 526 create_repo_perm.user = default_user
519 527 create_repo_perm.permission = self.sa.query(Permission)\
520 528 .filter(Permission.permission_name == 'hg.create.repository')\
521 529 .scalar()
522 530
523 531 default_repo_perm = UserToPerm()
524 532 default_repo_perm.user = default_user
525 533 default_repo_perm.permission = self.sa.query(Permission)\
526 534 .filter(Permission.permission_name == 'repository.read')\
527 535 .scalar()
528 536
529 537 self.sa.add(reg_perm)
530 538 self.sa.add(create_repo_perm)
531 539 self.sa.add(default_repo_perm)
@@ -1,1683 +1,1683 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 from collections import defaultdict
32 32
33 33 from sqlalchemy import *
34 34 from sqlalchemy.ext.hybrid import hybrid_property
35 35 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 36 from sqlalchemy.exc import DatabaseError
37 37 from beaker.cache import cache_region, region_invalidate
38 38 from webob.exc import HTTPNotFound
39 39
40 40 from pylons.i18n.translation import lazy_ugettext as _
41 41
42 42 from rhodecode.lib.vcs import get_backend
43 43 from rhodecode.lib.vcs.utils.helpers import get_scm
44 44 from rhodecode.lib.vcs.exceptions import VCSError
45 45 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 46
47 47 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
48 48 safe_unicode
49 49 from rhodecode.lib.compat import json
50 50 from rhodecode.lib.caching_query import FromCache
51 51
52 52 from rhodecode.model.meta import Base, Session
53 53
54 54 URL_SEP = '/'
55 55 log = logging.getLogger(__name__)
56 56
57 57 #==============================================================================
58 58 # BASE CLASSES
59 59 #==============================================================================
60 60
61 61 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
62 62
63 63
64 64 class BaseModel(object):
65 65 """
66 66 Base Model for all classess
67 67 """
68 68
69 69 @classmethod
70 70 def _get_keys(cls):
71 71 """return column names for this model """
72 72 return class_mapper(cls).c.keys()
73 73
74 74 def get_dict(self):
75 75 """
76 76 return dict with keys and values corresponding
77 77 to this model data """
78 78
79 79 d = {}
80 80 for k in self._get_keys():
81 81 d[k] = getattr(self, k)
82 82
83 83 # also use __json__() if present to get additional fields
84 84 _json_attr = getattr(self, '__json__', None)
85 85 if _json_attr:
86 86 # update with attributes from __json__
87 87 if callable(_json_attr):
88 88 _json_attr = _json_attr()
89 89 for k, val in _json_attr.iteritems():
90 90 d[k] = val
91 91 return d
92 92
93 93 def get_appstruct(self):
94 94 """return list with keys and values tupples corresponding
95 95 to this model data """
96 96
97 97 l = []
98 98 for k in self._get_keys():
99 99 l.append((k, getattr(self, k),))
100 100 return l
101 101
102 102 def populate_obj(self, populate_dict):
103 103 """populate model with data from given populate_dict"""
104 104
105 105 for k in self._get_keys():
106 106 if k in populate_dict:
107 107 setattr(self, k, populate_dict[k])
108 108
109 109 @classmethod
110 110 def query(cls):
111 111 return Session().query(cls)
112 112
113 113 @classmethod
114 114 def get(cls, id_):
115 115 if id_:
116 116 return cls.query().get(id_)
117 117
118 118 @classmethod
119 119 def get_or_404(cls, id_):
120 120 if id_:
121 121 res = cls.query().get(id_)
122 122 if not res:
123 123 raise HTTPNotFound
124 124 return res
125 125
126 126 @classmethod
127 127 def getAll(cls):
128 128 return cls.query().all()
129 129
130 130 @classmethod
131 131 def delete(cls, id_):
132 132 obj = cls.query().get(id_)
133 133 Session().delete(obj)
134 134
135 135 def __repr__(self):
136 136 if hasattr(self, '__unicode__'):
137 137 # python repr needs to return str
138 138 return safe_str(self.__unicode__())
139 139 return '<DB:%s>' % (self.__class__.__name__)
140 140
141 141
142 142 class RhodeCodeSetting(Base, BaseModel):
143 143 __tablename__ = 'rhodecode_settings'
144 144 __table_args__ = (
145 145 UniqueConstraint('app_settings_name'),
146 146 {'extend_existing': True, 'mysql_engine': 'InnoDB',
147 147 'mysql_charset': 'utf8'}
148 148 )
149 149 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
150 150 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
151 151 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
152 152
153 153 def __init__(self, k='', v=''):
154 154 self.app_settings_name = k
155 155 self.app_settings_value = v
156 156
157 157 @validates('_app_settings_value')
158 158 def validate_settings_value(self, key, val):
159 159 assert type(val) == unicode
160 160 return val
161 161
162 162 @hybrid_property
163 163 def app_settings_value(self):
164 164 v = self._app_settings_value
165 165 if self.app_settings_name == 'ldap_active':
166 166 v = str2bool(v)
167 167 return v
168 168
169 169 @app_settings_value.setter
170 170 def app_settings_value(self, val):
171 171 """
172 172 Setter that will always make sure we use unicode in app_settings_value
173 173
174 174 :param val:
175 175 """
176 176 self._app_settings_value = safe_unicode(val)
177 177
178 178 def __unicode__(self):
179 179 return u"<%s('%s:%s')>" % (
180 180 self.__class__.__name__,
181 181 self.app_settings_name, self.app_settings_value
182 182 )
183 183
184 184 @classmethod
185 185 def get_by_name(cls, key):
186 186 return cls.query()\
187 187 .filter(cls.app_settings_name == key).scalar()
188 188
189 189 @classmethod
190 190 def get_by_name_or_create(cls, key):
191 191 res = cls.get_by_name(key)
192 192 if not res:
193 193 res = cls(key)
194 194 return res
195 195
196 196 @classmethod
197 197 def get_app_settings(cls, cache=False):
198 198
199 199 ret = cls.query()
200 200
201 201 if cache:
202 202 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
203 203
204 204 if not ret:
205 205 raise Exception('Could not get application settings !')
206 206 settings = {}
207 207 for each in ret:
208 208 settings['rhodecode_' + each.app_settings_name] = \
209 209 each.app_settings_value
210 210
211 211 return settings
212 212
213 213 @classmethod
214 214 def get_ldap_settings(cls, cache=False):
215 215 ret = cls.query()\
216 216 .filter(cls.app_settings_name.startswith('ldap_')).all()
217 217 fd = {}
218 218 for row in ret:
219 219 fd.update({row.app_settings_name: row.app_settings_value})
220 220
221 221 return fd
222 222
223 223
224 224 class RhodeCodeUi(Base, BaseModel):
225 225 __tablename__ = 'rhodecode_ui'
226 226 __table_args__ = (
227 227 UniqueConstraint('ui_key'),
228 228 {'extend_existing': True, 'mysql_engine': 'InnoDB',
229 229 'mysql_charset': 'utf8'}
230 230 )
231 231
232 232 HOOK_UPDATE = 'changegroup.update'
233 233 HOOK_REPO_SIZE = 'changegroup.repo_size'
234 234 HOOK_PUSH = 'changegroup.push_logger'
235 235 HOOK_PULL = 'preoutgoing.pull_logger'
236 236
237 237 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
238 238 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
239 239 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
240 240 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
241 241 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
242 242
243 243 @classmethod
244 244 def get_by_key(cls, key):
245 return cls.query().filter(cls.ui_key == key)
245 return cls.query().filter(cls.ui_key == key).scalar()
246 246
247 247 @classmethod
248 248 def get_builtin_hooks(cls):
249 249 q = cls.query()
250 250 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
251 251 cls.HOOK_REPO_SIZE,
252 252 cls.HOOK_PUSH, cls.HOOK_PULL]))
253 253 return q.all()
254 254
255 255 @classmethod
256 256 def get_custom_hooks(cls):
257 257 q = cls.query()
258 258 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
259 259 cls.HOOK_REPO_SIZE,
260 260 cls.HOOK_PUSH, cls.HOOK_PULL]))
261 261 q = q.filter(cls.ui_section == 'hooks')
262 262 return q.all()
263 263
264 264 @classmethod
265 265 def get_repos_location(cls):
266 return cls.get_by_key('/').one().ui_value
266 return cls.get_by_key('/').ui_value
267 267
268 268 @classmethod
269 269 def create_or_update_hook(cls, key, val):
270 new_ui = cls.get_by_key(key).scalar() or cls()
270 new_ui = cls.get_by_key(key) or cls()
271 271 new_ui.ui_section = 'hooks'
272 272 new_ui.ui_active = True
273 273 new_ui.ui_key = key
274 274 new_ui.ui_value = val
275 275
276 276 Session().add(new_ui)
277 277
278 278
279 279 class User(Base, BaseModel):
280 280 __tablename__ = 'users'
281 281 __table_args__ = (
282 282 UniqueConstraint('username'), UniqueConstraint('email'),
283 283 {'extend_existing': True, 'mysql_engine': 'InnoDB',
284 284 'mysql_charset': 'utf8'}
285 285 )
286 286 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
287 287 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
288 288 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
289 289 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
290 290 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
291 291 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
292 292 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
293 293 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
294 294 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
295 295 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
296 296 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
297 297
298 298 user_log = relationship('UserLog', cascade='all')
299 299 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
300 300
301 301 repositories = relationship('Repository')
302 302 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
303 303 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
304 304 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
305 305
306 306 group_member = relationship('UsersGroupMember', cascade='all')
307 307
308 308 notifications = relationship('UserNotification', cascade='all')
309 309 # notifications assigned to this user
310 310 user_created_notifications = relationship('Notification', cascade='all')
311 311 # comments created by this user
312 312 user_comments = relationship('ChangesetComment', cascade='all')
313 313 #extra emails for this user
314 314 user_emails = relationship('UserEmailMap', cascade='all')
315 315
316 316 @hybrid_property
317 317 def email(self):
318 318 return self._email
319 319
320 320 @email.setter
321 321 def email(self, val):
322 322 self._email = val.lower() if val else None
323 323
324 324 @property
325 325 def emails(self):
326 326 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
327 327 return [self.email] + [x.email for x in other]
328 328
329 329 @property
330 330 def full_name(self):
331 331 return '%s %s' % (self.name, self.lastname)
332 332
333 333 @property
334 334 def full_name_or_username(self):
335 335 return ('%s %s' % (self.name, self.lastname)
336 336 if (self.name and self.lastname) else self.username)
337 337
338 338 @property
339 339 def full_contact(self):
340 340 return '%s %s <%s>' % (self.name, self.lastname, self.email)
341 341
342 342 @property
343 343 def short_contact(self):
344 344 return '%s %s' % (self.name, self.lastname)
345 345
346 346 @property
347 347 def is_admin(self):
348 348 return self.admin
349 349
350 350 def __unicode__(self):
351 351 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
352 352 self.user_id, self.username)
353 353
354 354 @classmethod
355 355 def get_by_username(cls, username, case_insensitive=False, cache=False):
356 356 if case_insensitive:
357 357 q = cls.query().filter(cls.username.ilike(username))
358 358 else:
359 359 q = cls.query().filter(cls.username == username)
360 360
361 361 if cache:
362 362 q = q.options(FromCache(
363 363 "sql_cache_short",
364 364 "get_user_%s" % _hash_key(username)
365 365 )
366 366 )
367 367 return q.scalar()
368 368
369 369 @classmethod
370 370 def get_by_api_key(cls, api_key, cache=False):
371 371 q = cls.query().filter(cls.api_key == api_key)
372 372
373 373 if cache:
374 374 q = q.options(FromCache("sql_cache_short",
375 375 "get_api_key_%s" % api_key))
376 376 return q.scalar()
377 377
378 378 @classmethod
379 379 def get_by_email(cls, email, case_insensitive=False, cache=False):
380 380 if case_insensitive:
381 381 q = cls.query().filter(cls.email.ilike(email))
382 382 else:
383 383 q = cls.query().filter(cls.email == email)
384 384
385 385 if cache:
386 386 q = q.options(FromCache("sql_cache_short",
387 387 "get_email_key_%s" % email))
388 388
389 389 ret = q.scalar()
390 390 if ret is None:
391 391 q = UserEmailMap.query()
392 392 # try fetching in alternate email map
393 393 if case_insensitive:
394 394 q = q.filter(UserEmailMap.email.ilike(email))
395 395 else:
396 396 q = q.filter(UserEmailMap.email == email)
397 397 q = q.options(joinedload(UserEmailMap.user))
398 398 if cache:
399 399 q = q.options(FromCache("sql_cache_short",
400 400 "get_email_map_key_%s" % email))
401 401 ret = getattr(q.scalar(), 'user', None)
402 402
403 403 return ret
404 404
405 405 def update_lastlogin(self):
406 406 """Update user lastlogin"""
407 407 self.last_login = datetime.datetime.now()
408 408 Session().add(self)
409 409 log.debug('updated user %s lastlogin' % self.username)
410 410
411 411 def get_api_data(self):
412 412 """
413 413 Common function for generating user related data for API
414 414 """
415 415 user = self
416 416 data = dict(
417 417 user_id=user.user_id,
418 418 username=user.username,
419 419 firstname=user.name,
420 420 lastname=user.lastname,
421 421 email=user.email,
422 422 emails=user.emails,
423 423 api_key=user.api_key,
424 424 active=user.active,
425 425 admin=user.admin,
426 426 ldap_dn=user.ldap_dn,
427 427 last_login=user.last_login,
428 428 )
429 429 return data
430 430
431 431 def __json__(self):
432 432 data = dict(
433 433 full_name=self.full_name,
434 434 full_name_or_username=self.full_name_or_username,
435 435 short_contact=self.short_contact,
436 436 full_contact=self.full_contact
437 437 )
438 438 data.update(self.get_api_data())
439 439 return data
440 440
441 441
442 442 class UserEmailMap(Base, BaseModel):
443 443 __tablename__ = 'user_email_map'
444 444 __table_args__ = (
445 445 Index('uem_email_idx', 'email'),
446 446 UniqueConstraint('email'),
447 447 {'extend_existing': True, 'mysql_engine': 'InnoDB',
448 448 'mysql_charset': 'utf8'}
449 449 )
450 450 __mapper_args__ = {}
451 451
452 452 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
453 453 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
454 454 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
455 455
456 456 user = relationship('User', lazy='joined')
457 457
458 458 @validates('_email')
459 459 def validate_email(self, key, email):
460 460 # check if this email is not main one
461 461 main_email = Session().query(User).filter(User.email == email).scalar()
462 462 if main_email is not None:
463 463 raise AttributeError('email %s is present is user table' % email)
464 464 return email
465 465
466 466 @hybrid_property
467 467 def email(self):
468 468 return self._email
469 469
470 470 @email.setter
471 471 def email(self, val):
472 472 self._email = val.lower() if val else None
473 473
474 474
475 475 class UserLog(Base, BaseModel):
476 476 __tablename__ = 'user_logs'
477 477 __table_args__ = (
478 478 {'extend_existing': True, 'mysql_engine': 'InnoDB',
479 479 'mysql_charset': 'utf8'},
480 480 )
481 481 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
482 482 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
483 483 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
484 484 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
485 485 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
486 486 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
487 487 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
488 488
489 489 @property
490 490 def action_as_day(self):
491 491 return datetime.date(*self.action_date.timetuple()[:3])
492 492
493 493 user = relationship('User')
494 494 repository = relationship('Repository', cascade='')
495 495
496 496
497 497 class UsersGroup(Base, BaseModel):
498 498 __tablename__ = 'users_groups'
499 499 __table_args__ = (
500 500 {'extend_existing': True, 'mysql_engine': 'InnoDB',
501 501 'mysql_charset': 'utf8'},
502 502 )
503 503
504 504 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
505 505 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
506 506 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
507 507
508 508 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
509 509 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
510 510 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
511 511
512 512 def __unicode__(self):
513 513 return u'<userGroup(%s)>' % (self.users_group_name)
514 514
515 515 @classmethod
516 516 def get_by_group_name(cls, group_name, cache=False,
517 517 case_insensitive=False):
518 518 if case_insensitive:
519 519 q = cls.query().filter(cls.users_group_name.ilike(group_name))
520 520 else:
521 521 q = cls.query().filter(cls.users_group_name == group_name)
522 522 if cache:
523 523 q = q.options(FromCache(
524 524 "sql_cache_short",
525 525 "get_user_%s" % _hash_key(group_name)
526 526 )
527 527 )
528 528 return q.scalar()
529 529
530 530 @classmethod
531 531 def get(cls, users_group_id, cache=False):
532 532 users_group = cls.query()
533 533 if cache:
534 534 users_group = users_group.options(FromCache("sql_cache_short",
535 535 "get_users_group_%s" % users_group_id))
536 536 return users_group.get(users_group_id)
537 537
538 538 def get_api_data(self):
539 539 users_group = self
540 540
541 541 data = dict(
542 542 users_group_id=users_group.users_group_id,
543 543 group_name=users_group.users_group_name,
544 544 active=users_group.users_group_active,
545 545 )
546 546
547 547 return data
548 548
549 549
550 550 class UsersGroupMember(Base, BaseModel):
551 551 __tablename__ = 'users_groups_members'
552 552 __table_args__ = (
553 553 {'extend_existing': True, 'mysql_engine': 'InnoDB',
554 554 'mysql_charset': 'utf8'},
555 555 )
556 556
557 557 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
558 558 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
559 559 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
560 560
561 561 user = relationship('User', lazy='joined')
562 562 users_group = relationship('UsersGroup')
563 563
564 564 def __init__(self, gr_id='', u_id=''):
565 565 self.users_group_id = gr_id
566 566 self.user_id = u_id
567 567
568 568
569 569 class Repository(Base, BaseModel):
570 570 __tablename__ = 'repositories'
571 571 __table_args__ = (
572 572 UniqueConstraint('repo_name'),
573 573 {'extend_existing': True, 'mysql_engine': 'InnoDB',
574 574 'mysql_charset': 'utf8'},
575 575 )
576 576
577 577 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
578 578 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
579 579 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
580 580 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
581 581 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
582 582 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
583 583 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
584 584 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
585 585 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
586 586 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
587 587 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
588 588
589 589 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
590 590 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
591 591
592 592 user = relationship('User')
593 593 fork = relationship('Repository', remote_side=repo_id)
594 594 group = relationship('RepoGroup')
595 595 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
596 596 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
597 597 stats = relationship('Statistics', cascade='all', uselist=False)
598 598
599 599 followers = relationship('UserFollowing',
600 600 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
601 601 cascade='all')
602 602
603 603 logs = relationship('UserLog')
604 604 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
605 605
606 606 pull_requests_org = relationship('PullRequest',
607 607 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
608 608 cascade="all, delete, delete-orphan")
609 609
610 610 pull_requests_other = relationship('PullRequest',
611 611 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
612 612 cascade="all, delete, delete-orphan")
613 613
614 614 def __unicode__(self):
615 615 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
616 616 self.repo_name)
617 617
618 618 @classmethod
619 619 def url_sep(cls):
620 620 return URL_SEP
621 621
622 622 @classmethod
623 623 def get_by_repo_name(cls, repo_name):
624 624 q = Session().query(cls).filter(cls.repo_name == repo_name)
625 625 q = q.options(joinedload(Repository.fork))\
626 626 .options(joinedload(Repository.user))\
627 627 .options(joinedload(Repository.group))
628 628 return q.scalar()
629 629
630 630 @classmethod
631 631 def get_by_full_path(cls, repo_full_path):
632 632 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
633 633 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
634 634
635 635 @classmethod
636 636 def get_repo_forks(cls, repo_id):
637 637 return cls.query().filter(Repository.fork_id == repo_id)
638 638
639 639 @classmethod
640 640 def base_path(cls):
641 641 """
642 642 Returns base path when all repos are stored
643 643
644 644 :param cls:
645 645 """
646 646 q = Session().query(RhodeCodeUi)\
647 647 .filter(RhodeCodeUi.ui_key == cls.url_sep())
648 648 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
649 649 return q.one().ui_value
650 650
651 651 @property
652 652 def forks(self):
653 653 """
654 654 Return forks of this repo
655 655 """
656 656 return Repository.get_repo_forks(self.repo_id)
657 657
658 658 @property
659 659 def parent(self):
660 660 """
661 661 Returns fork parent
662 662 """
663 663 return self.fork
664 664
665 665 @property
666 666 def just_name(self):
667 667 return self.repo_name.split(Repository.url_sep())[-1]
668 668
669 669 @property
670 670 def groups_with_parents(self):
671 671 groups = []
672 672 if self.group is None:
673 673 return groups
674 674
675 675 cur_gr = self.group
676 676 groups.insert(0, cur_gr)
677 677 while 1:
678 678 gr = getattr(cur_gr, 'parent_group', None)
679 679 cur_gr = cur_gr.parent_group
680 680 if gr is None:
681 681 break
682 682 groups.insert(0, gr)
683 683
684 684 return groups
685 685
686 686 @property
687 687 def groups_and_repo(self):
688 688 return self.groups_with_parents, self.just_name
689 689
690 690 @LazyProperty
691 691 def repo_path(self):
692 692 """
693 693 Returns base full path for that repository means where it actually
694 694 exists on a filesystem
695 695 """
696 696 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
697 697 Repository.url_sep())
698 698 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
699 699 return q.one().ui_value
700 700
701 701 @property
702 702 def repo_full_path(self):
703 703 p = [self.repo_path]
704 704 # we need to split the name by / since this is how we store the
705 705 # names in the database, but that eventually needs to be converted
706 706 # into a valid system path
707 707 p += self.repo_name.split(Repository.url_sep())
708 708 return os.path.join(*p)
709 709
710 710 def get_new_name(self, repo_name):
711 711 """
712 712 returns new full repository name based on assigned group and new new
713 713
714 714 :param group_name:
715 715 """
716 716 path_prefix = self.group.full_path_splitted if self.group else []
717 717 return Repository.url_sep().join(path_prefix + [repo_name])
718 718
719 719 @property
720 720 def _ui(self):
721 721 """
722 722 Creates an db based ui object for this repository
723 723 """
724 724 from mercurial import ui
725 725 from mercurial import config
726 726 baseui = ui.ui()
727 727
728 728 #clean the baseui object
729 729 baseui._ocfg = config.config()
730 730 baseui._ucfg = config.config()
731 731 baseui._tcfg = config.config()
732 732
733 733 ret = RhodeCodeUi.query()\
734 734 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
735 735
736 736 hg_ui = ret
737 737 for ui_ in hg_ui:
738 738 if ui_.ui_active:
739 739 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
740 740 ui_.ui_key, ui_.ui_value)
741 741 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
742 742 if ui_.ui_key == 'push_ssl':
743 743 # force set push_ssl requirement to False, rhodecode
744 744 # handles that
745 745 baseui.setconfig(ui_.ui_section, ui_.ui_key, False)
746 746
747 747 return baseui
748 748
749 749 @classmethod
750 750 def inject_ui(cls, repo, extras={}):
751 751 from rhodecode.lib.vcs.backends.hg import MercurialRepository
752 752 from rhodecode.lib.vcs.backends.git import GitRepository
753 753 required = (MercurialRepository, GitRepository)
754 754 if not isinstance(repo, required):
755 755 raise Exception('repo must be instance of %s' % required)
756 756
757 757 # inject ui extra param to log this action via push logger
758 758 for k, v in extras.items():
759 759 repo._repo.ui.setconfig('rhodecode_extras', k, v)
760 760
761 761 @classmethod
762 762 def is_valid(cls, repo_name):
763 763 """
764 764 returns True if given repo name is a valid filesystem repository
765 765
766 766 :param cls:
767 767 :param repo_name:
768 768 """
769 769 from rhodecode.lib.utils import is_valid_repo
770 770
771 771 return is_valid_repo(repo_name, cls.base_path())
772 772
773 773 def get_api_data(self):
774 774 """
775 775 Common function for generating repo api data
776 776
777 777 """
778 778 repo = self
779 779 data = dict(
780 780 repo_id=repo.repo_id,
781 781 repo_name=repo.repo_name,
782 782 repo_type=repo.repo_type,
783 783 clone_uri=repo.clone_uri,
784 784 private=repo.private,
785 785 created_on=repo.created_on,
786 786 description=repo.description,
787 787 landing_rev=repo.landing_rev,
788 788 owner=repo.user.username,
789 789 fork_of=repo.fork.repo_name if repo.fork else None
790 790 )
791 791
792 792 return data
793 793
794 794 #==========================================================================
795 795 # SCM PROPERTIES
796 796 #==========================================================================
797 797
798 798 def get_changeset(self, rev=None):
799 799 return get_changeset_safe(self.scm_instance, rev)
800 800
801 801 def get_landing_changeset(self):
802 802 """
803 803 Returns landing changeset, or if that doesn't exist returns the tip
804 804 """
805 805 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
806 806 return cs
807 807
808 808 @property
809 809 def tip(self):
810 810 return self.get_changeset('tip')
811 811
812 812 @property
813 813 def author(self):
814 814 return self.tip.author
815 815
816 816 @property
817 817 def last_change(self):
818 818 return self.scm_instance.last_change
819 819
820 820 def get_comments(self, revisions=None):
821 821 """
822 822 Returns comments for this repository grouped by revisions
823 823
824 824 :param revisions: filter query by revisions only
825 825 """
826 826 cmts = ChangesetComment.query()\
827 827 .filter(ChangesetComment.repo == self)
828 828 if revisions:
829 829 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
830 830 grouped = defaultdict(list)
831 831 for cmt in cmts.all():
832 832 grouped[cmt.revision].append(cmt)
833 833 return grouped
834 834
835 835 def statuses(self, revisions=None):
836 836 """
837 837 Returns statuses for this repository
838 838
839 839 :param revisions: list of revisions to get statuses for
840 840 :type revisions: list
841 841 """
842 842
843 843 statuses = ChangesetStatus.query()\
844 844 .filter(ChangesetStatus.repo == self)\
845 845 .filter(ChangesetStatus.version == 0)
846 846 if revisions:
847 847 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
848 848 grouped = {}
849 849
850 850 #maybe we have open new pullrequest without a status ?
851 851 stat = ChangesetStatus.STATUS_UNDER_REVIEW
852 852 status_lbl = ChangesetStatus.get_status_lbl(stat)
853 853 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
854 854 for rev in pr.revisions:
855 855 pr_id = pr.pull_request_id
856 856 pr_repo = pr.other_repo.repo_name
857 857 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
858 858
859 859 for stat in statuses.all():
860 860 pr_id = pr_repo = None
861 861 if stat.pull_request:
862 862 pr_id = stat.pull_request.pull_request_id
863 863 pr_repo = stat.pull_request.other_repo.repo_name
864 864 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
865 865 pr_id, pr_repo]
866 866 return grouped
867 867
868 868 #==========================================================================
869 869 # SCM CACHE INSTANCE
870 870 #==========================================================================
871 871
872 872 @property
873 873 def invalidate(self):
874 874 return CacheInvalidation.invalidate(self.repo_name)
875 875
876 876 def set_invalidate(self):
877 877 """
878 878 set a cache for invalidation for this instance
879 879 """
880 880 CacheInvalidation.set_invalidate(self.repo_name)
881 881
882 882 @LazyProperty
883 883 def scm_instance(self):
884 884 return self.__get_instance()
885 885
886 886 def scm_instance_cached(self, cache_map=None):
887 887 @cache_region('long_term')
888 888 def _c(repo_name):
889 889 return self.__get_instance()
890 890 rn = self.repo_name
891 891 log.debug('Getting cached instance of repo')
892 892
893 893 if cache_map:
894 894 # get using prefilled cache_map
895 895 invalidate_repo = cache_map[self.repo_name]
896 896 if invalidate_repo:
897 897 invalidate_repo = (None if invalidate_repo.cache_active
898 898 else invalidate_repo)
899 899 else:
900 900 # get from invalidate
901 901 invalidate_repo = self.invalidate
902 902
903 903 if invalidate_repo is not None:
904 904 region_invalidate(_c, None, rn)
905 905 # update our cache
906 906 CacheInvalidation.set_valid(invalidate_repo.cache_key)
907 907 return _c(rn)
908 908
909 909 def __get_instance(self):
910 910 repo_full_path = self.repo_full_path
911 911 try:
912 912 alias = get_scm(repo_full_path)[0]
913 913 log.debug('Creating instance of %s repository' % alias)
914 914 backend = get_backend(alias)
915 915 except VCSError:
916 916 log.error(traceback.format_exc())
917 917 log.error('Perhaps this repository is in db and not in '
918 918 'filesystem run rescan repositories with '
919 919 '"destroy old data " option from admin panel')
920 920 return
921 921
922 922 if alias == 'hg':
923 923
924 924 repo = backend(safe_str(repo_full_path), create=False,
925 925 baseui=self._ui)
926 926 # skip hidden web repository
927 927 if repo._get_hidden():
928 928 return
929 929 else:
930 930 repo = backend(repo_full_path, create=False)
931 931
932 932 return repo
933 933
934 934
935 935 class RepoGroup(Base, BaseModel):
936 936 __tablename__ = 'groups'
937 937 __table_args__ = (
938 938 UniqueConstraint('group_name', 'group_parent_id'),
939 939 CheckConstraint('group_id != group_parent_id'),
940 940 {'extend_existing': True, 'mysql_engine': 'InnoDB',
941 941 'mysql_charset': 'utf8'},
942 942 )
943 943 __mapper_args__ = {'order_by': 'group_name'}
944 944
945 945 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
946 946 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
947 947 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
948 948 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
949 949
950 950 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
951 951 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
952 952
953 953 parent_group = relationship('RepoGroup', remote_side=group_id)
954 954
955 955 def __init__(self, group_name='', parent_group=None):
956 956 self.group_name = group_name
957 957 self.parent_group = parent_group
958 958
959 959 def __unicode__(self):
960 960 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
961 961 self.group_name)
962 962
963 963 @classmethod
964 964 def groups_choices(cls):
965 965 from webhelpers.html import literal as _literal
966 966 repo_groups = [('', '')]
967 967 sep = ' &raquo; '
968 968 _name = lambda k: _literal(sep.join(k))
969 969
970 970 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
971 971 for x in cls.query().all()])
972 972
973 973 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
974 974 return repo_groups
975 975
976 976 @classmethod
977 977 def url_sep(cls):
978 978 return URL_SEP
979 979
980 980 @classmethod
981 981 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
982 982 if case_insensitive:
983 983 gr = cls.query()\
984 984 .filter(cls.group_name.ilike(group_name))
985 985 else:
986 986 gr = cls.query()\
987 987 .filter(cls.group_name == group_name)
988 988 if cache:
989 989 gr = gr.options(FromCache(
990 990 "sql_cache_short",
991 991 "get_group_%s" % _hash_key(group_name)
992 992 )
993 993 )
994 994 return gr.scalar()
995 995
996 996 @property
997 997 def parents(self):
998 998 parents_recursion_limit = 5
999 999 groups = []
1000 1000 if self.parent_group is None:
1001 1001 return groups
1002 1002 cur_gr = self.parent_group
1003 1003 groups.insert(0, cur_gr)
1004 1004 cnt = 0
1005 1005 while 1:
1006 1006 cnt += 1
1007 1007 gr = getattr(cur_gr, 'parent_group', None)
1008 1008 cur_gr = cur_gr.parent_group
1009 1009 if gr is None:
1010 1010 break
1011 1011 if cnt == parents_recursion_limit:
1012 1012 # this will prevent accidental infinit loops
1013 1013 log.error('group nested more than %s' %
1014 1014 parents_recursion_limit)
1015 1015 break
1016 1016
1017 1017 groups.insert(0, gr)
1018 1018 return groups
1019 1019
1020 1020 @property
1021 1021 def children(self):
1022 1022 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1023 1023
1024 1024 @property
1025 1025 def name(self):
1026 1026 return self.group_name.split(RepoGroup.url_sep())[-1]
1027 1027
1028 1028 @property
1029 1029 def full_path(self):
1030 1030 return self.group_name
1031 1031
1032 1032 @property
1033 1033 def full_path_splitted(self):
1034 1034 return self.group_name.split(RepoGroup.url_sep())
1035 1035
1036 1036 @property
1037 1037 def repositories(self):
1038 1038 return Repository.query()\
1039 1039 .filter(Repository.group == self)\
1040 1040 .order_by(Repository.repo_name)
1041 1041
1042 1042 @property
1043 1043 def repositories_recursive_count(self):
1044 1044 cnt = self.repositories.count()
1045 1045
1046 1046 def children_count(group):
1047 1047 cnt = 0
1048 1048 for child in group.children:
1049 1049 cnt += child.repositories.count()
1050 1050 cnt += children_count(child)
1051 1051 return cnt
1052 1052
1053 1053 return cnt + children_count(self)
1054 1054
1055 1055 def get_new_name(self, group_name):
1056 1056 """
1057 1057 returns new full group name based on parent and new name
1058 1058
1059 1059 :param group_name:
1060 1060 """
1061 1061 path_prefix = (self.parent_group.full_path_splitted if
1062 1062 self.parent_group else [])
1063 1063 return RepoGroup.url_sep().join(path_prefix + [group_name])
1064 1064
1065 1065
1066 1066 class Permission(Base, BaseModel):
1067 1067 __tablename__ = 'permissions'
1068 1068 __table_args__ = (
1069 1069 Index('p_perm_name_idx', 'permission_name'),
1070 1070 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1071 1071 'mysql_charset': 'utf8'},
1072 1072 )
1073 1073 PERMS = [
1074 1074 ('repository.none', _('Repository no access')),
1075 1075 ('repository.read', _('Repository read access')),
1076 1076 ('repository.write', _('Repository write access')),
1077 1077 ('repository.admin', _('Repository admin access')),
1078 1078
1079 1079 ('group.none', _('Repositories Group no access')),
1080 1080 ('group.read', _('Repositories Group read access')),
1081 1081 ('group.write', _('Repositories Group write access')),
1082 1082 ('group.admin', _('Repositories Group admin access')),
1083 1083
1084 1084 ('hg.admin', _('RhodeCode Administrator')),
1085 1085 ('hg.create.none', _('Repository creation disabled')),
1086 1086 ('hg.create.repository', _('Repository creation enabled')),
1087 1087 ('hg.register.none', _('Register disabled')),
1088 1088 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1089 1089 'with manual activation')),
1090 1090
1091 1091 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1092 1092 'with auto activation')),
1093 1093 ]
1094 1094
1095 1095 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1096 1096 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1097 1097 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1098 1098
1099 1099 def __unicode__(self):
1100 1100 return u"<%s('%s:%s')>" % (
1101 1101 self.__class__.__name__, self.permission_id, self.permission_name
1102 1102 )
1103 1103
1104 1104 @classmethod
1105 1105 def get_by_key(cls, key):
1106 1106 return cls.query().filter(cls.permission_name == key).scalar()
1107 1107
1108 1108 @classmethod
1109 1109 def get_default_perms(cls, default_user_id):
1110 1110 q = Session().query(UserRepoToPerm, Repository, cls)\
1111 1111 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1112 1112 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1113 1113 .filter(UserRepoToPerm.user_id == default_user_id)
1114 1114
1115 1115 return q.all()
1116 1116
1117 1117 @classmethod
1118 1118 def get_default_group_perms(cls, default_user_id):
1119 1119 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1120 1120 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1121 1121 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1122 1122 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1123 1123
1124 1124 return q.all()
1125 1125
1126 1126
1127 1127 class UserRepoToPerm(Base, BaseModel):
1128 1128 __tablename__ = 'repo_to_perm'
1129 1129 __table_args__ = (
1130 1130 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1131 1131 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1132 1132 'mysql_charset': 'utf8'}
1133 1133 )
1134 1134 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1135 1135 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1136 1136 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1137 1137 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1138 1138
1139 1139 user = relationship('User')
1140 1140 repository = relationship('Repository')
1141 1141 permission = relationship('Permission')
1142 1142
1143 1143 @classmethod
1144 1144 def create(cls, user, repository, permission):
1145 1145 n = cls()
1146 1146 n.user = user
1147 1147 n.repository = repository
1148 1148 n.permission = permission
1149 1149 Session().add(n)
1150 1150 return n
1151 1151
1152 1152 def __unicode__(self):
1153 1153 return u'<user:%s => %s >' % (self.user, self.repository)
1154 1154
1155 1155
1156 1156 class UserToPerm(Base, BaseModel):
1157 1157 __tablename__ = 'user_to_perm'
1158 1158 __table_args__ = (
1159 1159 UniqueConstraint('user_id', 'permission_id'),
1160 1160 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1161 1161 'mysql_charset': 'utf8'}
1162 1162 )
1163 1163 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1164 1164 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1165 1165 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1166 1166
1167 1167 user = relationship('User')
1168 1168 permission = relationship('Permission', lazy='joined')
1169 1169
1170 1170
1171 1171 class UsersGroupRepoToPerm(Base, BaseModel):
1172 1172 __tablename__ = 'users_group_repo_to_perm'
1173 1173 __table_args__ = (
1174 1174 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1175 1175 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1176 1176 'mysql_charset': 'utf8'}
1177 1177 )
1178 1178 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1179 1179 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1180 1180 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1181 1181 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1182 1182
1183 1183 users_group = relationship('UsersGroup')
1184 1184 permission = relationship('Permission')
1185 1185 repository = relationship('Repository')
1186 1186
1187 1187 @classmethod
1188 1188 def create(cls, users_group, repository, permission):
1189 1189 n = cls()
1190 1190 n.users_group = users_group
1191 1191 n.repository = repository
1192 1192 n.permission = permission
1193 1193 Session().add(n)
1194 1194 return n
1195 1195
1196 1196 def __unicode__(self):
1197 1197 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1198 1198
1199 1199
1200 1200 class UsersGroupToPerm(Base, BaseModel):
1201 1201 __tablename__ = 'users_group_to_perm'
1202 1202 __table_args__ = (
1203 1203 UniqueConstraint('users_group_id', 'permission_id',),
1204 1204 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1205 1205 'mysql_charset': 'utf8'}
1206 1206 )
1207 1207 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1208 1208 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1209 1209 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1210 1210
1211 1211 users_group = relationship('UsersGroup')
1212 1212 permission = relationship('Permission')
1213 1213
1214 1214
1215 1215 class UserRepoGroupToPerm(Base, BaseModel):
1216 1216 __tablename__ = 'user_repo_group_to_perm'
1217 1217 __table_args__ = (
1218 1218 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1219 1219 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1220 1220 'mysql_charset': 'utf8'}
1221 1221 )
1222 1222
1223 1223 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1224 1224 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1225 1225 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1226 1226 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1227 1227
1228 1228 user = relationship('User')
1229 1229 group = relationship('RepoGroup')
1230 1230 permission = relationship('Permission')
1231 1231
1232 1232
1233 1233 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1234 1234 __tablename__ = 'users_group_repo_group_to_perm'
1235 1235 __table_args__ = (
1236 1236 UniqueConstraint('users_group_id', 'group_id'),
1237 1237 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1238 1238 'mysql_charset': 'utf8'}
1239 1239 )
1240 1240
1241 1241 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)
1242 1242 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1243 1243 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1244 1244 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1245 1245
1246 1246 users_group = relationship('UsersGroup')
1247 1247 permission = relationship('Permission')
1248 1248 group = relationship('RepoGroup')
1249 1249
1250 1250
1251 1251 class Statistics(Base, BaseModel):
1252 1252 __tablename__ = 'statistics'
1253 1253 __table_args__ = (
1254 1254 UniqueConstraint('repository_id'),
1255 1255 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1256 1256 'mysql_charset': 'utf8'}
1257 1257 )
1258 1258 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1259 1259 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1260 1260 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1261 1261 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1262 1262 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1263 1263 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1264 1264
1265 1265 repository = relationship('Repository', single_parent=True)
1266 1266
1267 1267
1268 1268 class UserFollowing(Base, BaseModel):
1269 1269 __tablename__ = 'user_followings'
1270 1270 __table_args__ = (
1271 1271 UniqueConstraint('user_id', 'follows_repository_id'),
1272 1272 UniqueConstraint('user_id', 'follows_user_id'),
1273 1273 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1274 1274 'mysql_charset': 'utf8'}
1275 1275 )
1276 1276
1277 1277 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1278 1278 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1279 1279 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1280 1280 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1281 1281 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1282 1282
1283 1283 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1284 1284
1285 1285 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1286 1286 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1287 1287
1288 1288 @classmethod
1289 1289 def get_repo_followers(cls, repo_id):
1290 1290 return cls.query().filter(cls.follows_repo_id == repo_id)
1291 1291
1292 1292
1293 1293 class CacheInvalidation(Base, BaseModel):
1294 1294 __tablename__ = 'cache_invalidation'
1295 1295 __table_args__ = (
1296 1296 UniqueConstraint('cache_key'),
1297 1297 Index('key_idx', 'cache_key'),
1298 1298 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1299 1299 'mysql_charset': 'utf8'},
1300 1300 )
1301 1301 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1302 1302 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1303 1303 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1304 1304 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1305 1305
1306 1306 def __init__(self, cache_key, cache_args=''):
1307 1307 self.cache_key = cache_key
1308 1308 self.cache_args = cache_args
1309 1309 self.cache_active = False
1310 1310
1311 1311 def __unicode__(self):
1312 1312 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1313 1313 self.cache_id, self.cache_key)
1314 1314
1315 1315 @classmethod
1316 1316 def clear_cache(cls):
1317 1317 cls.query().delete()
1318 1318
1319 1319 @classmethod
1320 1320 def _get_key(cls, key):
1321 1321 """
1322 1322 Wrapper for generating a key, together with a prefix
1323 1323
1324 1324 :param key:
1325 1325 """
1326 1326 import rhodecode
1327 1327 prefix = ''
1328 1328 iid = rhodecode.CONFIG.get('instance_id')
1329 1329 if iid:
1330 1330 prefix = iid
1331 1331 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1332 1332
1333 1333 @classmethod
1334 1334 def get_by_key(cls, key):
1335 1335 return cls.query().filter(cls.cache_key == key).scalar()
1336 1336
1337 1337 @classmethod
1338 1338 def _get_or_create_key(cls, key, prefix, org_key):
1339 1339 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1340 1340 if not inv_obj:
1341 1341 try:
1342 1342 inv_obj = CacheInvalidation(key, org_key)
1343 1343 Session().add(inv_obj)
1344 1344 Session().commit()
1345 1345 except Exception:
1346 1346 log.error(traceback.format_exc())
1347 1347 Session().rollback()
1348 1348 return inv_obj
1349 1349
1350 1350 @classmethod
1351 1351 def invalidate(cls, key):
1352 1352 """
1353 1353 Returns Invalidation object if this given key should be invalidated
1354 1354 None otherwise. `cache_active = False` means that this cache
1355 1355 state is not valid and needs to be invalidated
1356 1356
1357 1357 :param key:
1358 1358 """
1359 1359
1360 1360 key, _prefix, _org_key = cls._get_key(key)
1361 1361 inv = cls._get_or_create_key(key, _prefix, _org_key)
1362 1362
1363 1363 if inv and inv.cache_active is False:
1364 1364 return inv
1365 1365
1366 1366 @classmethod
1367 1367 def set_invalidate(cls, key):
1368 1368 """
1369 1369 Mark this Cache key for invalidation
1370 1370
1371 1371 :param key:
1372 1372 """
1373 1373
1374 1374 key, _prefix, _org_key = cls._get_key(key)
1375 1375 inv_objs = Session().query(cls).filter(cls.cache_args == _org_key).all()
1376 1376 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1377 1377 _org_key))
1378 1378 try:
1379 1379 for inv_obj in inv_objs:
1380 1380 if inv_obj:
1381 1381 inv_obj.cache_active = False
1382 1382
1383 1383 Session().add(inv_obj)
1384 1384 Session().commit()
1385 1385 except Exception:
1386 1386 log.error(traceback.format_exc())
1387 1387 Session().rollback()
1388 1388
1389 1389 @classmethod
1390 1390 def set_valid(cls, key):
1391 1391 """
1392 1392 Mark this cache key as active and currently cached
1393 1393
1394 1394 :param key:
1395 1395 """
1396 1396 inv_obj = cls.get_by_key(key)
1397 1397 inv_obj.cache_active = True
1398 1398 Session().add(inv_obj)
1399 1399 Session().commit()
1400 1400
1401 1401 @classmethod
1402 1402 def get_cache_map(cls):
1403 1403
1404 1404 class cachemapdict(dict):
1405 1405
1406 1406 def __init__(self, *args, **kwargs):
1407 1407 fixkey = kwargs.get('fixkey')
1408 1408 if fixkey:
1409 1409 del kwargs['fixkey']
1410 1410 self.fixkey = fixkey
1411 1411 super(cachemapdict, self).__init__(*args, **kwargs)
1412 1412
1413 1413 def __getattr__(self, name):
1414 1414 key = name
1415 1415 if self.fixkey:
1416 1416 key, _prefix, _org_key = cls._get_key(key)
1417 1417 if key in self.__dict__:
1418 1418 return self.__dict__[key]
1419 1419 else:
1420 1420 return self[key]
1421 1421
1422 1422 def __getitem__(self, key):
1423 1423 if self.fixkey:
1424 1424 key, _prefix, _org_key = cls._get_key(key)
1425 1425 try:
1426 1426 return super(cachemapdict, self).__getitem__(key)
1427 1427 except KeyError:
1428 1428 return
1429 1429
1430 1430 cache_map = cachemapdict(fixkey=True)
1431 1431 for obj in cls.query().all():
1432 1432 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1433 1433 return cache_map
1434 1434
1435 1435
1436 1436 class ChangesetComment(Base, BaseModel):
1437 1437 __tablename__ = 'changeset_comments'
1438 1438 __table_args__ = (
1439 1439 Index('cc_revision_idx', 'revision'),
1440 1440 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1441 1441 'mysql_charset': 'utf8'},
1442 1442 )
1443 1443 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1444 1444 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1445 1445 revision = Column('revision', String(40), nullable=True)
1446 1446 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1447 1447 line_no = Column('line_no', Unicode(10), nullable=True)
1448 1448 f_path = Column('f_path', Unicode(1000), nullable=True)
1449 1449 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1450 1450 text = Column('text', Unicode(25000), nullable=False)
1451 1451 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1452 1452 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1453 1453
1454 1454 author = relationship('User', lazy='joined')
1455 1455 repo = relationship('Repository')
1456 1456 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1457 1457 pull_request = relationship('PullRequest', lazy='joined')
1458 1458
1459 1459 @classmethod
1460 1460 def get_users(cls, revision=None, pull_request_id=None):
1461 1461 """
1462 1462 Returns user associated with this ChangesetComment. ie those
1463 1463 who actually commented
1464 1464
1465 1465 :param cls:
1466 1466 :param revision:
1467 1467 """
1468 1468 q = Session().query(User)\
1469 1469 .join(ChangesetComment.author)
1470 1470 if revision:
1471 1471 q = q.filter(cls.revision == revision)
1472 1472 elif pull_request_id:
1473 1473 q = q.filter(cls.pull_request_id == pull_request_id)
1474 1474 return q.all()
1475 1475
1476 1476
1477 1477 class ChangesetStatus(Base, BaseModel):
1478 1478 __tablename__ = 'changeset_statuses'
1479 1479 __table_args__ = (
1480 1480 Index('cs_revision_idx', 'revision'),
1481 1481 Index('cs_version_idx', 'version'),
1482 1482 UniqueConstraint('repo_id', 'revision', 'version'),
1483 1483 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1484 1484 'mysql_charset': 'utf8'}
1485 1485 )
1486 1486 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1487 1487 STATUS_APPROVED = 'approved'
1488 1488 STATUS_REJECTED = 'rejected'
1489 1489 STATUS_UNDER_REVIEW = 'under_review'
1490 1490
1491 1491 STATUSES = [
1492 1492 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1493 1493 (STATUS_APPROVED, _("Approved")),
1494 1494 (STATUS_REJECTED, _("Rejected")),
1495 1495 (STATUS_UNDER_REVIEW, _("Under Review")),
1496 1496 ]
1497 1497
1498 1498 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1499 1499 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1500 1500 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1501 1501 revision = Column('revision', String(40), nullable=False)
1502 1502 status = Column('status', String(128), nullable=False, default=DEFAULT)
1503 1503 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1504 1504 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1505 1505 version = Column('version', Integer(), nullable=False, default=0)
1506 1506 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1507 1507
1508 1508 author = relationship('User', lazy='joined')
1509 1509 repo = relationship('Repository')
1510 1510 comment = relationship('ChangesetComment', lazy='joined')
1511 1511 pull_request = relationship('PullRequest', lazy='joined')
1512 1512
1513 1513 def __unicode__(self):
1514 1514 return u"<%s('%s:%s')>" % (
1515 1515 self.__class__.__name__,
1516 1516 self.status, self.author
1517 1517 )
1518 1518
1519 1519 @classmethod
1520 1520 def get_status_lbl(cls, value):
1521 1521 return dict(cls.STATUSES).get(value)
1522 1522
1523 1523 @property
1524 1524 def status_lbl(self):
1525 1525 return ChangesetStatus.get_status_lbl(self.status)
1526 1526
1527 1527
1528 1528 class PullRequest(Base, BaseModel):
1529 1529 __tablename__ = 'pull_requests'
1530 1530 __table_args__ = (
1531 1531 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1532 1532 'mysql_charset': 'utf8'},
1533 1533 )
1534 1534
1535 1535 STATUS_NEW = u'new'
1536 1536 STATUS_OPEN = u'open'
1537 1537 STATUS_CLOSED = u'closed'
1538 1538
1539 1539 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1540 1540 title = Column('title', Unicode(256), nullable=True)
1541 1541 description = Column('description', UnicodeText(10240), nullable=True)
1542 1542 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1543 1543 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1544 1544 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1545 1545 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1546 1546 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1547 1547 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1548 1548 org_ref = Column('org_ref', Unicode(256), nullable=False)
1549 1549 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1550 1550 other_ref = Column('other_ref', Unicode(256), nullable=False)
1551 1551
1552 1552 @hybrid_property
1553 1553 def revisions(self):
1554 1554 return self._revisions.split(':')
1555 1555
1556 1556 @revisions.setter
1557 1557 def revisions(self, val):
1558 1558 self._revisions = ':'.join(val)
1559 1559
1560 1560 author = relationship('User', lazy='joined')
1561 1561 reviewers = relationship('PullRequestReviewers',
1562 1562 cascade="all, delete, delete-orphan")
1563 1563 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1564 1564 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1565 1565 statuses = relationship('ChangesetStatus')
1566 1566 comments = relationship('ChangesetComment',
1567 1567 cascade="all, delete, delete-orphan")
1568 1568
1569 1569 def is_closed(self):
1570 1570 return self.status == self.STATUS_CLOSED
1571 1571
1572 1572 def __json__(self):
1573 1573 return dict(
1574 1574 revisions=self.revisions
1575 1575 )
1576 1576
1577 1577
1578 1578 class PullRequestReviewers(Base, BaseModel):
1579 1579 __tablename__ = 'pull_request_reviewers'
1580 1580 __table_args__ = (
1581 1581 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1582 1582 'mysql_charset': 'utf8'},
1583 1583 )
1584 1584
1585 1585 def __init__(self, user=None, pull_request=None):
1586 1586 self.user = user
1587 1587 self.pull_request = pull_request
1588 1588
1589 1589 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1590 1590 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1591 1591 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1592 1592
1593 1593 user = relationship('User')
1594 1594 pull_request = relationship('PullRequest')
1595 1595
1596 1596
1597 1597 class Notification(Base, BaseModel):
1598 1598 __tablename__ = 'notifications'
1599 1599 __table_args__ = (
1600 1600 Index('notification_type_idx', 'type'),
1601 1601 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1602 1602 'mysql_charset': 'utf8'},
1603 1603 )
1604 1604
1605 1605 TYPE_CHANGESET_COMMENT = u'cs_comment'
1606 1606 TYPE_MESSAGE = u'message'
1607 1607 TYPE_MENTION = u'mention'
1608 1608 TYPE_REGISTRATION = u'registration'
1609 1609 TYPE_PULL_REQUEST = u'pull_request'
1610 1610 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1611 1611
1612 1612 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1613 1613 subject = Column('subject', Unicode(512), nullable=True)
1614 1614 body = Column('body', UnicodeText(50000), nullable=True)
1615 1615 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1616 1616 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1617 1617 type_ = Column('type', Unicode(256))
1618 1618
1619 1619 created_by_user = relationship('User')
1620 1620 notifications_to_users = relationship('UserNotification', lazy='joined',
1621 1621 cascade="all, delete, delete-orphan")
1622 1622
1623 1623 @property
1624 1624 def recipients(self):
1625 1625 return [x.user for x in UserNotification.query()\
1626 1626 .filter(UserNotification.notification == self)\
1627 1627 .order_by(UserNotification.user_id.asc()).all()]
1628 1628
1629 1629 @classmethod
1630 1630 def create(cls, created_by, subject, body, recipients, type_=None):
1631 1631 if type_ is None:
1632 1632 type_ = Notification.TYPE_MESSAGE
1633 1633
1634 1634 notification = cls()
1635 1635 notification.created_by_user = created_by
1636 1636 notification.subject = subject
1637 1637 notification.body = body
1638 1638 notification.type_ = type_
1639 1639 notification.created_on = datetime.datetime.now()
1640 1640
1641 1641 for u in recipients:
1642 1642 assoc = UserNotification()
1643 1643 assoc.notification = notification
1644 1644 u.notifications.append(assoc)
1645 1645 Session().add(notification)
1646 1646 return notification
1647 1647
1648 1648 @property
1649 1649 def description(self):
1650 1650 from rhodecode.model.notification import NotificationModel
1651 1651 return NotificationModel().make_description(self)
1652 1652
1653 1653
1654 1654 class UserNotification(Base, BaseModel):
1655 1655 __tablename__ = 'user_to_notification'
1656 1656 __table_args__ = (
1657 1657 UniqueConstraint('user_id', 'notification_id'),
1658 1658 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1659 1659 'mysql_charset': 'utf8'}
1660 1660 )
1661 1661 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1662 1662 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1663 1663 read = Column('read', Boolean, default=False)
1664 1664 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1665 1665
1666 1666 user = relationship('User', lazy="joined")
1667 1667 notification = relationship('Notification', lazy="joined",
1668 1668 order_by=lambda: Notification.created_on.desc(),)
1669 1669
1670 1670 def mark_as_read(self):
1671 1671 self.read = True
1672 1672 Session().add(self)
1673 1673
1674 1674
1675 1675 class DbMigrateVersion(Base, BaseModel):
1676 1676 __tablename__ = 'db_migrate_version'
1677 1677 __table_args__ = (
1678 1678 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1679 1679 'mysql_charset': 'utf8'},
1680 1680 )
1681 1681 repository_id = Column('repository_id', String(250), primary_key=True)
1682 1682 repository_path = Column('repository_path', Text)
1683 1683 version = Column('version', Integer)
@@ -1,317 +1,321 b''
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14 14
15 15
16 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 import logging
23 23
24 24 import formencode
25 25 from formencode import All
26 26
27 27 from pylons.i18n.translation import _
28 28
29 29 from rhodecode.model import validators as v
30 30 from rhodecode import BACKENDS
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34
35 35 class LoginForm(formencode.Schema):
36 36 allow_extra_fields = True
37 37 filter_extra_fields = True
38 38 username = v.UnicodeString(
39 39 strip=True,
40 40 min=1,
41 41 not_empty=True,
42 42 messages={
43 43 'empty': _(u'Please enter a login'),
44 44 'tooShort': _(u'Enter a value %(min)i characters long or more')}
45 45 )
46 46
47 47 password = v.UnicodeString(
48 48 strip=False,
49 49 min=3,
50 50 not_empty=True,
51 51 messages={
52 52 'empty': _(u'Please enter a password'),
53 53 'tooShort': _(u'Enter %(min)i characters or more')}
54 54 )
55 55
56 56 remember = v.StringBoolean(if_missing=False)
57 57
58 58 chained_validators = [v.ValidAuth()]
59 59
60 60
61 61 def UserForm(edit=False, old_data={}):
62 62 class _UserForm(formencode.Schema):
63 63 allow_extra_fields = True
64 64 filter_extra_fields = True
65 65 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
66 66 v.ValidUsername(edit, old_data))
67 67 if edit:
68 68 new_password = All(
69 69 v.ValidPassword(),
70 70 v.UnicodeString(strip=False, min=6, not_empty=False)
71 71 )
72 72 password_confirmation = All(
73 73 v.ValidPassword(),
74 74 v.UnicodeString(strip=False, min=6, not_empty=False),
75 75 )
76 76 admin = v.StringBoolean(if_missing=False)
77 77 else:
78 78 password = All(
79 79 v.ValidPassword(),
80 80 v.UnicodeString(strip=False, min=6, not_empty=True)
81 81 )
82 82 password_confirmation = All(
83 83 v.ValidPassword(),
84 84 v.UnicodeString(strip=False, min=6, not_empty=False)
85 85 )
86 86
87 87 active = v.StringBoolean(if_missing=False)
88 88 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
89 89 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
90 90 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
91 91
92 92 chained_validators = [v.ValidPasswordsMatch()]
93 93
94 94 return _UserForm
95 95
96 96
97 97 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
98 98 class _UsersGroupForm(formencode.Schema):
99 99 allow_extra_fields = True
100 100 filter_extra_fields = True
101 101
102 102 users_group_name = All(
103 103 v.UnicodeString(strip=True, min=1, not_empty=True),
104 104 v.ValidUsersGroup(edit, old_data)
105 105 )
106 106
107 107 users_group_active = v.StringBoolean(if_missing=False)
108 108
109 109 if edit:
110 110 users_group_members = v.OneOf(
111 111 available_members, hideList=False, testValueList=True,
112 112 if_missing=None, not_empty=False
113 113 )
114 114
115 115 return _UsersGroupForm
116 116
117 117
118 118 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
119 119 class _ReposGroupForm(formencode.Schema):
120 120 allow_extra_fields = True
121 121 filter_extra_fields = False
122 122
123 123 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
124 124 v.SlugifyName())
125 125 group_description = v.UnicodeString(strip=True, min=1,
126 126 not_empty=True)
127 127 group_parent_id = v.OneOf(available_groups, hideList=False,
128 128 testValueList=True,
129 129 if_missing=None, not_empty=False)
130 130
131 131 chained_validators = [v.ValidReposGroup(edit, old_data),
132 132 v.ValidPerms('group')]
133 133
134 134 return _ReposGroupForm
135 135
136 136
137 137 def RegisterForm(edit=False, old_data={}):
138 138 class _RegisterForm(formencode.Schema):
139 139 allow_extra_fields = True
140 140 filter_extra_fields = True
141 141 username = All(
142 142 v.ValidUsername(edit, old_data),
143 143 v.UnicodeString(strip=True, min=1, not_empty=True)
144 144 )
145 145 password = All(
146 146 v.ValidPassword(),
147 147 v.UnicodeString(strip=False, min=6, not_empty=True)
148 148 )
149 149 password_confirmation = All(
150 150 v.ValidPassword(),
151 151 v.UnicodeString(strip=False, min=6, not_empty=True)
152 152 )
153 153 active = v.StringBoolean(if_missing=False)
154 154 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
155 155 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
156 156 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
157 157
158 158 chained_validators = [v.ValidPasswordsMatch()]
159 159
160 160 return _RegisterForm
161 161
162 162
163 163 def PasswordResetForm():
164 164 class _PasswordResetForm(formencode.Schema):
165 165 allow_extra_fields = True
166 166 filter_extra_fields = True
167 167 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
168 168 return _PasswordResetForm
169 169
170 170
171 171 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
172 172 repo_groups=[], landing_revs=[]):
173 173 class _RepoForm(formencode.Schema):
174 174 allow_extra_fields = True
175 175 filter_extra_fields = False
176 176 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
177 177 v.SlugifyName())
178 178 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
179 179 repo_group = v.OneOf(repo_groups, hideList=True)
180 180 repo_type = v.OneOf(supported_backends)
181 181 description = v.UnicodeString(strip=True, min=1, not_empty=False)
182 182 private = v.StringBoolean(if_missing=False)
183 183 enable_statistics = v.StringBoolean(if_missing=False)
184 184 enable_downloads = v.StringBoolean(if_missing=False)
185 185 landing_rev = v.OneOf(landing_revs, hideList=True)
186 186
187 187 if edit:
188 188 #this is repo owner
189 189 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
190 190
191 191 chained_validators = [v.ValidCloneUri(),
192 192 v.ValidRepoName(edit, old_data),
193 193 v.ValidPerms()]
194 194 return _RepoForm
195 195
196 196
197 197 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
198 198 repo_groups=[], landing_revs=[]):
199 199 class _RepoForkForm(formencode.Schema):
200 200 allow_extra_fields = True
201 201 filter_extra_fields = False
202 202 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
203 203 v.SlugifyName())
204 204 repo_group = v.OneOf(repo_groups, hideList=True)
205 205 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
206 206 description = v.UnicodeString(strip=True, min=1, not_empty=True)
207 207 private = v.StringBoolean(if_missing=False)
208 208 copy_permissions = v.StringBoolean(if_missing=False)
209 209 update_after_clone = v.StringBoolean(if_missing=False)
210 210 fork_parent_id = v.UnicodeString()
211 211 chained_validators = [v.ValidForkName(edit, old_data)]
212 212 landing_rev = v.OneOf(landing_revs, hideList=True)
213 213
214 214 return _RepoForkForm
215 215
216 216
217 217 def RepoSettingsForm(edit=False, old_data={},
218 218 supported_backends=BACKENDS.keys(), repo_groups=[],
219 219 landing_revs=[]):
220 220 class _RepoForm(formencode.Schema):
221 221 allow_extra_fields = True
222 222 filter_extra_fields = False
223 223 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
224 224 v.SlugifyName())
225 225 description = v.UnicodeString(strip=True, min=1, not_empty=True)
226 226 repo_group = v.OneOf(repo_groups, hideList=True)
227 227 private = v.StringBoolean(if_missing=False)
228 228 landing_rev = v.OneOf(landing_revs, hideList=True)
229 229 chained_validators = [v.ValidRepoName(edit, old_data), v.ValidPerms(),
230 230 v.ValidSettings()]
231 231 return _RepoForm
232 232
233 233
234 234 def ApplicationSettingsForm():
235 235 class _ApplicationSettingsForm(formencode.Schema):
236 236 allow_extra_fields = True
237 237 filter_extra_fields = False
238 238 rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True)
239 239 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
240 240 rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
241 241
242 242 return _ApplicationSettingsForm
243 243
244 244
245 245 def ApplicationVisualisationForm():
246 246 class _ApplicationVisualisationForm(formencode.Schema):
247 247 allow_extra_fields = True
248 248 filter_extra_fields = False
249 249 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
250 250 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
251 251 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
252 252
253 253 return _ApplicationVisualisationForm
254 254
255 255
256 256 def ApplicationUiSettingsForm():
257 257 class _ApplicationUiSettingsForm(formencode.Schema):
258 258 allow_extra_fields = True
259 259 filter_extra_fields = False
260 260 web_push_ssl = v.StringBoolean(if_missing=False)
261 261 paths_root_path = All(
262 262 v.ValidPath(),
263 263 v.UnicodeString(strip=True, min=1, not_empty=True)
264 264 )
265 265 hooks_changegroup_update = v.StringBoolean(if_missing=False)
266 266 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
267 267 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
268 268 hooks_preoutgoing_pull_logger = v.StringBoolean(if_missing=False)
269 269
270 extensions_largefiles = v.StringBoolean(if_missing=False)
271 extensions_hgsubversion = v.StringBoolean(if_missing=False)
272 extensions_hggit = v.StringBoolean(if_missing=False)
273
270 274 return _ApplicationUiSettingsForm
271 275
272 276
273 277 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
274 278 class _DefaultPermissionsForm(formencode.Schema):
275 279 allow_extra_fields = True
276 280 filter_extra_fields = True
277 281 overwrite_default = v.StringBoolean(if_missing=False)
278 282 anonymous = v.StringBoolean(if_missing=False)
279 283 default_perm = v.OneOf(perms_choices)
280 284 default_register = v.OneOf(register_choices)
281 285 default_create = v.OneOf(create_choices)
282 286
283 287 return _DefaultPermissionsForm
284 288
285 289
286 290 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
287 291 tls_kind_choices):
288 292 class _LdapSettingsForm(formencode.Schema):
289 293 allow_extra_fields = True
290 294 filter_extra_fields = True
291 295 #pre_validators = [LdapLibValidator]
292 296 ldap_active = v.StringBoolean(if_missing=False)
293 297 ldap_host = v.UnicodeString(strip=True,)
294 298 ldap_port = v.Number(strip=True,)
295 299 ldap_tls_kind = v.OneOf(tls_kind_choices)
296 300 ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
297 301 ldap_dn_user = v.UnicodeString(strip=True,)
298 302 ldap_dn_pass = v.UnicodeString(strip=True,)
299 303 ldap_base_dn = v.UnicodeString(strip=True,)
300 304 ldap_filter = v.UnicodeString(strip=True,)
301 305 ldap_search_scope = v.OneOf(search_scope_choices)
302 306 ldap_attr_login = All(
303 307 v.AttrLoginValidator(),
304 308 v.UnicodeString(strip=True,)
305 309 )
306 310 ldap_attr_firstname = v.UnicodeString(strip=True,)
307 311 ldap_attr_lastname = v.UnicodeString(strip=True,)
308 312 ldap_attr_email = v.UnicodeString(strip=True,)
309 313
310 314 return _LdapSettingsForm
311 315
312 316
313 317 def UserExtraEmailForm():
314 318 class _UserExtraEmailForm(formencode.Schema):
315 319 email = All(v.UniqSystemEmail(), v.Email)
316 320
317 321 return _UserExtraEmailForm
@@ -1,305 +1,305 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.user_group
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 users groups model for RhodeCode
7 7
8 8 :created_on: Jan 25, 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
26 26 import os
27 27 import logging
28 28 import traceback
29 29 import shutil
30 30
31 31 from rhodecode.lib.utils2 import LazyProperty
32 32
33 33 from rhodecode.model import BaseModel
34 34 from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \
35 35 User, Permission, UsersGroupRepoGroupToPerm, UsersGroup
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 40 class ReposGroupModel(BaseModel):
41 41
42 42 cls = RepoGroup
43 43
44 44 def __get_users_group(self, users_group):
45 45 return self._get_instance(UsersGroup, users_group,
46 46 callback=UsersGroup.get_by_group_name)
47 47
48 48 def _get_repos_group(self, repos_group):
49 49 return self._get_instance(RepoGroup, repos_group,
50 50 callback=RepoGroup.get_by_group_name)
51 51
52 52 @LazyProperty
53 53 def repos_path(self):
54 54 """
55 55 Get's the repositories root path from database
56 56 """
57 57
58 q = RhodeCodeUi.get_by_key('/').one()
58 q = RhodeCodeUi.get_by_key('/')
59 59 return q.ui_value
60 60
61 61 def _create_default_perms(self, new_group):
62 62 # create default permission
63 63 repo_group_to_perm = UserRepoGroupToPerm()
64 64 default_perm = 'group.read'
65 65 for p in User.get_by_username('default').user_perms:
66 66 if p.permission.permission_name.startswith('group.'):
67 67 default_perm = p.permission.permission_name
68 68 break
69 69
70 70 repo_group_to_perm.permission_id = self.sa.query(Permission)\
71 71 .filter(Permission.permission_name == default_perm)\
72 72 .one().permission_id
73 73
74 74 repo_group_to_perm.group = new_group
75 75 repo_group_to_perm.user_id = User.get_by_username('default').user_id
76 76
77 77 self.sa.add(repo_group_to_perm)
78 78
79 79 def __create_group(self, group_name):
80 80 """
81 81 makes repositories group on filesystem
82 82
83 83 :param repo_name:
84 84 :param parent_id:
85 85 """
86 86
87 87 create_path = os.path.join(self.repos_path, group_name)
88 88 log.debug('creating new group in %s' % create_path)
89 89
90 90 if os.path.isdir(create_path):
91 91 raise Exception('That directory already exists !')
92 92
93 93 os.makedirs(create_path)
94 94
95 95 def __rename_group(self, old, new):
96 96 """
97 97 Renames a group on filesystem
98 98
99 99 :param group_name:
100 100 """
101 101
102 102 if old == new:
103 103 log.debug('skipping group rename')
104 104 return
105 105
106 106 log.debug('renaming repos group from %s to %s' % (old, new))
107 107
108 108 old_path = os.path.join(self.repos_path, old)
109 109 new_path = os.path.join(self.repos_path, new)
110 110
111 111 log.debug('renaming repos paths from %s to %s' % (old_path, new_path))
112 112
113 113 if os.path.isdir(new_path):
114 114 raise Exception('Was trying to rename to already '
115 115 'existing dir %s' % new_path)
116 116 shutil.move(old_path, new_path)
117 117
118 118 def __delete_group(self, group):
119 119 """
120 120 Deletes a group from a filesystem
121 121
122 122 :param group: instance of group from database
123 123 """
124 124 paths = group.full_path.split(RepoGroup.url_sep())
125 125 paths = os.sep.join(paths)
126 126
127 127 rm_path = os.path.join(self.repos_path, paths)
128 128 if os.path.isdir(rm_path):
129 129 # delete only if that path really exists
130 130 os.rmdir(rm_path)
131 131
132 132 def create(self, group_name, group_description, parent=None, just_db=False):
133 133 try:
134 134 new_repos_group = RepoGroup()
135 135 new_repos_group.group_description = group_description
136 136 new_repos_group.parent_group = self._get_repos_group(parent)
137 137 new_repos_group.group_name = new_repos_group.get_new_name(group_name)
138 138
139 139 self.sa.add(new_repos_group)
140 140 self._create_default_perms(new_repos_group)
141 141
142 142 if not just_db:
143 143 # we need to flush here, in order to check if database won't
144 144 # throw any exceptions, create filesystem dirs at the very end
145 145 self.sa.flush()
146 146 self.__create_group(new_repos_group.group_name)
147 147
148 148 return new_repos_group
149 149 except:
150 150 log.error(traceback.format_exc())
151 151 raise
152 152
153 153 def update(self, repos_group_id, form_data):
154 154
155 155 try:
156 156 repos_group = RepoGroup.get(repos_group_id)
157 157
158 158 # update permissions
159 159 for member, perm, member_type in form_data['perms_updates']:
160 160 if member_type == 'user':
161 161 # this updates also current one if found
162 162 ReposGroupModel().grant_user_permission(
163 163 repos_group=repos_group, user=member, perm=perm
164 164 )
165 165 else:
166 166 ReposGroupModel().grant_users_group_permission(
167 167 repos_group=repos_group, group_name=member, perm=perm
168 168 )
169 169 # set new permissions
170 170 for member, perm, member_type in form_data['perms_new']:
171 171 if member_type == 'user':
172 172 ReposGroupModel().grant_user_permission(
173 173 repos_group=repos_group, user=member, perm=perm
174 174 )
175 175 else:
176 176 ReposGroupModel().grant_users_group_permission(
177 177 repos_group=repos_group, group_name=member, perm=perm
178 178 )
179 179
180 180 old_path = repos_group.full_path
181 181
182 182 # change properties
183 183 repos_group.group_description = form_data['group_description']
184 184 repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
185 185 repos_group.group_parent_id = form_data['group_parent_id']
186 186 repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
187 187 new_path = repos_group.full_path
188 188
189 189 self.sa.add(repos_group)
190 190
191 191 # we need to get all repositories from this new group and
192 192 # rename them accordingly to new group path
193 193 for r in repos_group.repositories:
194 194 r.repo_name = r.get_new_name(r.just_name)
195 195 self.sa.add(r)
196 196
197 197 self.__rename_group(old_path, new_path)
198 198
199 199 return repos_group
200 200 except:
201 201 log.error(traceback.format_exc())
202 202 raise
203 203
204 204 def delete(self, repos_group):
205 205 repos_group = self._get_repos_group(repos_group)
206 206 try:
207 207 self.sa.delete(repos_group)
208 208 self.__delete_group(repos_group)
209 209 except:
210 210 log.exception('Error removing repos_group %s' % repos_group)
211 211 raise
212 212
213 213 def grant_user_permission(self, repos_group, user, perm):
214 214 """
215 215 Grant permission for user on given repositories group, or update
216 216 existing one if found
217 217
218 218 :param repos_group: Instance of ReposGroup, repositories_group_id,
219 219 or repositories_group name
220 220 :param user: Instance of User, user_id or username
221 221 :param perm: Instance of Permission, or permission_name
222 222 """
223 223
224 224 repos_group = self._get_repos_group(repos_group)
225 225 user = self._get_user(user)
226 226 permission = self._get_perm(perm)
227 227
228 228 # check if we have that permission already
229 229 obj = self.sa.query(UserRepoGroupToPerm)\
230 230 .filter(UserRepoGroupToPerm.user == user)\
231 231 .filter(UserRepoGroupToPerm.group == repos_group)\
232 232 .scalar()
233 233 if obj is None:
234 234 # create new !
235 235 obj = UserRepoGroupToPerm()
236 236 obj.group = repos_group
237 237 obj.user = user
238 238 obj.permission = permission
239 239 self.sa.add(obj)
240 240
241 241 def revoke_user_permission(self, repos_group, user):
242 242 """
243 243 Revoke permission for user on given repositories group
244 244
245 245 :param repos_group: Instance of ReposGroup, repositories_group_id,
246 246 or repositories_group name
247 247 :param user: Instance of User, user_id or username
248 248 """
249 249
250 250 repos_group = self._get_repos_group(repos_group)
251 251 user = self._get_user(user)
252 252
253 253 obj = self.sa.query(UserRepoGroupToPerm)\
254 254 .filter(UserRepoGroupToPerm.user == user)\
255 255 .filter(UserRepoGroupToPerm.group == repos_group)\
256 256 .one()
257 257 self.sa.delete(obj)
258 258
259 259 def grant_users_group_permission(self, repos_group, group_name, perm):
260 260 """
261 261 Grant permission for users group on given repositories group, or update
262 262 existing one if found
263 263
264 264 :param repos_group: Instance of ReposGroup, repositories_group_id,
265 265 or repositories_group name
266 266 :param group_name: Instance of UserGroup, users_group_id,
267 267 or users group name
268 268 :param perm: Instance of Permission, or permission_name
269 269 """
270 270 repos_group = self._get_repos_group(repos_group)
271 271 group_name = self.__get_users_group(group_name)
272 272 permission = self._get_perm(perm)
273 273
274 274 # check if we have that permission already
275 275 obj = self.sa.query(UsersGroupRepoGroupToPerm)\
276 276 .filter(UsersGroupRepoGroupToPerm.group == repos_group)\
277 277 .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\
278 278 .scalar()
279 279
280 280 if obj is None:
281 281 # create new
282 282 obj = UsersGroupRepoGroupToPerm()
283 283
284 284 obj.group = repos_group
285 285 obj.users_group = group_name
286 286 obj.permission = permission
287 287 self.sa.add(obj)
288 288
289 289 def revoke_users_group_permission(self, repos_group, group_name):
290 290 """
291 291 Revoke permission for users group on given repositories group
292 292
293 293 :param repos_group: Instance of ReposGroup, repositories_group_id,
294 294 or repositories_group name
295 295 :param group_name: Instance of UserGroup, users_group_id,
296 296 or users group name
297 297 """
298 298 repos_group = self._get_repos_group(repos_group)
299 299 group_name = self.__get_users_group(group_name)
300 300
301 301 obj = self.sa.query(UsersGroupRepoGroupToPerm)\
302 302 .filter(UsersGroupRepoGroupToPerm.group == repos_group)\
303 303 .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\
304 304 .one()
305 305 self.sa.delete(obj)
@@ -1,306 +1,327 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Settings administration')} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Settings')}
10 10 </%def>
11 11
12 12 <%def name="page_nav()">
13 13 ${self.menu('admin')}
14 14 </%def>
15 15
16 16 <%def name="main()">
17 17 <div class="box">
18 18 <!-- box / title -->
19 19 <div class="title">
20 20 ${self.breadcrumbs()}
21 21 </div>
22 22 <!-- end box / title -->
23 23
24 24 <h3>${_('Remap and rescan repositories')}</h3>
25 25 ${h.form(url('admin_setting', setting_id='mapping'),method='put')}
26 26 <div class="form">
27 27 <!-- fields -->
28 28
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label label-checkbox">
32 32 <label for="destroy">${_('rescan option')}:</label>
33 33 </div>
34 34 <div class="checkboxes">
35 35 <div class="checkbox">
36 36 ${h.checkbox('destroy',True)}
37 37 <label for="destroy">
38 38 <span class="tooltip" title="${h.tooltip(_('In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it.'))}">
39 39 ${_('destroy old data')}</span> </label>
40 40 </div>
41 41 <span class="help-block">${_('Rescan repositories location for new repositories. Also deletes obsolete if `destroy` flag is checked ')}</span>
42 42 </div>
43 43 </div>
44 44
45 45 <div class="buttons">
46 46 ${h.submit('rescan',_('Rescan repositories'),class_="ui-btn large")}
47 47 </div>
48 48 </div>
49 49 </div>
50 50 ${h.end_form()}
51 51
52 52 <h3>${_('Whoosh indexing')}</h3>
53 53 ${h.form(url('admin_setting', setting_id='whoosh'),method='put')}
54 54 <div class="form">
55 55 <!-- fields -->
56 56
57 57 <div class="fields">
58 58 <div class="field">
59 59 <div class="label label-checkbox">
60 60 <label>${_('index build option')}:</label>
61 61 </div>
62 62 <div class="checkboxes">
63 63 <div class="checkbox">
64 64 ${h.checkbox('full_index',True)}
65 65 <label for="full_index">${_('build from scratch')}</label>
66 66 </div>
67 67 </div>
68 68 </div>
69 69
70 70 <div class="buttons">
71 71 ${h.submit('reindex',_('Reindex'),class_="ui-btn large")}
72 72 </div>
73 73 </div>
74 74 </div>
75 75 ${h.end_form()}
76 76
77 77 <h3>${_('Global application settings')}</h3>
78 78 ${h.form(url('admin_setting', setting_id='global'),method='put')}
79 79 <div class="form">
80 80 <!-- fields -->
81 81
82 82 <div class="fields">
83 83
84 84 <div class="field">
85 85 <div class="label">
86 86 <label for="rhodecode_title">${_('Application name')}:</label>
87 87 </div>
88 88 <div class="input">
89 89 ${h.text('rhodecode_title',size=30)}
90 90 </div>
91 91 </div>
92 92
93 93 <div class="field">
94 94 <div class="label">
95 95 <label for="rhodecode_realm">${_('Realm text')}:</label>
96 96 </div>
97 97 <div class="input">
98 98 ${h.text('rhodecode_realm',size=30)}
99 99 </div>
100 100 </div>
101 101
102 102 <div class="field">
103 103 <div class="label">
104 104 <label for="rhodecode_ga_code">${_('GA code')}:</label>
105 105 </div>
106 106 <div class="input">
107 107 ${h.text('rhodecode_ga_code',size=30)}
108 108 </div>
109 109 </div>
110 110
111 111 <div class="buttons">
112 112 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
113 113 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
114 114 </div>
115 115 </div>
116 116 </div>
117 117 ${h.end_form()}
118 118
119 119 <h3>${_('Visualisation settings')}</h3>
120 120 ${h.form(url('admin_setting', setting_id='visual'),method='put')}
121 121 <div class="form">
122 122 <!-- fields -->
123 123
124 124 <div class="fields">
125 125
126 126 <div class="field">
127 127 <div class="label label-checkbox">
128 128 <label>${_('Icons')}:</label>
129 129 </div>
130 130 <div class="checkboxes">
131 131 <div class="checkbox">
132 132 ${h.checkbox('rhodecode_show_public_icon','True')}
133 133 <label for="rhodecode_show_public_icon">${_('Show public repo icon on repositories')}</label>
134 134 </div>
135 135 <div class="checkbox">
136 136 ${h.checkbox('rhodecode_show_private_icon','True')}
137 137 <label for="rhodecode_show_private_icon">${_('Show private repo icon on repositories')}</label>
138 138 </div>
139 139 </div>
140 140 </div>
141 141
142 142 <div class="field">
143 143 <div class="label label-checkbox">
144 144 <label>${_('Meta-Tagging')}:</label>
145 145 </div>
146 146 <div class="checkboxes">
147 147 <div class="checkbox">
148 148 ${h.checkbox('rhodecode_stylify_metatags','True')}
149 149 <label for="rhodecode_stylify_metatags">${_('Stylify recognised metatags:')}</label>
150 150 </div>
151 151 <div style="padding-left: 20px;">
152 152 <ul> <!-- Fix style here -->
153 153 <li>[featured] <span class="metatag" tag="featured">featured</span></li>
154 154 <li>[stale] <span class="metatag" tag="stale">stale</span></li>
155 155 <li>[dead] <span class="metatag" tag="dead">dead</span></li>
156 156 <li>[lang =&gt; lang] <span class="metatag" tag="lang" >lang</span></li>
157 157 <li>[license =&gt; License] <span class="metatag" tag="license"><a href="http://www.opensource.org/licenses/License" >License</a></span></li>
158 158 <li>[requires =&gt; Repo] <span class="metatag" tag="requires" >requires =&gt; <a href="#" >Repo</a></span></li>
159 159 <li>[recommends =&gt; Repo] <span class="metatag" tag="recommends" >recommends =&gt; <a href="#" >Repo</a></span></li>
160 160 <li>[see =&gt; URI] <span class="metatag" tag="see">see =&gt; <a href="#">URI</a> </span></li>
161 161 </ul>
162 162 </div>
163 163 </div>
164 164 </div>
165 165
166 166 <div class="buttons">
167 167 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
168 168 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
169 169 </div>
170 170
171 171 </div>
172 172 </div>
173 173 ${h.end_form()}
174 174
175 175
176 176 <h3>${_('VCS settings')}</h3>
177 177 ${h.form(url('admin_setting', setting_id='vcs'),method='put')}
178 178 <div class="form">
179 179 <!-- fields -->
180 180
181 181 <div class="fields">
182 182
183 183 <div class="field">
184 184 <div class="label label-checkbox">
185 185 <label>${_('Web')}:</label>
186 186 </div>
187 187 <div class="checkboxes">
188 188 <div class="checkbox">
189 189 ${h.checkbox('web_push_ssl','true')}
190 190 <label for="web_push_ssl">${_('require ssl for vcs operations')}</label>
191 191 </div>
192 192 <span class="help-block">${_('RhodeCode will require SSL for pushing or pulling. If SSL is missing it will return HTTP Error 406: Not Acceptable')}</span>
193 193 </div>
194 194 </div>
195 195
196 196 <div class="field">
197 197 <div class="label label-checkbox">
198 198 <label>${_('Hooks')}:</label>
199 199 </div>
200 200 <div class="checkboxes">
201 201 <div class="checkbox">
202 202 ${h.checkbox('hooks_changegroup_update','True')}
203 203 <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
204 204 </div>
205 205 <div class="checkbox">
206 206 ${h.checkbox('hooks_changegroup_repo_size','True')}
207 207 <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
208 208 </div>
209 209 <div class="checkbox">
210 210 ${h.checkbox('hooks_changegroup_push_logger','True')}
211 211 <label for="hooks_changegroup_push_logger">${_('Log user push commands')}</label>
212 212 </div>
213 213 <div class="checkbox">
214 214 ${h.checkbox('hooks_preoutgoing_pull_logger','True')}
215 215 <label for="hooks_preoutgoing_pull_logger">${_('Log user pull commands')}</label>
216 216 </div>
217 217 </div>
218 218 <div class="input" style="margin-top:10px">
219 219 ${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'),class_="ui-btn")}
220 220 </div>
221 221 </div>
222 <div class="field">
223 <div class="label label-checkbox">
224 <label>${_('Mercurial Extensions')}:</label>
225 </div>
226 <div class="checkboxes">
227 <div class="checkbox">
228 ${h.checkbox('extensions_largefiles','True')}
229 <label for="extensions_hgsubversion">${_('largefiles extensions')}</label>
230 </div>
231 <div class="checkbox">
232 ${h.checkbox('extensions_hgsubversion','True')}
233 <label for="extensions_hgsubversion">${_('hgsubversion extensions')}</label>
234 </div>
235 <span class="help-block">${_('Requires hgsubversion library installed. Allows clonning from svn remote locations')}</span>
236 ##<div class="checkbox">
237 ## ${h.checkbox('extensions_hggit','True')}
238 ## <label for="extensions_hggit">${_('hg-git extensions')}</label>
239 ##</div>
240 ##<span class="help-block">${_('Requires hg-git library installed. Allows clonning from git remote locations')}</span>
241 </div>
242 </div>
222 243 <div class="field">
223 244 <div class="label">
224 245 <label for="paths_root_path">${_('Repositories location')}:</label>
225 246 </div>
226 247 <div class="input">
227 248 ${h.text('paths_root_path',size=30,readonly="readonly")}
228 249 <span id="path_unlock" class="tooltip"
229 250 title="${h.tooltip(_('This a crucial application setting. If you are really sure you need to change this, you must restart application in order to make this setting take effect. Click this label to unlock.'))}">
230 251 ${_('unlock')}</span>
231 252 <span class="help-block">${_('Location where repositories are stored. After changing this value a restart, and rescan is required')}</span>
232 253 </div>
233 254 </div>
234 255
235 256 <div class="buttons">
236 257 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
237 258 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
238 259 </div>
239 260 </div>
240 261 </div>
241 262 ${h.end_form()}
242 263
243 264 <script type="text/javascript">
244 265 YAHOO.util.Event.onDOMReady(function(){
245 266 YAHOO.util.Event.addListener('path_unlock','click',function(){
246 267 YAHOO.util.Dom.get('paths_root_path').removeAttribute('readonly');
247 268 });
248 269 });
249 270 </script>
250 271
251 272 <h3>${_('Test Email')}</h3>
252 273 ${h.form(url('admin_setting', setting_id='email'),method='put')}
253 274 <div class="form">
254 275 <!-- fields -->
255 276
256 277 <div class="fields">
257 278 <div class="field">
258 279 <div class="label">
259 280 <label for="test_email">${_('Email to')}:</label>
260 281 </div>
261 282 <div class="input">
262 283 ${h.text('test_email',size=30)}
263 284 </div>
264 285 </div>
265 286
266 287 <div class="buttons">
267 288 ${h.submit('send',_('Send'),class_="ui-btn large")}
268 289 </div>
269 290 </div>
270 291 </div>
271 292 ${h.end_form()}
272 293
273 294 <h3>${_('System Info and Packages')}</h3>
274 295 <div class="form">
275 296 <div>
276 297 <h5 id="expand_modules" style="cursor: pointer">&darr; ${_('show')} &darr;</h5>
277 298 </div>
278 299 <div id="expand_modules_table" style="display:none">
279 300 <h5>Python - ${c.py_version}</h5>
280 301 <h5>System - ${c.platform}</h5>
281 302
282 303 <table class="table" style="margin:0px 0px 0px 20px">
283 304 <colgroup>
284 305 <col style="width:220px">
285 306 </colgroup>
286 307 <tbody>
287 308 %for key, value in c.modules:
288 309 <tr>
289 310 <th style="text-align: right;padding-right:5px;">${key}</th>
290 311 <td>${value}</td>
291 312 </tr>
292 313 %endfor
293 314 </tbody>
294 315 </table>
295 316 </div>
296 317 </div>
297 318
298 319 <script type="text/javascript">
299 320 YUE.on('expand_modules','click',function(e){
300 321 YUD.setStyle('expand_modules_table','display','');
301 322 YUD.setStyle('expand_modules','display','none');
302 323 })
303 324 </script>
304 325
305 326 </div>
306 327 </%def>
General Comments 0
You need to be logged in to leave comments. Login now