##// END OF EJS Templates
Added more advanced hook management into rhodecode admin settings
marcink -
r1460:b5034881 beta
parent child Browse files
Show More
@@ -0,0 +1,96
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
3
4 <%def name="title()">
5 ${_('Settings administration')} - ${c.rhodecode_name}
6 </%def>
7
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Settings')}
10 </%def>
11
12 <%def name="page_nav()">
13 ${self.menu('admin')}
14 </%def>
15
16 <%def name="main()">
17 <div class="box">
18 <!-- box / title -->
19 <div class="title">
20 ${self.breadcrumbs()}
21 </div>
22 <!-- end box / title -->
23
24 <h3>${_('Built in hooks - read only')}</h3>
25 <div class="form">
26 <div class="fields">
27 % for hook in c.hooks:
28 <div class="field">
29 <div class="label label">
30 <label for="${hook.ui_key}">${hook.ui_key}</label>
31 </div>
32 <div class="input" style="margin-left:280px">
33 ${h.text(hook.ui_key,hook.ui_value,size=60,readonly="readonly")}
34 </div>
35 </div>
36 % endfor
37 </div>
38 </div>
39
40 <h3>${_('Custom hooks')}</h3>
41 ${h.form(url('admin_setting', setting_id='hooks'),method='put')}
42 <div class="form">
43 <div class="fields">
44
45 % for hook in c.custom_hooks:
46 <div class="field" id="${'id%s' % hook.ui_id }">
47 <div class="label label">
48 <label for="${hook.ui_key}">${hook.ui_key}</label>
49 </div>
50 <div class="input" style="margin-left:280px">
51 ${h.hidden('hook_ui_key',hook.ui_key)}
52 ${h.hidden('hook_ui_value',hook.ui_value)}
53 ${h.text('hook_ui_value_new',hook.ui_value,size=60)}
54 <span class="delete_icon action_button"
55 onclick="ajaxActionHook(${hook.ui_id},'${'id%s' % hook.ui_id }')">
56 ${_('remove')}
57 </span>
58 </div>
59 </div>
60 % endfor
61
62 <div class="field">
63 <div class="input" style="margin-left:-180px;position: absolute;">
64 <div class="input">
65 ${h.text('new_hook_ui_key',size=30)}
66 </div>
67 </div>
68 <div class="input" style="margin-left:280px">
69 ${h.text('new_hook_ui_value',size=60)}
70 </div>
71 </div>
72 <div class="buttons" style="margin-left:280px">
73 ${h.submit('save','Save',class_="ui-button")}
74 </div>
75 </div>
76 </div>
77 ${h.end_form()}
78 </div>
79 <script type="text/javascript">
80 function ajaxActionHook(hook_id,field_id) {
81 var sUrl = "${h.url('admin_setting', setting_id='hooks')}";
82 var callback = {
83 success: function (o) {
84 var elem = YUD.get(""+field_id);
85 elem.parentNode.removeChild(elem);
86 },
87 failure: function (o) {
88 alert("${_('Failed to remove hook')}");
89 },
90 };
91 var postData = '_method=delete&hook_id=' + hook_id;
92 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
93 };
94 </script>
95
96 </%def>
@@ -1,365 +1,407
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 settings controller for rhodecode admin
7 7
8 8 :created_on: Jul 14, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29
30 30 from sqlalchemy import func
31 31 from formencode import htmlfill
32 32 from pylons import request, session, tmpl_context as c, url, config
33 33 from pylons.controllers.util import abort, redirect
34 34 from pylons.i18n.translation import _
35 35
36 36 from rhodecode.lib import helpers as h
37 37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
38 38 HasPermissionAnyDecorator, NotAnonymous
39 39 from rhodecode.lib.base import BaseController, render
40 40 from rhodecode.lib.celerylib import tasks, run_task
41 41 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
42 42 set_rhodecode_config, repo_name_slug
43 43 from rhodecode.model.db import RhodeCodeUi, Repository, Group, \
44 44 RhodeCodeSettings
45 45 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
46 46 ApplicationUiSettingsForm
47 47 from rhodecode.model.scm import ScmModel
48 48 from rhodecode.model.user import UserModel
49 49
50 50 log = logging.getLogger(__name__)
51 51
52 52
53 53 class SettingsController(BaseController):
54 54 """REST Controller styled on the Atom Publishing Protocol"""
55 55 # To properly map this controller, ensure your config/routing.py
56 56 # file has a resource setup:
57 57 # map.resource('setting', 'settings', controller='admin/settings',
58 58 # path_prefix='/admin', name_prefix='admin_')
59 59
60 60 @LoginRequired()
61 61 def __before__(self):
62 62 c.admin_user = session.get('admin_user')
63 63 c.admin_username = session.get('admin_username')
64 64 super(SettingsController, self).__before__()
65 65
66 66 @HasPermissionAllDecorator('hg.admin')
67 67 def index(self, format='html'):
68 68 """GET /admin/settings: All items in the collection"""
69 69 # url('admin_settings')
70 70
71 71 defaults = RhodeCodeSettings.get_app_settings()
72 72 defaults.update(self.get_hg_ui_settings())
73 73 return htmlfill.render(
74 74 render('admin/settings/settings.html'),
75 75 defaults=defaults,
76 76 encoding="UTF-8",
77 77 force_defaults=False
78 78 )
79 79
80 80 @HasPermissionAllDecorator('hg.admin')
81 81 def create(self):
82 82 """POST /admin/settings: Create a new item"""
83 83 # url('admin_settings')
84 84
85 85 @HasPermissionAllDecorator('hg.admin')
86 86 def new(self, format='html'):
87 87 """GET /admin/settings/new: Form to create a new item"""
88 88 # url('admin_new_setting')
89 89
90 90 @HasPermissionAllDecorator('hg.admin')
91 91 def update(self, setting_id):
92 92 """PUT /admin/settings/setting_id: Update an existing item"""
93 93 # Forms posted to this method should contain a hidden field:
94 94 # <input type="hidden" name="_method" value="PUT" />
95 95 # Or using helpers:
96 96 # h.form(url('admin_setting', setting_id=ID),
97 97 # method='put')
98 98 # url('admin_setting', setting_id=ID)
99 99 if setting_id == 'mapping':
100 100 rm_obsolete = request.POST.get('destroy', False)
101 101 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
102 102 initial = ScmModel().repo_scan()
103 103 log.debug('invalidating all repositories')
104 104 for repo_name in initial.keys():
105 105 invalidate_cache('get_repo_cached_%s' % repo_name)
106 106
107 107 added, removed = repo2db_mapper(initial, rm_obsolete)
108 108
109 109 h.flash(_('Repositories successfully'
110 110 ' rescanned added: %s,removed: %s') % (added, removed),
111 111 category='success')
112 112
113 113 if setting_id == 'whoosh':
114 114 repo_location = self.get_hg_ui_settings()['paths_root_path']
115 115 full_index = request.POST.get('full_index', False)
116 116 run_task(tasks.whoosh_index, repo_location, full_index)
117 117
118 118 h.flash(_('Whoosh reindex task scheduled'), category='success')
119 119 if setting_id == 'global':
120 120
121 121 application_form = ApplicationSettingsForm()()
122 122 try:
123 123 form_result = application_form.to_python(dict(request.POST))
124 124
125 125 try:
126 126 hgsettings1 = RhodeCodeSettings.get_by_name('title')
127 127 hgsettings1.app_settings_value = \
128 128 form_result['rhodecode_title']
129 129
130 130 hgsettings2 = RhodeCodeSettings.get_by_name('realm')
131 131 hgsettings2.app_settings_value = \
132 132 form_result['rhodecode_realm']
133 133
134 134 hgsettings3 = RhodeCodeSettings.get_by_name('ga_code')
135 135 hgsettings3.app_settings_value = \
136 136 form_result['rhodecode_ga_code']
137 137
138 138 self.sa.add(hgsettings1)
139 139 self.sa.add(hgsettings2)
140 140 self.sa.add(hgsettings3)
141 141 self.sa.commit()
142 142 set_rhodecode_config(config)
143 143 h.flash(_('Updated application settings'),
144 144 category='success')
145 145
146 146 except Exception:
147 147 log.error(traceback.format_exc())
148 148 h.flash(_('error occurred during updating '
149 149 'application settings'),
150 150 category='error')
151 151
152 152 self.sa.rollback()
153 153
154 154 except formencode.Invalid, errors:
155 155 return htmlfill.render(
156 156 render('admin/settings/settings.html'),
157 157 defaults=errors.value,
158 158 errors=errors.error_dict or {},
159 159 prefix_error=False,
160 160 encoding="UTF-8")
161 161
162 162 if setting_id == 'mercurial':
163 163 application_form = ApplicationUiSettingsForm()()
164 164 try:
165 165 form_result = application_form.to_python(dict(request.POST))
166 166
167 167 try:
168 168
169 169 hgsettings1 = self.sa.query(RhodeCodeUi)\
170 170 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
171 171 hgsettings1.ui_value = form_result['web_push_ssl']
172 172
173 173 hgsettings2 = self.sa.query(RhodeCodeUi)\
174 174 .filter(RhodeCodeUi.ui_key == '/').one()
175 175 hgsettings2.ui_value = form_result['paths_root_path']
176 176
177 177 #HOOKS
178 178 hgsettings3 = self.sa.query(RhodeCodeUi)\
179 179 .filter(RhodeCodeUi.ui_key == 'changegroup.update').one()
180 180 hgsettings3.ui_active = \
181 181 bool(form_result['hooks_changegroup_update'])
182 182
183 183 hgsettings4 = self.sa.query(RhodeCodeUi)\
184 184 .filter(RhodeCodeUi.ui_key ==
185 185 'changegroup.repo_size').one()
186 186 hgsettings4.ui_active = \
187 187 bool(form_result['hooks_changegroup_repo_size'])
188 188
189 189 hgsettings5 = self.sa.query(RhodeCodeUi)\
190 190 .filter(RhodeCodeUi.ui_key ==
191 191 'pretxnchangegroup.push_logger').one()
192 192 hgsettings5.ui_active = \
193 193 bool(form_result['hooks_pretxnchangegroup'
194 194 '_push_logger'])
195 195
196 196 hgsettings6 = self.sa.query(RhodeCodeUi)\
197 197 .filter(RhodeCodeUi.ui_key ==
198 198 'preoutgoing.pull_logger').one()
199 199 hgsettings6.ui_active = \
200 200 bool(form_result['hooks_preoutgoing_pull_logger'])
201 201
202 202 self.sa.add(hgsettings1)
203 203 self.sa.add(hgsettings2)
204 204 self.sa.add(hgsettings3)
205 205 self.sa.add(hgsettings4)
206 206 self.sa.add(hgsettings5)
207 207 self.sa.add(hgsettings6)
208 208 self.sa.commit()
209 209
210 210 h.flash(_('Updated mercurial settings'),
211 211 category='success')
212 212
213 213 except:
214 214 log.error(traceback.format_exc())
215 215 h.flash(_('error occurred during updating '
216 216 'application settings'), category='error')
217 217
218 218 self.sa.rollback()
219 219
220 220 except formencode.Invalid, errors:
221 221 return htmlfill.render(
222 222 render('admin/settings/settings.html'),
223 223 defaults=errors.value,
224 224 errors=errors.error_dict or {},
225 225 prefix_error=False,
226 226 encoding="UTF-8")
227 227
228
229 if setting_id == 'hooks':
230 ui_key = request.POST.get('new_hook_ui_key')
231 ui_value = request.POST.get('new_hook_ui_value')
232 try:
233
234 if ui_value and ui_key:
235 RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
236 h.flash(_('Added new hook'),
237 category='success')
238
239 # check for edits
240 update = False
241 _d = request.POST.dict_of_lists()
242 for k, v in zip(_d.get('hook_ui_key',[]), _d.get('hook_ui_value_new',[])):
243 RhodeCodeUi.create_or_update_hook(k, v)
244 update = True
245
246 if update:
247 h.flash(_('Updated hooks'), category='success')
248
249 except:
250 log.error(traceback.format_exc())
251 h.flash(_('error occurred during hook creation'),
252 category='error')
253
254 return redirect(url('admin_edit_setting', setting_id='hooks'))
255
228 256 return redirect(url('admin_settings'))
229 257
230 258 @HasPermissionAllDecorator('hg.admin')
231 259 def delete(self, setting_id):
232 260 """DELETE /admin/settings/setting_id: Delete an existing item"""
233 261 # Forms posted to this method should contain a hidden field:
234 262 # <input type="hidden" name="_method" value="DELETE" />
235 263 # Or using helpers:
236 264 # h.form(url('admin_setting', setting_id=ID),
237 265 # method='delete')
238 266 # url('admin_setting', setting_id=ID)
239
267 if setting_id == 'hooks':
268 hook_id = request.POST.get('hook_id')
269 RhodeCodeUi.delete(hook_id)
270
271
240 272 @HasPermissionAllDecorator('hg.admin')
241 273 def show(self, setting_id, format='html'):
242 274 """
243 275 GET /admin/settings/setting_id: Show a specific item"""
244 276 # url('admin_setting', setting_id=ID)
245 277
246 278 @HasPermissionAllDecorator('hg.admin')
247 279 def edit(self, setting_id, format='html'):
248 280 """
249 281 GET /admin/settings/setting_id/edit: Form to
250 282 edit an existing item"""
251 283 # url('admin_edit_setting', setting_id=ID)
252 284
285 c.hooks = RhodeCodeUi.get_builtin_hooks()
286 c.custom_hooks = RhodeCodeUi.get_custom_hooks()
287
288 return htmlfill.render(
289 render('admin/settings/hooks.html'),
290 defaults={},
291 encoding="UTF-8",
292 force_defaults=False
293 )
294
253 295 @NotAnonymous()
254 296 def my_account(self):
255 297 """
256 298 GET /_admin/my_account Displays info about my account
257 299 """
258 300 # url('admin_settings_my_account')
259 301
260 302 c.user = UserModel().get(self.rhodecode_user.user_id, cache=False)
261 303 all_repos = self.sa.query(Repository)\
262 304 .filter(Repository.user_id == c.user.user_id)\
263 305 .order_by(func.lower(Repository.repo_name)).all()
264 306
265 307 c.user_repos = ScmModel().get_repos(all_repos)
266 308
267 309 if c.user.username == 'default':
268 310 h.flash(_("You can't edit this user since it's"
269 311 " crucial for entire application"), category='warning')
270 312 return redirect(url('users'))
271 313
272 314 defaults = c.user.get_dict()
273 315 return htmlfill.render(
274 316 render('admin/users/user_edit_my_account.html'),
275 317 defaults=defaults,
276 318 encoding="UTF-8",
277 319 force_defaults=False
278 320 )
279 321
280 322 def my_account_update(self):
281 323 """PUT /_admin/my_account_update: Update an existing item"""
282 324 # Forms posted to this method should contain a hidden field:
283 325 # <input type="hidden" name="_method" value="PUT" />
284 326 # Or using helpers:
285 327 # h.form(url('admin_settings_my_account_update'),
286 328 # method='put')
287 329 # url('admin_settings_my_account_update', id=ID)
288 330 user_model = UserModel()
289 331 uid = self.rhodecode_user.user_id
290 332 _form = UserForm(edit=True,
291 333 old_data={'user_id': uid,
292 334 'email': self.rhodecode_user.email})()
293 335 form_result = {}
294 336 try:
295 337 form_result = _form.to_python(dict(request.POST))
296 338 user_model.update_my_account(uid, form_result)
297 339 h.flash(_('Your account was updated successfully'),
298 340 category='success')
299 341
300 342 except formencode.Invalid, errors:
301 343 c.user = user_model.get(self.rhodecode_user.user_id, cache=False)
302 344 c.user = UserModel().get(self.rhodecode_user.user_id, cache=False)
303 345 all_repos = self.sa.query(Repository)\
304 346 .filter(Repository.user_id == c.user.user_id)\
305 347 .order_by(func.lower(Repository.repo_name))\
306 348 .all()
307 349 c.user_repos = ScmModel().get_repos(all_repos)
308 350
309 351 return htmlfill.render(
310 352 render('admin/users/user_edit_my_account.html'),
311 353 defaults=errors.value,
312 354 errors=errors.error_dict or {},
313 355 prefix_error=False,
314 356 encoding="UTF-8")
315 357 except Exception:
316 358 log.error(traceback.format_exc())
317 359 h.flash(_('error occurred during update of user %s') \
318 360 % form_result.get('username'), category='error')
319 361
320 362 return redirect(url('my_account'))
321 363
322 364 @NotAnonymous()
323 365 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
324 366 def create_repository(self):
325 367 """GET /_admin/create_repository: Form to create a new item"""
326 368
327 369 c.repo_groups = [('', '')]
328 370 parents_link = lambda k: h.literal('&raquo;'.join(
329 371 map(lambda k: k.group_name,
330 372 k.parents + [k])
331 373 )
332 374 )
333 375
334 376 c.repo_groups.extend([(x.group_id, parents_link(x)) for \
335 377 x in self.sa.query(Group).all()])
336 378 c.repo_groups = sorted(c.repo_groups,
337 379 key=lambda t: t[1].split('&raquo;')[0])
338 380 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
339 381
340 382 new_repo = request.GET.get('repo', '')
341 383 c.new_repo = repo_name_slug(new_repo)
342 384
343 385 return render('admin/repos/repo_add_create_repository.html')
344 386
345 387 def get_hg_ui_settings(self):
346 388 ret = self.sa.query(RhodeCodeUi).all()
347 389
348 390 if not ret:
349 391 raise Exception('Could not get application ui settings !')
350 392 settings = {}
351 393 for each in ret:
352 394 k = each.ui_key
353 395 v = each.ui_value
354 396 if k == '/':
355 397 k = 'root_path'
356 398
357 399 if k.find('.') != -1:
358 400 k = k.replace('.', '_')
359 401
360 402 if each.ui_section == 'hooks':
361 403 v = each.ui_active
362 404
363 405 settings[each.ui_section + '_' + k] = v
364 406
365 407 return settings
@@ -1,512 +1,512
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) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
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.lib.auth import get_crypt_password, generate_api_key
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 RhodeCodeSettings, UserToPerm, DbMigrateVersion
41 41
42 42 from sqlalchemy.engine import create_engine
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46
47 47 class DbManage(object):
48 48 def __init__(self, log_sql, dbconf, root, tests=False):
49 49 self.dbname = dbconf.split('/')[-1]
50 50 self.tests = tests
51 51 self.root = root
52 52 self.dburi = dbconf
53 53 self.log_sql = log_sql
54 54 self.db_exists = False
55 55 self.init_db()
56 56
57 57 def init_db(self):
58 58 engine = create_engine(self.dburi, echo=self.log_sql)
59 59 init_model(engine)
60 60 self.sa = meta.Session()
61 61
62 62 def create_tables(self, override=False):
63 63 """Create a auth database
64 64 """
65 65
66 66 log.info("Any existing database is going to be destroyed")
67 67 if self.tests:
68 68 destroy = True
69 69 else:
70 70 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
71 71 if not destroy:
72 72 sys.exit()
73 73 if destroy:
74 74 meta.Base.metadata.drop_all()
75 75
76 76 checkfirst = not override
77 77 meta.Base.metadata.create_all(checkfirst=checkfirst)
78 78 log.info('Created tables for %s', self.dbname)
79 79
80 80 def set_db_version(self):
81 81 try:
82 82 ver = DbMigrateVersion()
83 83 ver.version = __dbversion__
84 84 ver.repository_id = 'rhodecode_db_migrations'
85 85 ver.repository_path = 'versions'
86 86 self.sa.add(ver)
87 87 self.sa.commit()
88 88 except:
89 89 self.sa.rollback()
90 90 raise
91 91 log.info('db version set to: %s', __dbversion__)
92 92
93 93 def upgrade(self):
94 94 """Upgrades given database schema to given revision following
95 95 all needed steps, to perform the upgrade
96 96
97 97 """
98 98
99 99 from rhodecode.lib.dbmigrate.migrate.versioning import api
100 100 from rhodecode.lib.dbmigrate.migrate.exceptions import \
101 101 DatabaseNotControlledError
102 102
103 103 upgrade = ask_ok('You are about to perform database upgrade, make '
104 104 'sure You backed up your database before. '
105 105 'Continue ? [y/n]')
106 106 if not upgrade:
107 107 sys.exit('Nothing done')
108 108
109 109 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
110 110 'rhodecode/lib/dbmigrate')
111 111 db_uri = self.dburi
112 112
113 113 try:
114 114 curr_version = api.db_version(db_uri, repository_path)
115 115 msg = ('Found current database under version'
116 116 ' control with version %s' % curr_version)
117 117
118 118 except (RuntimeError, DatabaseNotControlledError):
119 119 curr_version = 1
120 120 msg = ('Current database is not under version control. Setting'
121 121 ' as version %s' % curr_version)
122 122 api.version_control(db_uri, repository_path, curr_version)
123 123
124 124 print (msg)
125 125
126 126 if curr_version == __dbversion__:
127 127 sys.exit('This database is already at the newest version')
128 128
129 129 #======================================================================
130 130 # UPGRADE STEPS
131 131 #======================================================================
132 132 class UpgradeSteps(object):
133 133 """Those steps follow schema versions so for example schema
134 134 for example schema with seq 002 == step_2 and so on.
135 135 """
136 136
137 137 def __init__(self, klass):
138 138 self.klass = klass
139 139
140 140 def step_0(self):
141 141 #step 0 is the schema upgrade, and than follow proper upgrades
142 142 print ('attempting to do database upgrade to version %s' \
143 143 % __dbversion__)
144 144 api.upgrade(db_uri, repository_path, __dbversion__)
145 145 print ('Schema upgrade completed')
146 146
147 147 def step_1(self):
148 148 pass
149 149
150 150 def step_2(self):
151 151 print ('Patching repo paths for newer version of RhodeCode')
152 152 self.klass.fix_repo_paths()
153 153
154 154 print ('Patching default user of RhodeCode')
155 155 self.klass.fix_default_user()
156 156
157 157 log.info('Changing ui settings')
158 158 self.klass.create_ui_settings()
159 159
160 160 def step_3(self):
161 161 print ('Adding additional settings into RhodeCode db')
162 162 self.klass.fix_settings()
163 163
164 164 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
165 165
166 166 #CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
167 167 for step in upgrade_steps:
168 168 print ('performing upgrade step %s' % step)
169 169 callable = getattr(UpgradeSteps(self), 'step_%s' % step)()
170 170
171 171 def fix_repo_paths(self):
172 172 """Fixes a old rhodecode version path into new one without a '*'
173 173 """
174 174
175 175 paths = self.sa.query(RhodeCodeUi)\
176 176 .filter(RhodeCodeUi.ui_key == '/')\
177 177 .scalar()
178 178
179 179 paths.ui_value = paths.ui_value.replace('*', '')
180 180
181 181 try:
182 182 self.sa.add(paths)
183 183 self.sa.commit()
184 184 except:
185 185 self.sa.rollback()
186 186 raise
187 187
188 188 def fix_default_user(self):
189 189 """Fixes a old default user with some 'nicer' default values,
190 190 used mostly for anonymous access
191 191 """
192 192 def_user = self.sa.query(User)\
193 193 .filter(User.username == 'default')\
194 194 .one()
195 195
196 196 def_user.name = 'Anonymous'
197 197 def_user.lastname = 'User'
198 198 def_user.email = 'anonymous@rhodecode.org'
199 199
200 200 try:
201 201 self.sa.add(def_user)
202 202 self.sa.commit()
203 203 except:
204 204 self.sa.rollback()
205 205 raise
206 206
207 207 def fix_settings(self):
208 208 """Fixes rhodecode settings adds ga_code key for google analytics
209 209 """
210 210
211 211 hgsettings3 = RhodeCodeSettings('ga_code', '')
212 212
213 213 try:
214 214 self.sa.add(hgsettings3)
215 215 self.sa.commit()
216 216 except:
217 217 self.sa.rollback()
218 218 raise
219 219
220 220 def admin_prompt(self, second=False):
221 221 if not self.tests:
222 222 import getpass
223 223
224 224 def get_password():
225 225 password = getpass.getpass('Specify admin password '
226 226 '(min 6 chars):')
227 227 confirm = getpass.getpass('Confirm password:')
228 228
229 229 if password != confirm:
230 230 log.error('passwords mismatch')
231 231 return False
232 232 if len(password) < 6:
233 233 log.error('password is to short use at least 6 characters')
234 234 return False
235 235
236 236 return password
237 237
238 238 username = raw_input('Specify admin username:')
239 239
240 240 password = get_password()
241 241 if not password:
242 242 #second try
243 243 password = get_password()
244 244 if not password:
245 245 sys.exit()
246 246
247 247 email = raw_input('Specify admin email:')
248 248 self.create_user(username, password, email, True)
249 249 else:
250 250 log.info('creating admin and regular test users')
251 251 self.create_user('test_admin', 'test12',
252 252 'test_admin@mail.com', True)
253 253 self.create_user('test_regular', 'test12',
254 254 'test_regular@mail.com', False)
255 255 self.create_user('test_regular2', 'test12',
256 256 'test_regular2@mail.com', False)
257 257
258 258 def create_ui_settings(self):
259 259 """Creates ui settings, fills out hooks
260 260 and disables dotencode
261 261
262 262 """
263 263 #HOOKS
264 hooks1_key = 'changegroup.update'
264 hooks1_key = RhodeCodeUi.HOOK_UPDATE
265 265 hooks1_ = self.sa.query(RhodeCodeUi)\
266 266 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
267 267
268 268 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
269 269 hooks1.ui_section = 'hooks'
270 270 hooks1.ui_key = hooks1_key
271 271 hooks1.ui_value = 'hg update >&2'
272 272 hooks1.ui_active = False
273 273
274 hooks2_key = 'changegroup.repo_size'
274 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
275 275 hooks2_ = self.sa.query(RhodeCodeUi)\
276 276 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
277 277
278 278 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
279 279 hooks2.ui_section = 'hooks'
280 280 hooks2.ui_key = hooks2_key
281 281 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
282 282
283 283 hooks3 = RhodeCodeUi()
284 284 hooks3.ui_section = 'hooks'
285 hooks3.ui_key = 'pretxnchangegroup.push_logger'
285 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
286 286 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
287 287
288 288 hooks4 = RhodeCodeUi()
289 289 hooks4.ui_section = 'hooks'
290 hooks4.ui_key = 'preoutgoing.pull_logger'
290 hooks4.ui_key = RhodeCodeUi.HOOK_PULL
291 291 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
292 292
293 293 #For mercurial 1.7 set backward comapatibility with format
294 294 dotencode_disable = RhodeCodeUi()
295 295 dotencode_disable.ui_section = 'format'
296 296 dotencode_disable.ui_key = 'dotencode'
297 297 dotencode_disable.ui_value = 'false'
298 298
299 299 try:
300 300 self.sa.add(hooks1)
301 301 self.sa.add(hooks2)
302 302 self.sa.add(hooks3)
303 303 self.sa.add(hooks4)
304 304 self.sa.add(dotencode_disable)
305 305 self.sa.commit()
306 306 except:
307 307 self.sa.rollback()
308 308 raise
309 309
310 310 def create_ldap_options(self):
311 311 """Creates ldap settings"""
312 312
313 313 try:
314 314 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
315 315 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
316 316 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
317 317 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
318 318 ('ldap_filter', ''), ('ldap_search_scope', ''),
319 319 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
320 320 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
321 321
322 322 setting = RhodeCodeSettings(k, v)
323 323 self.sa.add(setting)
324 324 self.sa.commit()
325 325 except:
326 326 self.sa.rollback()
327 327 raise
328 328
329 329 def config_prompt(self, test_repo_path='', retries=3):
330 330 if retries == 3:
331 331 log.info('Setting up repositories config')
332 332
333 333 if not self.tests and not test_repo_path:
334 334 path = raw_input('Specify valid full path to your repositories'
335 335 ' you can change this later in application settings:')
336 336 else:
337 337 path = test_repo_path
338 338 path_ok = True
339 339
340 340 #check proper dir
341 341 if not os.path.isdir(path):
342 342 path_ok = False
343 343 log.error('Given path %s is not a valid directory', path)
344 344
345 345 #check write access
346 346 if not os.access(path, os.W_OK) and path_ok:
347 347 path_ok = False
348 348 log.error('No write permission to given path %s', path)
349 349
350 350
351 351 if retries == 0:
352 352 sys.exit('max retries reached')
353 353 if path_ok is False:
354 354 retries -= 1
355 355 return self.config_prompt(test_repo_path, retries)
356 356
357 357 return path
358 358
359 359 def create_settings(self, path):
360 360
361 361 self.create_ui_settings()
362 362
363 363 #HG UI OPTIONS
364 364 web1 = RhodeCodeUi()
365 365 web1.ui_section = 'web'
366 366 web1.ui_key = 'push_ssl'
367 367 web1.ui_value = 'false'
368 368
369 369 web2 = RhodeCodeUi()
370 370 web2.ui_section = 'web'
371 371 web2.ui_key = 'allow_archive'
372 372 web2.ui_value = 'gz zip bz2'
373 373
374 374 web3 = RhodeCodeUi()
375 375 web3.ui_section = 'web'
376 376 web3.ui_key = 'allow_push'
377 377 web3.ui_value = '*'
378 378
379 379 web4 = RhodeCodeUi()
380 380 web4.ui_section = 'web'
381 381 web4.ui_key = 'baseurl'
382 382 web4.ui_value = '/'
383 383
384 384 paths = RhodeCodeUi()
385 385 paths.ui_section = 'paths'
386 386 paths.ui_key = '/'
387 387 paths.ui_value = path
388 388
389 389 hgsettings1 = RhodeCodeSettings('realm', 'RhodeCode authentication')
390 390 hgsettings2 = RhodeCodeSettings('title', 'RhodeCode')
391 391 hgsettings3 = RhodeCodeSettings('ga_code', '')
392 392
393 393 try:
394 394 self.sa.add(web1)
395 395 self.sa.add(web2)
396 396 self.sa.add(web3)
397 397 self.sa.add(web4)
398 398 self.sa.add(paths)
399 399 self.sa.add(hgsettings1)
400 400 self.sa.add(hgsettings2)
401 401 self.sa.add(hgsettings3)
402 402
403 403 self.sa.commit()
404 404 except:
405 405 self.sa.rollback()
406 406 raise
407 407
408 408 self.create_ldap_options()
409 409
410 410 log.info('created ui config')
411 411
412 412 def create_user(self, username, password, email='', admin=False):
413 413 log.info('creating administrator user %s', username)
414 414 new_user = User()
415 415 new_user.username = username
416 416 new_user.password = get_crypt_password(password)
417 417 new_user.api_key = generate_api_key(username)
418 418 new_user.name = 'RhodeCode'
419 419 new_user.lastname = 'Admin'
420 420 new_user.email = email
421 421 new_user.admin = admin
422 422 new_user.active = True
423 423
424 424 try:
425 425 self.sa.add(new_user)
426 426 self.sa.commit()
427 427 except:
428 428 self.sa.rollback()
429 429 raise
430 430
431 431 def create_default_user(self):
432 432 log.info('creating default user')
433 433 #create default user for handling default permissions.
434 434 def_user = User()
435 435 def_user.username = 'default'
436 436 def_user.password = get_crypt_password(str(uuid.uuid1())[:8])
437 437 def_user.api_key = generate_api_key('default')
438 438 def_user.name = 'Anonymous'
439 439 def_user.lastname = 'User'
440 440 def_user.email = 'anonymous@rhodecode.org'
441 441 def_user.admin = False
442 442 def_user.active = False
443 443 try:
444 444 self.sa.add(def_user)
445 445 self.sa.commit()
446 446 except:
447 447 self.sa.rollback()
448 448 raise
449 449
450 450 def create_permissions(self):
451 451 #module.(access|create|change|delete)_[name]
452 452 #module.(read|write|owner)
453 453 perms = [('repository.none', 'Repository no access'),
454 454 ('repository.read', 'Repository read access'),
455 455 ('repository.write', 'Repository write access'),
456 456 ('repository.admin', 'Repository admin access'),
457 457 ('hg.admin', 'Hg Administrator'),
458 458 ('hg.create.repository', 'Repository create'),
459 459 ('hg.create.none', 'Repository creation disabled'),
460 460 ('hg.register.none', 'Register disabled'),
461 461 ('hg.register.manual_activate', 'Register new user with '
462 462 'RhodeCode without manual'
463 463 'activation'),
464 464
465 465 ('hg.register.auto_activate', 'Register new user with '
466 466 'RhodeCode without auto '
467 467 'activation'),
468 468 ]
469 469
470 470 for p in perms:
471 471 new_perm = Permission()
472 472 new_perm.permission_name = p[0]
473 473 new_perm.permission_longname = p[1]
474 474 try:
475 475 self.sa.add(new_perm)
476 476 self.sa.commit()
477 477 except:
478 478 self.sa.rollback()
479 479 raise
480 480
481 481 def populate_default_permissions(self):
482 482 log.info('creating default user permissions')
483 483
484 484 default_user = self.sa.query(User)\
485 485 .filter(User.username == 'default').scalar()
486 486
487 487 reg_perm = UserToPerm()
488 488 reg_perm.user = default_user
489 489 reg_perm.permission = self.sa.query(Permission)\
490 490 .filter(Permission.permission_name == 'hg.register.manual_activate')\
491 491 .scalar()
492 492
493 493 create_repo_perm = UserToPerm()
494 494 create_repo_perm.user = default_user
495 495 create_repo_perm.permission = self.sa.query(Permission)\
496 496 .filter(Permission.permission_name == 'hg.create.repository')\
497 497 .scalar()
498 498
499 499 default_repo_perm = UserToPerm()
500 500 default_repo_perm.user = default_user
501 501 default_repo_perm.permission = self.sa.query(Permission)\
502 502 .filter(Permission.permission_name == 'repository.read')\
503 503 .scalar()
504 504
505 505 try:
506 506 self.sa.add(reg_perm)
507 507 self.sa.add(create_repo_perm)
508 508 self.sa.add(default_repo_perm)
509 509 self.sa.commit()
510 510 except:
511 511 self.sa.rollback()
512 512 raise
@@ -1,875 +1,916
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 from datetime import date
31 31
32 32 from sqlalchemy import *
33 33 from sqlalchemy.exc import DatabaseError
34 34 from sqlalchemy.orm import relationship, backref, joinedload, class_mapper
35 35 from sqlalchemy.orm.interfaces import MapperExtension
36 36
37 37 from beaker.cache import cache_region, region_invalidate
38 38
39 39 from vcs import get_backend
40 40 from vcs.utils.helpers import get_scm
41 41 from vcs.exceptions import RepositoryError, VCSError
42 42 from vcs.utils.lazy import LazyProperty
43 43 from vcs.nodes import FileNode
44 44
45 45 from rhodecode.lib.exceptions import UsersGroupsAssignedException
46 46 from rhodecode.lib import str2bool, json, safe_str
47 47 from rhodecode.model.meta import Base, Session
48 48 from rhodecode.model.caching_query import FromCache
49 49
50 50 log = logging.getLogger(__name__)
51 51
52 52 #==============================================================================
53 53 # BASE CLASSES
54 54 #==============================================================================
55 55
56 56 class ModelSerializer(json.JSONEncoder):
57 57 """
58 58 Simple Serializer for JSON,
59 59
60 60 usage::
61 61
62 62 to make object customized for serialization implement a __json__
63 63 method that will return a dict for serialization into json
64 64
65 65 example::
66 66
67 67 class Task(object):
68 68
69 69 def __init__(self, name, value):
70 70 self.name = name
71 71 self.value = value
72 72
73 73 def __json__(self):
74 74 return dict(name=self.name,
75 75 value=self.value)
76 76
77 77 """
78 78
79 79 def default(self, obj):
80 80
81 81 if hasattr(obj, '__json__'):
82 82 return obj.__json__()
83 83 else:
84 84 return json.JSONEncoder.default(self, obj)
85 85
86 86 class BaseModel(object):
87 87 """Base Model for all classess
88 88
89 89 """
90 90
91 91 @classmethod
92 92 def _get_keys(cls):
93 93 """return column names for this model """
94 94 return class_mapper(cls).c.keys()
95 95
96 96 def get_dict(self):
97 97 """return dict with keys and values corresponding
98 98 to this model data """
99 99
100 100 d = {}
101 101 for k in self._get_keys():
102 102 d[k] = getattr(self, k)
103 103 return d
104 104
105 105 def get_appstruct(self):
106 106 """return list with keys and values tupples corresponding
107 107 to this model data """
108 108
109 109 l = []
110 110 for k in self._get_keys():
111 111 l.append((k, getattr(self, k),))
112 112 return l
113 113
114 114 def populate_obj(self, populate_dict):
115 115 """populate model with data from given populate_dict"""
116 116
117 117 for k in self._get_keys():
118 118 if k in populate_dict:
119 119 setattr(self, k, populate_dict[k])
120 120
121 121 @classmethod
122 122 def query(cls):
123 123 return Session.query(cls)
124 124
125 125 @classmethod
126 126 def get(cls, id_):
127 127 return Session.query(cls).get(id_)
128
128
129 @classmethod
130 def delete(cls, id_):
131 obj = Session.query(cls).get(id_)
132 Session.delete(obj)
133 Session.commit()
134
129 135
130 136 class RhodeCodeSettings(Base, BaseModel):
131 137 __tablename__ = 'rhodecode_settings'
132 138 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
133 139 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
134 140 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
135 141 app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
136 142
137 143 def __init__(self, k='', v=''):
138 144 self.app_settings_name = k
139 145 self.app_settings_value = v
140 146
141 147 def __repr__(self):
142 148 return "<%s('%s:%s')>" % (self.__class__.__name__,
143 149 self.app_settings_name, self.app_settings_value)
144 150
145 151
146 152 @classmethod
147 153 def get_by_name(cls, ldap_key):
148 154 return Session.query(cls)\
149 155 .filter(cls.app_settings_name == ldap_key).scalar()
150 156
151 157 @classmethod
152 158 def get_app_settings(cls, cache=False):
153 159
154 160 ret = Session.query(cls)
155 161
156 162 if cache:
157 163 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
158 164
159 165 if not ret:
160 166 raise Exception('Could not get application settings !')
161 167 settings = {}
162 168 for each in ret:
163 169 settings['rhodecode_' + each.app_settings_name] = \
164 170 each.app_settings_value
165 171
166 172 return settings
167 173
168 174 @classmethod
169 175 def get_ldap_settings(cls, cache=False):
170 176 ret = Session.query(cls)\
171 177 .filter(cls.app_settings_name.startswith('ldap_'))\
172 178 .all()
173 179 fd = {}
174 180 for row in ret:
175 181 fd.update({row.app_settings_name:row.app_settings_value})
176 182
177 183 fd.update({'ldap_active':str2bool(fd.get('ldap_active'))})
178 184
179 185 return fd
180 186
181 187
182 188 class RhodeCodeUi(Base, BaseModel):
183 189 __tablename__ = 'rhodecode_ui'
184 __table_args__ = {'extend_existing':True}
190 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
191
192 HOOK_UPDATE = 'changegroup.update'
193 HOOK_REPO_SIZE = 'changegroup.repo_size'
194 HOOK_PUSH = 'pretxnchangegroup.push_logger'
195 HOOK_PULL = 'preoutgoing.pull_logger'
196
185 197 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
186 198 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
187 199 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
188 200 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
189 201 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
190 202
191 203
192 204 @classmethod
193 205 def get_by_key(cls, key):
194 206 return Session.query(cls).filter(cls.ui_key == key)
195 207
196 208
209 @classmethod
210 def get_builtin_hooks(cls):
211 q = cls.query()
212 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
213 cls.HOOK_REPO_SIZE,
214 cls.HOOK_PUSH, cls.HOOK_PULL]))
215 return q.all()
216
217 @classmethod
218 def get_custom_hooks(cls):
219 q = cls.query()
220 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
221 cls.HOOK_REPO_SIZE,
222 cls.HOOK_PUSH, cls.HOOK_PULL]))
223 q = q.filter(cls.ui_section == 'hooks')
224 return q.all()
225
226 @classmethod
227 def create_or_update_hook(cls, key, val):
228 new_ui = cls.get_by_key(key).scalar() or cls()
229 new_ui.ui_section = 'hooks'
230 new_ui.ui_active = True
231 new_ui.ui_key = key
232 new_ui.ui_value = val
233
234 Session.add(new_ui)
235 Session.commit()
236
237
197 238 class User(Base, BaseModel):
198 239 __tablename__ = 'users'
199 240 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
200 241 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
201 242 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
202 243 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
203 244 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
204 245 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
205 246 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
206 247 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
207 248 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
208 249 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
209 250 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
210 251 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
211 252
212 253 user_log = relationship('UserLog', cascade='all')
213 254 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
214 255
215 256 repositories = relationship('Repository')
216 257 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
217 258 repo_to_perm = relationship('RepoToPerm', primaryjoin='RepoToPerm.user_id==User.user_id', cascade='all')
218 259
219 260 group_member = relationship('UsersGroupMember', cascade='all')
220 261
221 262 @property
222 263 def full_contact(self):
223 264 return '%s %s <%s>' % (self.name, self.lastname, self.email)
224 265
225 266 @property
226 267 def short_contact(self):
227 268 return '%s %s' % (self.name, self.lastname)
228 269
229 270 @property
230 271 def is_admin(self):
231 272 return self.admin
232 273
233 274 def __repr__(self):
234 275 try:
235 276 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
236 277 self.user_id, self.username)
237 278 except:
238 279 return self.__class__.__name__
239 280
240 281 @classmethod
241 282 def by_username(cls, username, case_insensitive=False):
242 283 if case_insensitive:
243 284 return Session.query(cls).filter(cls.username.like(username)).one()
244 285 else:
245 286 return Session.query(cls).filter(cls.username == username).one()
246 287
247 288 @classmethod
248 289 def get_by_api_key(cls, api_key):
249 290 return Session.query(cls).filter(cls.api_key == api_key).one()
250 291
251 292
252 293 def update_lastlogin(self):
253 294 """Update user lastlogin"""
254 295
255 296 self.last_login = datetime.datetime.now()
256 297 Session.add(self)
257 298 Session.commit()
258 299 log.debug('updated user %s lastlogin', self.username)
259 300
260 301
261 302 class UserLog(Base, BaseModel):
262 303 __tablename__ = 'user_logs'
263 304 __table_args__ = {'extend_existing':True}
264 305 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
265 306 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
266 307 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
267 308 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 309 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 310 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
270 311 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
271 312
272 313 @property
273 314 def action_as_day(self):
274 315 return date(*self.action_date.timetuple()[:3])
275 316
276 317 user = relationship('User')
277 318 repository = relationship('Repository')
278 319
279 320
280 321 class UsersGroup(Base, BaseModel):
281 322 __tablename__ = 'users_groups'
282 323 __table_args__ = {'extend_existing':True}
283 324
284 325 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
285 326 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
286 327 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
287 328
288 329 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
289 330
290 331 def __repr__(self):
291 332 return '<userGroup(%s)>' % (self.users_group_name)
292 333
293 334 @classmethod
294 335 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
295 336 if case_insensitive:
296 337 gr = Session.query(cls)\
297 338 .filter(cls.users_group_name.ilike(group_name))
298 339 else:
299 340 gr = Session.query(UsersGroup)\
300 341 .filter(UsersGroup.users_group_name == group_name)
301 342 if cache:
302 343 gr = gr.options(FromCache("sql_cache_short",
303 344 "get_user_%s" % group_name))
304 345 return gr.scalar()
305 346
306 347
307 348 @classmethod
308 349 def get(cls, users_group_id, cache=False):
309 350 users_group = Session.query(cls)
310 351 if cache:
311 352 users_group = users_group.options(FromCache("sql_cache_short",
312 353 "get_users_group_%s" % users_group_id))
313 354 return users_group.get(users_group_id)
314 355
315 356 @classmethod
316 357 def create(cls, form_data):
317 358 try:
318 359 new_users_group = cls()
319 360 for k, v in form_data.items():
320 361 setattr(new_users_group, k, v)
321 362
322 363 Session.add(new_users_group)
323 364 Session.commit()
324 365 except:
325 366 log.error(traceback.format_exc())
326 367 Session.rollback()
327 368 raise
328 369
329 370 @classmethod
330 371 def update(cls, users_group_id, form_data):
331 372
332 373 try:
333 374 users_group = cls.get(users_group_id, cache=False)
334 375
335 376 for k, v in form_data.items():
336 377 if k == 'users_group_members':
337 378 users_group.members = []
338 379 Session.flush()
339 380 members_list = []
340 381 if v:
341 382 for u_id in set(v):
342 383 members_list.append(UsersGroupMember(
343 384 users_group_id,
344 385 u_id))
345 386 setattr(users_group, 'members', members_list)
346 387 setattr(users_group, k, v)
347 388
348 389 Session.add(users_group)
349 390 Session.commit()
350 391 except:
351 392 log.error(traceback.format_exc())
352 393 Session.rollback()
353 394 raise
354 395
355 396 @classmethod
356 397 def delete(cls, users_group_id):
357 398 try:
358 399
359 400 # check if this group is not assigned to repo
360 401 assigned_groups = UsersGroupRepoToPerm.query()\
361 402 .filter(UsersGroupRepoToPerm.users_group_id ==
362 403 users_group_id).all()
363 404
364 405 if assigned_groups:
365 406 raise UsersGroupsAssignedException('Group assigned to %s' %
366 407 assigned_groups)
367 408
368 409 users_group = cls.get(users_group_id, cache=False)
369 410 Session.delete(users_group)
370 411 Session.commit()
371 412 except:
372 413 log.error(traceback.format_exc())
373 414 Session.rollback()
374 415 raise
375 416
376 417
377 418 class UsersGroupMember(Base, BaseModel):
378 419 __tablename__ = 'users_groups_members'
379 420 __table_args__ = {'extend_existing':True}
380 421
381 422 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
382 423 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
383 424 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
384 425
385 426 user = relationship('User', lazy='joined')
386 427 users_group = relationship('UsersGroup')
387 428
388 429 def __init__(self, gr_id='', u_id=''):
389 430 self.users_group_id = gr_id
390 431 self.user_id = u_id
391 432
392 433 class Repository(Base, BaseModel):
393 434 __tablename__ = 'repositories'
394 435 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
395 436
396 437 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
397 438 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
398 439 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
399 440 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
400 441 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
401 442 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
402 443 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
403 444 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
404 445 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
405 446 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
406 447
407 448 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
408 449 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
409 450
410 451
411 452 user = relationship('User')
412 453 fork = relationship('Repository', remote_side=repo_id)
413 454 group = relationship('Group')
414 455 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
415 456 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
416 457 stats = relationship('Statistics', cascade='all', uselist=False)
417 458
418 459 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
419 460
420 461 logs = relationship('UserLog', cascade='all')
421 462
422 463 def __repr__(self):
423 464 return "<%s('%s:%s')>" % (self.__class__.__name__,
424 465 self.repo_id, self.repo_name)
425 466
426 467 @classmethod
427 468 def by_repo_name(cls, repo_name):
428 469 q = Session.query(cls).filter(cls.repo_name == repo_name)
429 470
430 471 q = q.options(joinedload(Repository.fork))\
431 472 .options(joinedload(Repository.user))\
432 473 .options(joinedload(Repository.group))\
433 474
434 475 return q.one()
435 476
436 477 @classmethod
437 478 def get_repo_forks(cls, repo_id):
438 479 return Session.query(cls).filter(Repository.fork_id == repo_id)
439 480
440 481 @property
441 482 def just_name(self):
442 483 return self.repo_name.split(os.sep)[-1]
443 484
444 485 @property
445 486 def groups_with_parents(self):
446 487 groups = []
447 488 if self.group is None:
448 489 return groups
449 490
450 491 cur_gr = self.group
451 492 groups.insert(0, cur_gr)
452 493 while 1:
453 494 gr = getattr(cur_gr, 'parent_group', None)
454 495 cur_gr = cur_gr.parent_group
455 496 if gr is None:
456 497 break
457 498 groups.insert(0, gr)
458 499
459 500 return groups
460 501
461 502 @property
462 503 def groups_and_repo(self):
463 504 return self.groups_with_parents, self.just_name
464 505
465 506 @LazyProperty
466 507 def repo_path(self):
467 508 """
468 509 Returns base full path for that repository means where it actually
469 510 exists on a filesystem
470 511 """
471 512 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/')
472 513 q.options(FromCache("sql_cache_short", "repository_repo_path"))
473 514 return q.one().ui_value
474 515
475 516 @property
476 517 def repo_full_path(self):
477 518 p = [self.repo_path]
478 519 # we need to split the name by / since this is how we store the
479 520 # names in the database, but that eventually needs to be converted
480 521 # into a valid system path
481 522 p += self.repo_name.split('/')
482 523 return os.path.join(*p)
483 524
484 525 @property
485 526 def _ui(self):
486 527 """
487 528 Creates an db based ui object for this repository
488 529 """
489 530 from mercurial import ui
490 531 from mercurial import config
491 532 baseui = ui.ui()
492 533
493 534 #clean the baseui object
494 535 baseui._ocfg = config.config()
495 536 baseui._ucfg = config.config()
496 537 baseui._tcfg = config.config()
497 538
498 539
499 540 ret = Session.query(RhodeCodeUi)\
500 541 .options(FromCache("sql_cache_short",
501 542 "repository_repo_ui")).all()
502 543
503 544 hg_ui = ret
504 545 for ui_ in hg_ui:
505 546 if ui_.ui_active:
506 547 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
507 548 ui_.ui_key, ui_.ui_value)
508 549 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
509 550
510 551 return baseui
511 552
512 553 #==========================================================================
513 554 # SCM CACHE INSTANCE
514 555 #==========================================================================
515 556
516 557 @property
517 558 def invalidate(self):
518 559 """
519 560 Returns Invalidation object if this repo should be invalidated
520 561 None otherwise. `cache_active = False` means that this cache
521 562 state is not valid and needs to be invalidated
522 563 """
523 564 return Session.query(CacheInvalidation)\
524 565 .filter(CacheInvalidation.cache_key == self.repo_name)\
525 566 .filter(CacheInvalidation.cache_active == False)\
526 567 .scalar()
527 568
528 569 def set_invalidate(self):
529 570 """
530 571 set a cache for invalidation for this instance
531 572 """
532 573 inv = Session.query(CacheInvalidation)\
533 574 .filter(CacheInvalidation.cache_key == self.repo_name)\
534 575 .scalar()
535 576
536 577 if inv is None:
537 578 inv = CacheInvalidation(self.repo_name)
538 579 inv.cache_active = True
539 580 Session.add(inv)
540 581 Session.commit()
541 582
542 583 @property
543 584 def scm_instance(self):
544 585 return self.__get_instance()
545 586
546 587 @property
547 588 def scm_instance_cached(self):
548 589 @cache_region('long_term')
549 590 def _c(repo_name):
550 591 return self.__get_instance()
551 592
552 593 # TODO: remove this trick when beaker 1.6 is released
553 594 # and have fixed this issue with not supporting unicode keys
554 595 rn = safe_str(self.repo_name)
555 596
556 597 inv = self.invalidate
557 598 if inv is not None:
558 599 region_invalidate(_c, None, rn)
559 600 # update our cache
560 601 inv.cache_active = True
561 602 Session.add(inv)
562 603 Session.commit()
563 604
564 605 return _c(rn)
565 606
566 607 def __get_instance(self):
567 608
568 609 repo_full_path = self.repo_full_path
569 610
570 611 try:
571 612 alias = get_scm(repo_full_path)[0]
572 613 log.debug('Creating instance of %s repository', alias)
573 614 backend = get_backend(alias)
574 615 except VCSError:
575 616 log.error(traceback.format_exc())
576 617 log.error('Perhaps this repository is in db and not in '
577 618 'filesystem run rescan repositories with '
578 619 '"destroy old data " option from admin panel')
579 620 return
580 621
581 622 if alias == 'hg':
582 623
583 624 repo = backend(safe_str(repo_full_path), create=False,
584 625 baseui=self._ui)
585 626 #skip hidden web repository
586 627 if repo._get_hidden():
587 628 return
588 629 else:
589 630 repo = backend(repo_full_path, create=False)
590 631
591 632 return repo
592 633
593 634
594 635 class Group(Base, BaseModel):
595 636 __tablename__ = 'groups'
596 637 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
597 638 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
598 639 __mapper_args__ = {'order_by':'group_name'}
599 640
600 641 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
601 642 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
602 643 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
603 644 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
604 645
605 646 parent_group = relationship('Group', remote_side=group_id)
606 647
607 648
608 649 def __init__(self, group_name='', parent_group=None):
609 650 self.group_name = group_name
610 651 self.parent_group = parent_group
611 652
612 653 def __repr__(self):
613 654 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
614 655 self.group_name)
615 656
616 657 @classmethod
617 658 def url_sep(cls):
618 659 return '/'
619 660
620 661 @property
621 662 def parents(self):
622 663 parents_recursion_limit = 5
623 664 groups = []
624 665 if self.parent_group is None:
625 666 return groups
626 667 cur_gr = self.parent_group
627 668 groups.insert(0, cur_gr)
628 669 cnt = 0
629 670 while 1:
630 671 cnt += 1
631 672 gr = getattr(cur_gr, 'parent_group', None)
632 673 cur_gr = cur_gr.parent_group
633 674 if gr is None:
634 675 break
635 676 if cnt == parents_recursion_limit:
636 677 # this will prevent accidental infinit loops
637 678 log.error('group nested more than %s' %
638 679 parents_recursion_limit)
639 680 break
640 681
641 682 groups.insert(0, gr)
642 683 return groups
643 684
644 685 @property
645 686 def children(self):
646 687 return Session.query(Group).filter(Group.parent_group == self)
647 688
648 689 @property
649 690 def full_path(self):
650 691 return Group.url_sep().join([g.group_name for g in self.parents] +
651 692 [self.group_name])
652 693
653 694 @property
654 695 def repositories(self):
655 696 return Session.query(Repository).filter(Repository.group == self)
656 697
657 698 @property
658 699 def repositories_recursive_count(self):
659 700 cnt = self.repositories.count()
660 701
661 702 def children_count(group):
662 703 cnt = 0
663 704 for child in group.children:
664 705 cnt += child.repositories.count()
665 706 cnt += children_count(child)
666 707 return cnt
667 708
668 709 return cnt + children_count(self)
669 710
670 711 class Permission(Base, BaseModel):
671 712 __tablename__ = 'permissions'
672 713 __table_args__ = {'extend_existing':True}
673 714 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
674 715 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
675 716 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
676 717
677 718 def __repr__(self):
678 719 return "<%s('%s:%s')>" % (self.__class__.__name__,
679 720 self.permission_id, self.permission_name)
680 721
681 722 @classmethod
682 723 def get_by_key(cls, key):
683 724 return Session.query(cls).filter(cls.permission_name == key).scalar()
684 725
685 726 class RepoToPerm(Base, BaseModel):
686 727 __tablename__ = 'repo_to_perm'
687 728 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
688 729 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
689 730 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
690 731 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
691 732 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
692 733
693 734 user = relationship('User')
694 735 permission = relationship('Permission')
695 736 repository = relationship('Repository')
696 737
697 738 class UserToPerm(Base, BaseModel):
698 739 __tablename__ = 'user_to_perm'
699 740 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
700 741 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
701 742 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
702 743 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
703 744
704 745 user = relationship('User')
705 746 permission = relationship('Permission')
706 747
707 748 @classmethod
708 749 def has_perm(cls, user_id, perm):
709 750 if not isinstance(perm, Permission):
710 751 raise Exception('perm needs to be an instance of Permission class')
711 752
712 753 return Session.query(cls).filter(cls.user_id == user_id)\
713 754 .filter(cls.permission == perm).scalar() is not None
714 755
715 756 @classmethod
716 757 def grant_perm(cls, user_id, perm):
717 758 if not isinstance(perm, Permission):
718 759 raise Exception('perm needs to be an instance of Permission class')
719 760
720 761 new = cls()
721 762 new.user_id = user_id
722 763 new.permission = perm
723 764 try:
724 765 Session.add(new)
725 766 Session.commit()
726 767 except:
727 768 Session.rollback()
728 769
729 770
730 771 @classmethod
731 772 def revoke_perm(cls, user_id, perm):
732 773 if not isinstance(perm, Permission):
733 774 raise Exception('perm needs to be an instance of Permission class')
734 775
735 776 try:
736 777 Session.query(cls).filter(cls.user_id == user_id)\
737 778 .filter(cls.permission == perm).delete()
738 779 Session.commit()
739 780 except:
740 781 Session.rollback()
741 782
742 783 class UsersGroupRepoToPerm(Base, BaseModel):
743 784 __tablename__ = 'users_group_repo_to_perm'
744 785 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
745 786 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
746 787 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
747 788 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
748 789 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
749 790
750 791 users_group = relationship('UsersGroup')
751 792 permission = relationship('Permission')
752 793 repository = relationship('Repository')
753 794
754 795 def __repr__(self):
755 796 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
756 797
757 798 class UsersGroupToPerm(Base, BaseModel):
758 799 __tablename__ = 'users_group_to_perm'
759 800 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
760 801 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
761 802 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
762 803
763 804 users_group = relationship('UsersGroup')
764 805 permission = relationship('Permission')
765 806
766 807
767 808 @classmethod
768 809 def has_perm(cls, users_group_id, perm):
769 810 if not isinstance(perm, Permission):
770 811 raise Exception('perm needs to be an instance of Permission class')
771 812
772 813 return Session.query(cls).filter(cls.users_group_id ==
773 814 users_group_id)\
774 815 .filter(cls.permission == perm)\
775 816 .scalar() is not None
776 817
777 818 @classmethod
778 819 def grant_perm(cls, users_group_id, perm):
779 820 if not isinstance(perm, Permission):
780 821 raise Exception('perm needs to be an instance of Permission class')
781 822
782 823 new = cls()
783 824 new.users_group_id = users_group_id
784 825 new.permission = perm
785 826 try:
786 827 Session.add(new)
787 828 Session.commit()
788 829 except:
789 830 Session.rollback()
790 831
791 832
792 833 @classmethod
793 834 def revoke_perm(cls, users_group_id, perm):
794 835 if not isinstance(perm, Permission):
795 836 raise Exception('perm needs to be an instance of Permission class')
796 837
797 838 try:
798 839 Session.query(cls).filter(cls.users_group_id == users_group_id)\
799 840 .filter(cls.permission == perm).delete()
800 841 Session.commit()
801 842 except:
802 843 Session.rollback()
803 844
804 845
805 846 class GroupToPerm(Base, BaseModel):
806 847 __tablename__ = 'group_to_perm'
807 848 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
808 849
809 850 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
810 851 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
811 852 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
812 853 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
813 854
814 855 user = relationship('User')
815 856 permission = relationship('Permission')
816 857 group = relationship('Group')
817 858
818 859 class Statistics(Base, BaseModel):
819 860 __tablename__ = 'statistics'
820 861 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
821 862 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
822 863 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
823 864 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
824 865 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
825 866 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
826 867 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
827 868
828 869 repository = relationship('Repository', single_parent=True)
829 870
830 871 class UserFollowing(Base, BaseModel):
831 872 __tablename__ = 'user_followings'
832 873 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
833 874 UniqueConstraint('user_id', 'follows_user_id')
834 875 , {'extend_existing':True})
835 876
836 877 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
837 878 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
838 879 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
839 880 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
840 881 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
841 882
842 883 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
843 884
844 885 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
845 886 follows_repository = relationship('Repository', order_by='Repository.repo_name')
846 887
847 888
848 889 @classmethod
849 890 def get_repo_followers(cls, repo_id):
850 891 return Session.query(cls).filter(cls.follows_repo_id == repo_id)
851 892
852 893 class CacheInvalidation(Base, BaseModel):
853 894 __tablename__ = 'cache_invalidation'
854 895 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
855 896 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
856 897 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
857 898 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
858 899 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
859 900
860 901
861 902 def __init__(self, cache_key, cache_args=''):
862 903 self.cache_key = cache_key
863 904 self.cache_args = cache_args
864 905 self.cache_active = False
865 906
866 907 def __repr__(self):
867 908 return "<%s('%s:%s')>" % (self.__class__.__name__,
868 909 self.cache_id, self.cache_key)
869 910
870 911 class DbMigrateVersion(Base, BaseModel):
871 912 __tablename__ = 'db_migrate_version'
872 913 __table_args__ = {'extend_existing':True}
873 914 repository_id = Column('repository_id', String(250), primary_key=True)
874 915 repository_path = Column('repository_path', Text)
875 916 version = Column('version', Integer)
@@ -1,189 +1,192
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="checkbox-1">
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 </div>
42 42 </div>
43 43
44 44 <div class="buttons">
45 45 ${h.submit('rescan','Rescan repositories',class_="ui-button")}
46 46 </div>
47 47 </div>
48 48 </div>
49 49 ${h.end_form()}
50 50
51 51 <h3>${_('Whoosh indexing')}</h3>
52 52 ${h.form(url('admin_setting', setting_id='whoosh'),method='put')}
53 53 <div class="form">
54 54 <!-- fields -->
55 55
56 56 <div class="fields">
57 57 <div class="field">
58 58 <div class="label label-checkbox">
59 59 <label for="destroy">${_('index build option')}:</label>
60 60 </div>
61 61 <div class="checkboxes">
62 62 <div class="checkbox">
63 63 ${h.checkbox('full_index',True)}
64 64 <label for="checkbox-1">${_('build from scratch')}</label>
65 65 </div>
66 66 </div>
67 67 </div>
68 68
69 69 <div class="buttons">
70 70 ${h.submit('reindex','Reindex',class_="ui-button")}
71 71 </div>
72 72 </div>
73 73 </div>
74 74 ${h.end_form()}
75 75
76 76 <h3>${_('Global application settings')}</h3>
77 77 ${h.form(url('admin_setting', setting_id='global'),method='put')}
78 78 <div class="form">
79 79 <!-- fields -->
80 80
81 81 <div class="fields">
82 82
83 83 <div class="field">
84 84 <div class="label">
85 85 <label for="rhodecode_title">${_('Application name')}:</label>
86 86 </div>
87 87 <div class="input">
88 88 ${h.text('rhodecode_title',size=30)}
89 89 </div>
90 90 </div>
91 91
92 92 <div class="field">
93 93 <div class="label">
94 94 <label for="rhodecode_realm">${_('Realm text')}:</label>
95 95 </div>
96 96 <div class="input">
97 97 ${h.text('rhodecode_realm',size=30)}
98 98 </div>
99 99 </div>
100 100
101 101 <div class="field">
102 102 <div class="label">
103 103 <label for="ga_code">${_('GA code')}:</label>
104 104 </div>
105 105 <div class="input">
106 106 ${h.text('rhodecode_ga_code',size=30)}
107 107 </div>
108 108 </div>
109 109
110 110 <div class="buttons">
111 111 ${h.submit('save','Save settings',class_="ui-button")}
112 112 ${h.reset('reset','Reset',class_="ui-button")}
113 113 </div>
114 114 </div>
115 115 </div>
116 116 ${h.end_form()}
117 117
118 118 <h3>${_('Mercurial settings')}</h3>
119 119 ${h.form(url('admin_setting', setting_id='mercurial'),method='put')}
120 120 <div class="form">
121 121 <!-- fields -->
122 122
123 123 <div class="fields">
124 124
125 125 <div class="field">
126 126 <div class="label label-checkbox">
127 127 <label for="web_push_ssl">${_('Web')}:</label>
128 128 </div>
129 129 <div class="checkboxes">
130 130 <div class="checkbox">
131 131 ${h.checkbox('web_push_ssl','true')}
132 132 <label for="web_push_ssl">${_('require ssl for pushing')}</label>
133 133 </div>
134 134 </div>
135 135 </div>
136 136
137 137 <div class="field">
138 138 <div class="label label-checkbox">
139 139 <label for="web_push_ssl">${_('Hooks')}:</label>
140 140 </div>
141 <div class="input">
142 ${h.link_to('advanced setup',url('admin_edit_setting',setting_id='hooks'))}
143 </div>
141 144 <div class="checkboxes">
142 145 <div class="checkbox">
143 146 ${h.checkbox('hooks_changegroup_update','True')}
144 147 <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
145 148 </div>
146 149 <div class="checkbox">
147 150 ${h.checkbox('hooks_changegroup_repo_size','True')}
148 151 <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
149 152 </div>
150 153 <div class="checkbox">
151 154 ${h.checkbox('hooks_pretxnchangegroup_push_logger','True')}
152 155 <label for="hooks_pretxnchangegroup_push_logger">${_('Log user push commands')}</label>
153 156 </div>
154 157 <div class="checkbox">
155 158 ${h.checkbox('hooks_preoutgoing_pull_logger','True')}
156 159 <label for="hooks_preoutgoing_pull_logger">${_('Log user pull commands')}</label>
157 160 </div>
158 161 </div>
159 162 </div>
160 163
161 164 <div class="field">
162 165 <div class="label">
163 166 <label for="paths_root_path">${_('Repositories location')}:</label>
164 167 </div>
165 168 <div class="input">
166 169 ${h.text('paths_root_path',size=30,readonly="readonly")}
167 170 <span id="path_unlock" class="tooltip"
168 171 title="${h.tooltip(_('This a crucial application setting. If You really sure you need to change this, you must restart application in order to make this settings take effect. Click this label to unlock.'))}">
169 172 ${_('unlock')}</span>
170 173 </div>
171 174 </div>
172 175
173 176 <div class="buttons">
174 177 ${h.submit('save','Save settings',class_="ui-button")}
175 178 ${h.reset('reset','Reset',class_="ui-button")}
176 179 </div>
177 180 </div>
178 181 </div>
179 182 ${h.end_form()}
180 183
181 184 <script type="text/javascript">
182 185 YAHOO.util.Event.onDOMReady(function(){
183 186 YAHOO.util.Event.addListener('path_unlock','click',function(){
184 187 YAHOO.util.Dom.get('paths_root_path').removeAttribute('readonly');
185 188 });
186 189 });
187 190 </script>
188 191 </div>
189 192 </%def>
General Comments 0
You need to be logged in to leave comments. Login now