djangocms_installer/config/__init__.py
import argparse
import locale
import os.path
import sys
import warnings
from distutils.version import LooseVersion
import pytz
from .. import compat
from ..utils import less_than_version, supported_versions
from . import data, ini
from .internal import DbAction, validate_project
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 "{}" 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
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 "{}" 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 "{}" 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 "{}" 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, str):
args.languages = args.languages.split(",")
elif len(args.languages) == 1 and isinstance(args.languages[0], str):
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(str(e))
sys.exit(6)
if django_version is None: # pragma: no cover
sys.stderr.write(
"Please provide a Django supported version: {}. 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: {}. 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 '{}' 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 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"])
if not args.no_db_driver:
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"])
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<{}".format(less_than_version(django_version)))
if django_version == "2.2":
requirements.extend(data.REQUIREMENTS["django-2.2"])
elif django_version == "3.0":
requirements.extend(data.REQUIREMENTS["django-3.0"])
elif django_version == "3.1":
requirements.extend(data.REQUIREMENTS["django-3.1"])
requirements.extend(data.REQUIREMENTS["default"])
args.requirements = "\n".join(requirements).strip()
# Convenient shortcuts
args.cms_version = cms_version
args.django_version = django_version
args.settings_path = os.path.join(args.project_directory, args.project_name, "settings.py").strip()
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
def get_settings():
module = __import__("djangocms_installer.config", globals(), locals(), ["settings"])
return module.settings
def write_default(config):
pass
def show_plugins():
"""
Shows a descriptive text about supported plugins
"""
sys.stdout.write(str(data.PLUGIN_LIST_TEXT))
def show_requirements(args):
"""
Prints the list of requirements according to the arguments provided
"""
sys.stdout.write(str(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
if not args.noinput:
if action.choices:
choices = " (choices: {})".format(", ".join(action.choices))
if input_value:
if type(input_value) == list:
default = " [default {}]".format(", ".join(input_value))
else:
default = " [default {}]".format(input_value)
while not new_val:
prompt = "{}{}{}: ".format(action.help, choices, default)
new_val = 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: # pragma: no cover
raise ValueError("Option {} 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 is True or new_val == "yes"
setattr(args, action.dest, new_val)
return args