##// END OF EJS Templates
python3: fix compatibility call on print function.
marcink -
r2401:467c2f86 default
parent child Browse files
Show More
@@ -1,620 +1,620 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Database creation, and setup module for RhodeCode Enterprise. Used for creation
23 23 of database as well as for migration operations
24 24 """
25 25
26 26 import os
27 27 import sys
28 28 import time
29 29 import uuid
30 30 import logging
31 31 import getpass
32 32 from os.path import dirname as dn, join as jn
33 33
34 34 from sqlalchemy.engine import create_engine
35 35
36 36 from rhodecode import __dbversion__
37 37 from rhodecode.model import init_model
38 38 from rhodecode.model.user import UserModel
39 39 from rhodecode.model.db import (
40 40 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
41 41 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
42 42 from rhodecode.model.meta import Session, Base
43 43 from rhodecode.model.permission import PermissionModel
44 44 from rhodecode.model.repo import RepoModel
45 45 from rhodecode.model.repo_group import RepoGroupModel
46 46 from rhodecode.model.settings import SettingsModel
47 47
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 def notify(msg):
53 53 """
54 54 Notification for migrations messages
55 55 """
56 56 ml = len(msg) + (4 * 2)
57 print('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()
57 print(('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper())
58 58
59 59
60 60 class DbManage(object):
61 61
62 62 def __init__(self, log_sql, dbconf, root, tests=False,
63 63 SESSION=None, cli_args=None):
64 64 self.dbname = dbconf.split('/')[-1]
65 65 self.tests = tests
66 66 self.root = root
67 67 self.dburi = dbconf
68 68 self.log_sql = log_sql
69 69 self.db_exists = False
70 70 self.cli_args = cli_args or {}
71 71 self.init_db(SESSION=SESSION)
72 72 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
73 73
74 74 def get_ask_ok_func(self, param):
75 75 if param not in [None]:
76 76 # return a function lambda that has a default set to param
77 77 return lambda *args, **kwargs: param
78 78 else:
79 79 from rhodecode.lib.utils import ask_ok
80 80 return ask_ok
81 81
82 82 def init_db(self, SESSION=None):
83 83 if SESSION:
84 84 self.sa = SESSION
85 85 else:
86 86 # init new sessions
87 87 engine = create_engine(self.dburi, echo=self.log_sql)
88 88 init_model(engine)
89 89 self.sa = Session()
90 90
91 91 def create_tables(self, override=False):
92 92 """
93 93 Create a auth database
94 94 """
95 95
96 96 log.info("Existing database with the same name is going to be destroyed.")
97 97 log.info("Setup command will run DROP ALL command on that database.")
98 98 if self.tests:
99 99 destroy = True
100 100 else:
101 101 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
102 102 if not destroy:
103 103 log.info('Nothing done.')
104 104 sys.exit(0)
105 105 if destroy:
106 106 Base.metadata.drop_all()
107 107
108 108 checkfirst = not override
109 109 Base.metadata.create_all(checkfirst=checkfirst)
110 110 log.info('Created tables for %s' % self.dbname)
111 111
112 112 def set_db_version(self):
113 113 ver = DbMigrateVersion()
114 114 ver.version = __dbversion__
115 115 ver.repository_id = 'rhodecode_db_migrations'
116 116 ver.repository_path = 'versions'
117 117 self.sa.add(ver)
118 118 log.info('db version set to: %s' % __dbversion__)
119 119
120 120 def run_pre_migration_tasks(self):
121 121 """
122 122 Run various tasks before actually doing migrations
123 123 """
124 124 # delete cache keys on each upgrade
125 125 total = CacheKey.query().count()
126 126 log.info("Deleting (%s) cache keys now...", total)
127 127 CacheKey.delete_all_cache()
128 128
129 129 def upgrade(self):
130 130 """
131 131 Upgrades given database schema to given revision following
132 132 all needed steps, to perform the upgrade
133 133
134 134 """
135 135
136 136 from rhodecode.lib.dbmigrate.migrate.versioning import api
137 137 from rhodecode.lib.dbmigrate.migrate.exceptions import \
138 138 DatabaseNotControlledError
139 139
140 140 if 'sqlite' in self.dburi:
141 141 print (
142 142 '********************** WARNING **********************\n'
143 143 'Make sure your version of sqlite is at least 3.7.X. \n'
144 144 'Earlier versions are known to fail on some migrations\n'
145 145 '*****************************************************\n')
146 146
147 147 upgrade = self.ask_ok(
148 148 'You are about to perform a database upgrade. Make '
149 149 'sure you have backed up your database. '
150 150 'Continue ? [y/n]')
151 151 if not upgrade:
152 152 log.info('No upgrade performed')
153 153 sys.exit(0)
154 154
155 155 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
156 156 'rhodecode/lib/dbmigrate')
157 157 db_uri = self.dburi
158 158
159 159 try:
160 160 curr_version = api.db_version(db_uri, repository_path)
161 161 msg = ('Found current database under version '
162 162 'control with version %s' % curr_version)
163 163
164 164 except (RuntimeError, DatabaseNotControlledError):
165 165 curr_version = 1
166 166 msg = ('Current database is not under version control. Setting '
167 167 'as version %s' % curr_version)
168 168 api.version_control(db_uri, repository_path, curr_version)
169 169
170 170 notify(msg)
171 171
172 172 self.run_pre_migration_tasks()
173 173
174 174 if curr_version == __dbversion__:
175 175 log.info('This database is already at the newest version')
176 176 sys.exit(0)
177 177
178 178 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
179 179 notify('attempting to upgrade database from '
180 180 'version %s to version %s' % (curr_version, __dbversion__))
181 181
182 182 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
183 183 _step = None
184 184 for step in upgrade_steps:
185 185 notify('performing upgrade step %s' % step)
186 186 time.sleep(0.5)
187 187
188 188 api.upgrade(db_uri, repository_path, step)
189 189 self.sa.rollback()
190 190 notify('schema upgrade for step %s completed' % (step,))
191 191
192 192 _step = step
193 193
194 194 notify('upgrade to version %s successful' % _step)
195 195
196 196 def fix_repo_paths(self):
197 197 """
198 198 Fixes an old RhodeCode version path into new one without a '*'
199 199 """
200 200
201 201 paths = self.sa.query(RhodeCodeUi)\
202 202 .filter(RhodeCodeUi.ui_key == '/')\
203 203 .scalar()
204 204
205 205 paths.ui_value = paths.ui_value.replace('*', '')
206 206
207 207 try:
208 208 self.sa.add(paths)
209 209 self.sa.commit()
210 210 except Exception:
211 211 self.sa.rollback()
212 212 raise
213 213
214 214 def fix_default_user(self):
215 215 """
216 216 Fixes an old default user with some 'nicer' default values,
217 217 used mostly for anonymous access
218 218 """
219 219 def_user = self.sa.query(User)\
220 220 .filter(User.username == User.DEFAULT_USER)\
221 221 .one()
222 222
223 223 def_user.name = 'Anonymous'
224 224 def_user.lastname = 'User'
225 225 def_user.email = User.DEFAULT_USER_EMAIL
226 226
227 227 try:
228 228 self.sa.add(def_user)
229 229 self.sa.commit()
230 230 except Exception:
231 231 self.sa.rollback()
232 232 raise
233 233
234 234 def fix_settings(self):
235 235 """
236 236 Fixes rhodecode settings and adds ga_code key for google analytics
237 237 """
238 238
239 239 hgsettings3 = RhodeCodeSetting('ga_code', '')
240 240
241 241 try:
242 242 self.sa.add(hgsettings3)
243 243 self.sa.commit()
244 244 except Exception:
245 245 self.sa.rollback()
246 246 raise
247 247
248 248 def create_admin_and_prompt(self):
249 249
250 250 # defaults
251 251 defaults = self.cli_args
252 252 username = defaults.get('username')
253 253 password = defaults.get('password')
254 254 email = defaults.get('email')
255 255
256 256 if username is None:
257 257 username = raw_input('Specify admin username:')
258 258 if password is None:
259 259 password = self._get_admin_password()
260 260 if not password:
261 261 # second try
262 262 password = self._get_admin_password()
263 263 if not password:
264 264 sys.exit()
265 265 if email is None:
266 266 email = raw_input('Specify admin email:')
267 267 api_key = self.cli_args.get('api_key')
268 268 self.create_user(username, password, email, True,
269 269 strict_creation_check=False,
270 270 api_key=api_key)
271 271
272 272 def _get_admin_password(self):
273 273 password = getpass.getpass('Specify admin password '
274 274 '(min 6 chars):')
275 275 confirm = getpass.getpass('Confirm password:')
276 276
277 277 if password != confirm:
278 278 log.error('passwords mismatch')
279 279 return False
280 280 if len(password) < 6:
281 281 log.error('password is too short - use at least 6 characters')
282 282 return False
283 283
284 284 return password
285 285
286 286 def create_test_admin_and_users(self):
287 287 log.info('creating admin and regular test users')
288 288 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
289 289 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
290 290 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
291 291 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
292 292 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
293 293
294 294 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
295 295 TEST_USER_ADMIN_EMAIL, True, api_key=True)
296 296
297 297 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
298 298 TEST_USER_REGULAR_EMAIL, False, api_key=True)
299 299
300 300 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
301 301 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
302 302
303 303 def create_ui_settings(self, repo_store_path):
304 304 """
305 305 Creates ui settings, fills out hooks
306 306 and disables dotencode
307 307 """
308 308 settings_model = SettingsModel(sa=self.sa)
309 309 from rhodecode.lib.vcs.backends.hg import largefiles_store
310 310 from rhodecode.lib.vcs.backends.git import lfs_store
311 311
312 312 # Build HOOKS
313 313 hooks = [
314 314 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
315 315
316 316 # HG
317 317 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
318 318 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
319 319 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
320 320 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
321 321 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
322 322 (RhodeCodeUi.HOOK_PUSH_KEY, 'python:vcsserver.hooks.key_push'),
323 323
324 324 ]
325 325
326 326 for key, value in hooks:
327 327 hook_obj = settings_model.get_ui_by_key(key)
328 328 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
329 329 hooks2.ui_section = 'hooks'
330 330 hooks2.ui_key = key
331 331 hooks2.ui_value = value
332 332 self.sa.add(hooks2)
333 333
334 334 # enable largefiles
335 335 largefiles = RhodeCodeUi()
336 336 largefiles.ui_section = 'extensions'
337 337 largefiles.ui_key = 'largefiles'
338 338 largefiles.ui_value = ''
339 339 self.sa.add(largefiles)
340 340
341 341 # set default largefiles cache dir, defaults to
342 342 # /repo_store_location/.cache/largefiles
343 343 largefiles = RhodeCodeUi()
344 344 largefiles.ui_section = 'largefiles'
345 345 largefiles.ui_key = 'usercache'
346 346 largefiles.ui_value = largefiles_store(repo_store_path)
347 347
348 348 self.sa.add(largefiles)
349 349
350 350 # set default lfs cache dir, defaults to
351 351 # /repo_store_location/.cache/lfs_store
352 352 lfsstore = RhodeCodeUi()
353 353 lfsstore.ui_section = 'vcs_git_lfs'
354 354 lfsstore.ui_key = 'store_location'
355 355 lfsstore.ui_value = lfs_store(repo_store_path)
356 356
357 357 self.sa.add(lfsstore)
358 358
359 359 # enable hgsubversion disabled by default
360 360 hgsubversion = RhodeCodeUi()
361 361 hgsubversion.ui_section = 'extensions'
362 362 hgsubversion.ui_key = 'hgsubversion'
363 363 hgsubversion.ui_value = ''
364 364 hgsubversion.ui_active = False
365 365 self.sa.add(hgsubversion)
366 366
367 367 # enable hgevolve disabled by default
368 368 hgevolve = RhodeCodeUi()
369 369 hgevolve.ui_section = 'extensions'
370 370 hgevolve.ui_key = 'evolve'
371 371 hgevolve.ui_value = ''
372 372 hgevolve.ui_active = False
373 373 self.sa.add(hgevolve)
374 374
375 375 # enable hggit disabled by default
376 376 hggit = RhodeCodeUi()
377 377 hggit.ui_section = 'extensions'
378 378 hggit.ui_key = 'hggit'
379 379 hggit.ui_value = ''
380 380 hggit.ui_active = False
381 381 self.sa.add(hggit)
382 382
383 383 # set svn branch defaults
384 384 branches = ["/branches/*", "/trunk"]
385 385 tags = ["/tags/*"]
386 386
387 387 for branch in branches:
388 388 settings_model.create_ui_section_value(
389 389 RhodeCodeUi.SVN_BRANCH_ID, branch)
390 390
391 391 for tag in tags:
392 392 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
393 393
394 394 def create_auth_plugin_options(self, skip_existing=False):
395 395 """
396 396 Create default auth plugin settings, and make it active
397 397
398 398 :param skip_existing:
399 399 """
400 400
401 401 for k, v, t in [('auth_plugins', 'egg:rhodecode-enterprise-ce#rhodecode', 'list'),
402 402 ('auth_rhodecode_enabled', 'True', 'bool')]:
403 403 if (skip_existing and
404 404 SettingsModel().get_setting_by_name(k) is not None):
405 405 log.debug('Skipping option %s' % k)
406 406 continue
407 407 setting = RhodeCodeSetting(k, v, t)
408 408 self.sa.add(setting)
409 409
410 410 def create_default_options(self, skip_existing=False):
411 411 """Creates default settings"""
412 412
413 413 for k, v, t in [
414 414 ('default_repo_enable_locking', False, 'bool'),
415 415 ('default_repo_enable_downloads', False, 'bool'),
416 416 ('default_repo_enable_statistics', False, 'bool'),
417 417 ('default_repo_private', False, 'bool'),
418 418 ('default_repo_type', 'hg', 'unicode')]:
419 419
420 420 if (skip_existing and
421 421 SettingsModel().get_setting_by_name(k) is not None):
422 422 log.debug('Skipping option %s' % k)
423 423 continue
424 424 setting = RhodeCodeSetting(k, v, t)
425 425 self.sa.add(setting)
426 426
427 427 def fixup_groups(self):
428 428 def_usr = User.get_default_user()
429 429 for g in RepoGroup.query().all():
430 430 g.group_name = g.get_new_name(g.name)
431 431 self.sa.add(g)
432 432 # get default perm
433 433 default = UserRepoGroupToPerm.query()\
434 434 .filter(UserRepoGroupToPerm.group == g)\
435 435 .filter(UserRepoGroupToPerm.user == def_usr)\
436 436 .scalar()
437 437
438 438 if default is None:
439 439 log.debug('missing default permission for group %s adding' % g)
440 440 perm_obj = RepoGroupModel()._create_default_perms(g)
441 441 self.sa.add(perm_obj)
442 442
443 443 def reset_permissions(self, username):
444 444 """
445 445 Resets permissions to default state, useful when old systems had
446 446 bad permissions, we must clean them up
447 447
448 448 :param username:
449 449 """
450 450 default_user = User.get_by_username(username)
451 451 if not default_user:
452 452 return
453 453
454 454 u2p = UserToPerm.query()\
455 455 .filter(UserToPerm.user == default_user).all()
456 456 fixed = False
457 457 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
458 458 for p in u2p:
459 459 Session().delete(p)
460 460 fixed = True
461 461 self.populate_default_permissions()
462 462 return fixed
463 463
464 464 def update_repo_info(self):
465 465 RepoModel.update_repoinfo()
466 466
467 467 def config_prompt(self, test_repo_path='', retries=3):
468 468 defaults = self.cli_args
469 469 _path = defaults.get('repos_location')
470 470 if retries == 3:
471 471 log.info('Setting up repositories config')
472 472
473 473 if _path is not None:
474 474 path = _path
475 475 elif not self.tests and not test_repo_path:
476 476 path = raw_input(
477 477 'Enter a valid absolute path to store repositories. '
478 478 'All repositories in that path will be added automatically:'
479 479 )
480 480 else:
481 481 path = test_repo_path
482 482 path_ok = True
483 483
484 484 # check proper dir
485 485 if not os.path.isdir(path):
486 486 path_ok = False
487 487 log.error('Given path %s is not a valid directory' % (path,))
488 488
489 489 elif not os.path.isabs(path):
490 490 path_ok = False
491 491 log.error('Given path %s is not an absolute path' % (path,))
492 492
493 493 # check if path is at least readable.
494 494 if not os.access(path, os.R_OK):
495 495 path_ok = False
496 496 log.error('Given path %s is not readable' % (path,))
497 497
498 498 # check write access, warn user about non writeable paths
499 499 elif not os.access(path, os.W_OK) and path_ok:
500 500 log.warning('No write permission to given path %s' % (path,))
501 501
502 502 q = ('Given path %s is not writeable, do you want to '
503 503 'continue with read only mode ? [y/n]' % (path,))
504 504 if not self.ask_ok(q):
505 505 log.error('Canceled by user')
506 506 sys.exit(-1)
507 507
508 508 if retries == 0:
509 509 sys.exit('max retries reached')
510 510 if not path_ok:
511 511 retries -= 1
512 512 return self.config_prompt(test_repo_path, retries)
513 513
514 514 real_path = os.path.normpath(os.path.realpath(path))
515 515
516 516 if real_path != os.path.normpath(path):
517 517 q = ('Path looks like a symlink, RhodeCode Enterprise will store '
518 518 'given path as %s ? [y/n]') % (real_path,)
519 519 if not self.ask_ok(q):
520 520 log.error('Canceled by user')
521 521 sys.exit(-1)
522 522
523 523 return real_path
524 524
525 525 def create_settings(self, path):
526 526
527 527 self.create_ui_settings(path)
528 528
529 529 ui_config = [
530 530 ('web', 'push_ssl', 'False'),
531 531 ('web', 'allow_archive', 'gz zip bz2'),
532 532 ('web', 'allow_push', '*'),
533 533 ('web', 'baseurl', '/'),
534 534 ('paths', '/', path),
535 535 ('phases', 'publish', 'True')
536 536 ]
537 537 for section, key, value in ui_config:
538 538 ui_conf = RhodeCodeUi()
539 539 setattr(ui_conf, 'ui_section', section)
540 540 setattr(ui_conf, 'ui_key', key)
541 541 setattr(ui_conf, 'ui_value', value)
542 542 self.sa.add(ui_conf)
543 543
544 544 # rhodecode app settings
545 545 settings = [
546 546 ('realm', 'RhodeCode', 'unicode'),
547 547 ('title', '', 'unicode'),
548 548 ('pre_code', '', 'unicode'),
549 549 ('post_code', '', 'unicode'),
550 550 ('show_public_icon', True, 'bool'),
551 551 ('show_private_icon', True, 'bool'),
552 552 ('stylify_metatags', False, 'bool'),
553 553 ('dashboard_items', 100, 'int'),
554 554 ('admin_grid_items', 25, 'int'),
555 555 ('show_version', True, 'bool'),
556 556 ('use_gravatar', False, 'bool'),
557 557 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
558 558 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
559 559 ('support_url', '', 'unicode'),
560 560 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
561 561 ('show_revision_number', True, 'bool'),
562 562 ('show_sha_length', 12, 'int'),
563 563 ]
564 564
565 565 for key, val, type_ in settings:
566 566 sett = RhodeCodeSetting(key, val, type_)
567 567 self.sa.add(sett)
568 568
569 569 self.create_auth_plugin_options()
570 570 self.create_default_options()
571 571
572 572 log.info('created ui config')
573 573
574 574 def create_user(self, username, password, email='', admin=False,
575 575 strict_creation_check=True, api_key=None):
576 576 log.info('creating user %s' % username)
577 577 user = UserModel().create_or_update(
578 578 username, password, email, firstname=u'RhodeCode', lastname=u'Admin',
579 579 active=True, admin=admin, extern_type="rhodecode",
580 580 strict_creation_check=strict_creation_check)
581 581
582 582 if api_key:
583 583 log.info('setting a provided api key for the user %s', username)
584 584 from rhodecode.model.auth_token import AuthTokenModel
585 585 AuthTokenModel().create(
586 586 user=user, description=u'BUILTIN TOKEN')
587 587
588 588 def create_default_user(self):
589 589 log.info('creating default user')
590 590 # create default user for handling default permissions.
591 591 user = UserModel().create_or_update(username=User.DEFAULT_USER,
592 592 password=str(uuid.uuid1())[:20],
593 593 email=User.DEFAULT_USER_EMAIL,
594 594 firstname=u'Anonymous',
595 595 lastname=u'User',
596 596 strict_creation_check=False)
597 597 # based on configuration options activate/deactive this user which
598 598 # controlls anonymous access
599 599 if self.cli_args.get('public_access') is False:
600 600 log.info('Public access disabled')
601 601 user.active = False
602 602 Session().add(user)
603 603 Session().commit()
604 604
605 605 def create_permissions(self):
606 606 """
607 607 Creates all permissions defined in the system
608 608 """
609 609 # module.(access|create|change|delete)_[name]
610 610 # module.(none|read|write|admin)
611 611 log.info('creating permissions')
612 612 PermissionModel(self.sa).create_permissions()
613 613
614 614 def populate_default_permissions(self):
615 615 """
616 616 Populate default permissions. It will create only the default
617 617 permissions that are missing, and not alter already defined ones
618 618 """
619 619 log.info('creating default user permissions')
620 620 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
General Comments 0
You need to be logged in to leave comments. Login now