# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals
import argparse
import locale
import os.path
import sys
import warnings
from distutils.version import LooseVersion
import pytz
import six
from .. import compat, utils
from ..utils import less_than_version, supported_versions
from . import data, ini
from .internal import DbAction, validate_project
[docs]def parse(args):
"""
Define the available arguments
"""
from tzlocal import get_localzone
try:
timezone = get_localzone()
if isinstance(timezone, pytz.BaseTzInfo):
timezone = timezone.zone
except Exception: # pragma: no cover
timezone = 'UTC'
if timezone == 'local':
timezone = 'UTC'
parser = argparse.ArgumentParser(description="""Bootstrap a django CMS project.
Major usage modes:
- wizard: djangocms -w -p /path/whatever project_name: ask for all the options through a
CLI wizard.
- batch: djangocms project_name: runs with the default values plus any
additional option provided (see below) with no question asked.
- config file: djangocms_installer --config-file /path/to/config.ini project_name: reads values
from an ini-style config file.
Check https://djangocms-installer.readthedocs.io/en/latest/usage.html for detailed usage
information.
""", formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('--config-file', dest='config_file', action='store',
default=None,
help='Configuration file for djangocms_installer')
parser.add_argument('--config-dump', dest='config_dump', action='store',
default=None,
help='Dump configuration file with current args')
parser.add_argument('--db', '-d', dest='db', action=DbAction,
default='sqlite://localhost/project.db',
help='Database configuration (in URL format). '
'Example: sqlite://localhost/project.db')
parser.add_argument('--i18n', '-i', dest='i18n', action='store',
choices=('yes', 'no'),
default='yes', help='Activate Django I18N / L10N setting; this is '
'automatically activated if more than '
'language is provided')
parser.add_argument('--use-tz', '-z', dest='use_timezone', action='store',
choices=('yes', 'no'),
default='yes', help='Activate Django timezone support')
parser.add_argument('--timezone', '-t', dest='timezone',
required=False, default=timezone,
action='store', help='Optional default time zone. Example: Europe/Rome')
parser.add_argument('--reversion', '-e', dest='reversion', action='store',
choices=('yes', 'no'),
default='yes', help='Install and configure reversion support '
'(only for django CMS 3.2 and 3.3)')
parser.add_argument('--permissions', dest='permissions', action='store',
choices=('yes', 'no'),
default='no', help='Activate CMS permission management')
parser.add_argument('--pip-options', help='pass custom pip options', default='')
parser.add_argument('--languages', '-l', dest='languages', action='append',
help='Languages to enable. Option can be provided multiple times, or as a '
'comma separated list. Only language codes supported by Django can '
'be used here. Example: en, fr-FR, it-IT')
parser.add_argument('--django-version', dest='django_version', action='store',
choices=data.DJANGO_SUPPORTED,
default=data.DJANGO_DEFAULT, help='Django version')
parser.add_argument('--cms-version', '-v', dest='cms_version', action='store',
choices=data.DJANGOCMS_SUPPORTED,
default=data.DJANGOCMS_DEFAULT, help='django CMS version')
parser.add_argument('--parent-dir', '-p', dest='project_directory',
default='',
action='store', help='Optional project parent directory')
parser.add_argument('--bootstrap', dest='bootstrap', action='store',
choices=('yes', 'no'),
default='no', help='Use Bootstrap 4 Theme')
parser.add_argument('--templates', dest='templates', action='store',
default='no', help='Use custom template set')
parser.add_argument('--starting-page', dest='starting_page', action='store',
choices=('yes', 'no'),
default='no', help='Load a starting page with examples after installation '
'(english language only). Choose "no" if you use a '
'custom template set.')
parser.add_argument(dest='project_name', action='store',
help='Name of the project to be created')
# Command that lists the supported plugins in verbose description
parser.add_argument('--list-plugins', '-P', dest='plugins', action='store_true',
help='List plugins that\'s going to be installed and configured')
# Command that lists the supported plugins in verbose description
parser.add_argument('--dump-requirements', '-R', dest='dump_reqs', action='store_true',
help='It dumps the requirements that would be installed according to '
'parameters given. Together with --requirements argument is useful '
'for customizing the virtualenv')
# Advanced options. These have a predefined default and are not asked
# by config wizard.
parser.add_argument('--no-input', '-q', dest='noinput', action='store_true',
default=True, help='Don\'t run the configuration wizard, just use the '
'provided values')
parser.add_argument('--wizard', '-w', dest='wizard', action='store_true',
default=False, help='Run the configuration wizard')
parser.add_argument('--verbose', dest='verbose', action='store_true',
default=False,
help='Be more verbose and don\'t swallow subcommands output')
parser.add_argument('--filer', '-f', dest='filer', action='store_true',
default=True, help='Install and configure django-filer plugins '
'- Always enabled')
parser.add_argument('--requirements', '-r', dest='requirements_file', action='store',
default=None, help='Externally defined requirements file')
parser.add_argument('--no-deps', '-n', dest='no_deps', action='store_true',
default=False, help='Don\'t install package dependencies')
parser.add_argument('--no-plugins', dest='no_plugins', action='store_true',
default=False, help='Don\'t install plugins')
parser.add_argument('--no-db-driver', dest='no_db_driver', action='store_true',
default=False, help='Don\'t install database package')
parser.add_argument('--no-sync', '-m', dest='no_sync', action='store_true',
default=False, help='Don\'t run syncdb / migrate after bootstrapping')
parser.add_argument('--no-user', '-u', dest='no_user', action='store_true',
default=False, help='Don\'t create the admin user')
parser.add_argument('--template', dest='template', action='store',
default=None, help='The path or URL to load the django project '
'template from.')
parser.add_argument('--extra-settings', dest='extra_settings', action='store',
default=None, help='The path to an file that contains extra settings.')
parser.add_argument('--skip-empty-check', '-s', dest='skip_project_dir_check',
action='store_true',
default=False, help='Skip the check if project dir is empty.')
parser.add_argument('--delete-project-dir', '-c', dest='delete_project_dir',
action='store_true',
default=False, help='Delete project directory on creation failure.')
parser.add_argument('--utc', dest='utc',
action='store_true',
default=False, help='Use UTC timezone.')
if '--utc' in args:
for action in parser._positionals._actions:
if action.dest == 'timezone':
action.default = 'UTC'
# If config_args then pretend that config args came from the stdin and run parser again.
config_args = ini.parse_config_file(parser, args)
args = parser.parse_args(config_args + args)
if not args.wizard:
args.noinput = True
else:
args.noinput = False
if not args.project_directory:
args.project_directory = args.project_name
args.project_directory = os.path.abspath(args.project_directory)
# First of all, check if the project name is valid
if not validate_project(args.project_name):
sys.stderr.write(
'Project name "{0}" is not valid or it\'s already defined. '
'Please use only numbers, letters and underscores.\n'.format(args.project_name)
)
sys.exit(3)
# Checking the given path
setattr(args, 'project_path', os.path.join(args.project_directory, args.project_name).strip())
if not args.skip_project_dir_check:
if (os.path.exists(args.project_directory) and
[path for path in os.listdir(args.project_directory) if not path.startswith('.')]):
sys.stderr.write(
'Path "{0}" already exists and is not empty, please choose a different one\n'
'If you want to use this path anyway use the -s flag to skip this check.\n'
''.format(args.project_directory)
)
sys.exit(4)
if os.path.exists(args.project_path):
sys.stderr.write(
'Path "{0}" already exists, please choose a different one\n'.format(args.project_path)
)
sys.exit(4)
if args.config_dump and os.path.isfile(args.config_dump):
sys.stdout.write(
'Cannot dump because given configuration file "{0}" exists.\n'.format(args.config_dump)
)
sys.exit(8)
args = _manage_args(parser, args)
# what do we want here?!
# * if languages are given as multiple arguments, let's use it as is
# * if no languages are given, use a default and stop handling it further
# * if languages are given as a comma-separated list, split it and use the
# resulting list.
if not args.languages:
try:
args.languages = [locale.getdefaultlocale()[0].split('_')[0]]
except Exception: # pragma: no cover
args.languages = ['en']
elif isinstance(args.languages, six.string_types):
args.languages = args.languages.split(',')
elif len(args.languages) == 1 and isinstance(args.languages[0], six.string_types):
args.languages = args.languages[0].split(',')
args.languages = [lang.strip().lower() for lang in args.languages]
if len(args.languages) > 1:
args.i18n = 'yes'
args.filer = True
# Convert version to numeric format for easier checking
try:
django_version, cms_version = supported_versions(args.django_version, args.cms_version)
cms_package = data.PACKAGE_MATRIX.get(
cms_version, data.PACKAGE_MATRIX[data.DJANGOCMS_LTS]
)
except RuntimeError as e: # pragma: no cover
sys.stderr.write(compat.unicode(e))
sys.exit(6)
if django_version is None: # pragma: no cover
sys.stderr.write(
'Please provide a Django supported version: {0}. Only Major.Minor '
'version selector is accepted\n'.format(', '.join(data.DJANGO_SUPPORTED))
)
sys.exit(6)
if cms_version is None: # pragma: no cover
sys.stderr.write(
'Please provide a django CMS supported version: {0}. Only Major.Minor '
'version selector is accepted\n'.format(', '.join(data.DJANGOCMS_SUPPORTED))
)
sys.exit(6)
default_settings = '{}.settings'.format(args.project_name)
env_settings = os.environ.get('DJANGO_SETTINGS_MODULE', default_settings)
if env_settings != default_settings:
sys.stderr.write(
'`DJANGO_SETTINGS_MODULE` is currently set to \'{0}\' which is not compatible with '
'djangocms installer.\nPlease unset `DJANGO_SETTINGS_MODULE` and re-run the installer '
'\n'.format(env_settings)
)
sys.exit(10)
if not getattr(args, 'requirements_file'):
requirements = []
# django CMS version check
if args.cms_version == 'develop':
requirements.append(cms_package)
warnings.warn(data.VERSION_WARNING.format('develop', 'django CMS'))
elif args.cms_version == 'rc': # pragma: no cover
requirements.append(cms_package)
elif args.cms_version == 'beta': # pragma: no cover
requirements.append(cms_package)
warnings.warn(data.VERSION_WARNING.format('beta', 'django CMS'))
else:
requirements.append(cms_package)
if args.cms_version in ('rc', 'develop'):
requirements.extend(data.REQUIREMENTS['cms-master'])
elif LooseVersion(cms_version) >= LooseVersion('3.7'):
requirements.extend(data.REQUIREMENTS['cms-3.7'])
elif LooseVersion(cms_version) >= LooseVersion('3.6'):
requirements.extend(data.REQUIREMENTS['cms-3.6'])
if not args.no_db_driver:
if args.db_driver == 'psycopg2' and django_version in ('1.11', '2.0', '2.1'):
requirements.append('psycopg2<2.8')
else:
requirements.append(args.db_driver)
if not args.no_plugins:
if args.cms_version in ('rc', 'develop'):
requirements.extend(data.REQUIREMENTS['plugins-master'])
elif LooseVersion(cms_version) >= LooseVersion('3.7'):
requirements.extend(data.REQUIREMENTS['plugins-3.7'])
elif LooseVersion(cms_version) >= LooseVersion('3.6'):
requirements.extend(data.REQUIREMENTS['plugins-3.6'])
requirements.extend(data.REQUIREMENTS['filer'])
# Django version check
if args.django_version == 'develop': # pragma: no cover
requirements.append(data.DJANGO_DEVELOP)
warnings.warn(data.VERSION_WARNING.format('develop', 'Django'))
elif args.django_version == 'beta': # pragma: no cover
requirements.append(data.DJANGO_BETA)
warnings.warn(data.VERSION_WARNING.format('beta', 'Django'))
else:
requirements.append('Django<{0}'.format(less_than_version(django_version)))
if django_version == '2.2':
requirements.extend(data.REQUIREMENTS['django-2.2'])
elif django_version == '2.1':
requirements.extend(data.REQUIREMENTS['django-2.1'])
elif django_version == '2.0':
requirements.extend(data.REQUIREMENTS['django-2.0'])
elif django_version == '1.11':
requirements.extend(data.REQUIREMENTS['django-1.11'])
requirements.extend(data.REQUIREMENTS['default'])
setattr(args, 'requirements', '\n'.join(requirements).strip())
# Convenient shortcuts
setattr(args, 'cms_version', cms_version)
setattr(args, 'django_version', django_version)
setattr(args, 'settings_path',
os.path.join(args.project_directory, args.project_name, 'settings.py').strip())
setattr(args, 'urlconf_path',
os.path.join(args.project_directory, args.project_name, 'urls.py').strip())
if args.config_dump:
ini.dump_config_file(args.config_dump, args, parser)
return args
[docs]def get_settings():
module = __import__(str('djangocms_installer.config'), globals(), locals(), [str('settings')])
return module.settings
[docs]def write_default(config):
pass
[docs]def show_plugins():
"""
Shows a descriptive text about supported plugins
"""
sys.stdout.write(compat.unicode(data.PLUGIN_LIST_TEXT))
[docs]def show_requirements(args):
"""
Prints the list of requirements according to the arguments provided
"""
sys.stdout.write(compat.unicode(args.requirements))
def _manage_args(parser, args):
"""
Checks and validate provided input
"""
for item in data.CONFIGURABLE_OPTIONS:
action = parser._option_string_actions[item]
choices = default = ''
input_value = getattr(args, action.dest)
new_val = None
# cannot count this until we find a way to test input
if not args.noinput: # pragma: no cover
if action.choices:
choices = ' (choices: {0})'.format(', '.join(action.choices))
if input_value:
if type(input_value) == list:
default = ' [default {0}]'.format(', '.join(input_value))
else:
default = ' [default {0}]'.format(input_value)
while not new_val:
prompt = '{0}{1}{2}: '.format(action.help, choices, default)
if action.choices in ('yes', 'no'):
new_val = utils.query_yes_no(prompt)
else:
new_val = compat.input(prompt)
new_val = compat.clean(new_val)
if not new_val and input_value:
new_val = input_value
if new_val and action.dest == 'templates':
if new_val != 'no' and not os.path.isdir(new_val):
sys.stdout.write('Given directory does not exists, retry\n')
new_val = False
if new_val and action.dest == 'db':
action(parser, args, new_val, action.option_strings)
new_val = getattr(args, action.dest)
else:
if not input_value and action.required:
raise ValueError(
'Option {0} is required when in no-input mode'.format(action.dest)
)
new_val = input_value
if action.dest == 'db':
action(parser, args, new_val, action.option_strings)
new_val = getattr(args, action.dest)
if action.dest == 'templates' and (new_val == 'no' or not os.path.isdir(new_val)):
new_val = False
if action.dest in ('bootstrap', 'starting_page'):
new_val = (new_val == 'yes')
setattr(args, action.dest, new_val)
return args