import glob
import os
import re
import shutil
import subprocess
import sys
import textwrap
from copy import copy, deepcopy
from distutils.version import LooseVersion
from shlex import quote as shlex_quote
from ..config import data, get_settings
from ..utils import chdir, format_val
[docs]def create_project(config_data):
"""
Call django-admin to create the project structure
:param config_data: configuration data
"""
env = deepcopy(dict(os.environ))
env["DJANGO_SETTINGS_MODULE"] = str("{}.settings".format(config_data.project_name))
env["PYTHONPATH"] = str(os.pathsep.join(map(shlex_quote, sys.path)))
kwargs = {}
args = []
if config_data.template:
kwargs["template"] = config_data.template
args.append(config_data.project_name)
if config_data.project_directory:
args.append(config_data.project_directory)
if not os.path.exists(config_data.project_directory):
os.makedirs(config_data.project_directory)
base_cmd = "django-admin.py"
start_cmds = [os.path.join(os.path.dirname(sys.executable), base_cmd)]
start_cmd_pnodes = ["Scripts"]
start_cmds.extend([os.path.join(os.path.dirname(sys.executable), pnode, base_cmd) for pnode in start_cmd_pnodes])
start_cmd = [base_cmd]
for p in start_cmds:
if os.path.exists(p):
start_cmd = [sys.executable, p]
break
cmd_args = start_cmd + ["startproject"] + args
if config_data.verbose:
sys.stdout.write("Project creation command: {}\n".format(" ".join(cmd_args)))
try:
output = subprocess.check_output(cmd_args, stderr=subprocess.STDOUT)
sys.stdout.write(output.decode("utf-8"))
except subprocess.CalledProcessError as e: # pragma: no cover
raise RuntimeError(e.output.decode("utf-8"))
[docs]def copy_files(config_data):
"""
It's a little rude actually: it just overwrites the django-generated urls.py
with a custom version and put other files in the project directory.
:param config_data: configuration data
"""
if config_data.i18n == "yes":
urlconf_path = os.path.join(os.path.dirname(__file__), "../config/urls_i18n.py")
else:
urlconf_path = os.path.join(os.path.dirname(__file__), "../config/urls_noi18n.py")
share_path = os.path.join(os.path.dirname(__file__), "../share")
template_path = os.path.join(share_path, "templates")
media_project = os.path.join(config_data.project_directory, "media")
static_main = os.path.join(config_data.project_path, "static")
static_project = os.path.join(config_data.project_directory, "static")
template_target = os.path.join(config_data.project_path, "templates")
if config_data.templates and os.path.isdir(config_data.templates):
template_path = config_data.templates
elif config_data.bootstrap:
template_path = os.path.join(template_path, "bootstrap")
else:
template_path = os.path.join(template_path, "basic")
shutil.copy(urlconf_path, config_data.urlconf_path)
if media_project:
os.makedirs(media_project)
if static_main:
os.makedirs(static_main)
if not os.path.exists(static_project):
os.makedirs(static_project)
if not os.path.exists(template_target):
os.makedirs(template_target)
for filename in glob.glob(os.path.join(template_path, "*.html")):
if os.path.isfile(filename):
shutil.copy(filename, template_target)
if config_data.noinput and not config_data.no_user:
script_path = os.path.join(share_path, "create_user.py")
if os.path.isfile(script_path):
shutil.copy(script_path, os.path.join(config_data.project_path, ".."))
if config_data.starting_page:
for filename in glob.glob(os.path.join(share_path, "starting_page.*")):
if os.path.isfile(filename):
shutil.copy(filename, os.path.join(config_data.project_path, ".."))
[docs]def patch_settings(config_data):
"""
Modify the settings file created by Django injecting the django CMS
configuration
:param config_data: configuration data
"""
import django
current_django_version = LooseVersion(django.__version__)
declared_django_version = LooseVersion(config_data.django_version)
if not os.path.exists(config_data.settings_path): # pragma: no cover
sys.stderr.write(
"Error while creating target project, "
"please check the given configuration: {}\n".format(config_data.settings_path)
)
return sys.exit(5)
if current_django_version.version[:2] != declared_django_version.version[:2]:
sys.stderr.write(
"Currently installed Django version {} differs from the declared {}. "
"Please check the given `--django-version` installer argument, your virtualenv "
"configuration and any package forcing a different Django version"
"\n".format(current_django_version, declared_django_version)
)
return sys.exit(9)
overridden_settings = (
"MIDDLEWARE_CLASSES",
"MIDDLEWARE",
"INSTALLED_APPS",
"TEMPLATE_LOADERS",
"TEMPLATE_CONTEXT_PROCESSORS",
"TEMPLATE_DIRS",
"LANGUAGES",
)
extra_settings = ""
with open(config_data.settings_path) as fd_original:
original = fd_original.read()
# extra settings reading
if config_data.extra_settings and os.path.exists(config_data.extra_settings):
with open(config_data.extra_settings) as fd_extra:
extra_settings = fd_extra.read()
original = original.replace("# -*- coding: utf-8 -*-\n", "")
DATA_DIR = "DATA_DIR = os.path.dirname(os.path.dirname(__file__))\n" # noqa
STATICFILES_DIR = "os.path.join(BASE_DIR, '{}', 'static'),".format(config_data.project_name) # noqa
original = data.DEFAULT_PROJECT_HEADER + DATA_DIR + original
original += "MEDIA_URL = '/media/'\n"
original += "MEDIA_ROOT = os.path.join(DATA_DIR, 'media')\n"
original += "STATIC_ROOT = os.path.join(DATA_DIR, 'static')\n"
original += """
STATICFILES_DIRS = (
{}
)
""".format(
STATICFILES_DIR
)
original = original.replace("# -*- coding: utf-8 -*-\n", "")
# I18N
if config_data.i18n == "no":
original = original.replace("I18N = True", "I18N = False")
original = original.replace("L10N = True", "L10N = False")
# TZ
if config_data.use_timezone == "no":
original = original.replace("USE_TZ = True", "USE_TZ = False")
if config_data.languages:
original = original.replace(
"LANGUAGE_CODE = 'en-us'",
"LANGUAGE_CODE = '{}'".format(config_data.languages[0]),
)
if config_data.timezone:
original = original.replace("TIME_ZONE = 'UTC'", "TIME_ZONE = '{}'".format(config_data.timezone))
for item in overridden_settings:
item_re = re.compile(r"{} = [^\]]+\]".format(item), re.DOTALL | re.MULTILINE)
original = item_re.sub("", original)
# TEMPLATES is special, so custom regexp needed
item_re = re.compile(r"TEMPLATES = .+\},\n\s+\},\n]$", re.DOTALL | re.MULTILINE)
original = item_re.sub("", original)
# DATABASES is a dictionary, so different regexp needed
item_re = re.compile(r"DATABASES = [^\}]+\}[^\}]+\}", re.DOTALL | re.MULTILINE)
original = item_re.sub("", original)
if original.find("SITE_ID") == -1:
original += "SITE_ID = 1\n\n"
original += _build_settings(config_data)
# Append extra settings at the end of the file
original += "\n" + extra_settings
with open(config_data.settings_path, "w") as fd_dest:
fd_dest.write(original)
def _build_settings(config_data):
"""
Build the django CMS settings dictionary
:param config_data: configuration data
"""
spacer = " "
text = []
settings_data = get_settings()
settings_data.MIDDLEWARE_CLASSES.insert(0, settings_data.APPHOOK_RELOAD_MIDDLEWARE_CLASS)
processors = settings_data.TEMPLATE_CONTEXT_PROCESSORS + settings_data.TEMPLATE_CONTEXT_PROCESSORS_3
text.append(
data.TEMPLATES_1_8.format(
loaders=(",\n" + spacer * 4).join(
[
"'{}'".format(var)
for var in settings_data.TEMPLATE_LOADERS
if (LooseVersion(config_data.django_version) < LooseVersion("2.0") or "eggs" not in var)
]
),
processors=(",\n" + spacer * 4).join(["'{}'".format(var) for var in processors]),
dirs="os.path.join(BASE_DIR, '{}', 'templates'),".format(config_data.project_name),
)
)
text.append(
"MIDDLEWARE = [\n{}{}\n]".format(
spacer,
(",\n" + spacer).join(["'{}'".format(var) for var in settings_data.MIDDLEWARE_CLASSES]),
)
)
apps = list(settings_data.INSTALLED_APPS)
apps = list(settings_data.CMS_3_HEAD) + apps
apps.extend(settings_data.TREEBEARD_APPS)
apps.extend(settings_data.CMS_3_APPLICATIONS)
if not config_data.no_plugins:
apps.extend(settings_data.FILER_PLUGINS_3)
text.append(
"INSTALLED_APPS = [\n{}{}\n]".format(
spacer,
(",\n" + spacer).join(["'{}'".format(var) for var in apps] + ["'{}'".format(config_data.project_name)]),
)
)
text.append(
"LANGUAGES = (\n{0}{1}\n{0}{2}\n)".format(
spacer,
"## Customize this",
("\n" + spacer).join(["('{0}', gettext('{0}')),".format(item) for item in config_data.languages]), # NOQA
)
)
cms_langs = deepcopy(settings_data.CMS_LANGUAGES)
for lang in config_data.languages:
lang_dict = {"code": lang, "name": lang}
lang_dict.update(copy(cms_langs["default"]))
cms_langs[1].append(lang_dict)
cms_text = ["CMS_LANGUAGES = {"]
cms_text.append("{}{}".format(spacer, "## Customize this"))
for key, value in cms_langs.items():
if key == "default":
cms_text.append("{0}'{1}': {{".format(spacer, key))
for config_name, config_value in value.items():
cms_text.append("{}'{}': {},".format(spacer * 2, config_name, config_value))
cms_text.append("{0}}},".format(spacer))
else:
cms_text.append("{}{}: [".format(spacer, key))
for lang in value:
cms_text.append("{0}{{".format(spacer * 2))
for config_name, config_value in lang.items():
if config_name == "code":
cms_text.append("{}'{}': '{}',".format(spacer * 3, config_name, config_value)) # NOQA
elif config_name == "name":
cms_text.append("{}'{}': gettext('{}'),".format(spacer * 3, config_name, config_value)) # NOQA
else:
cms_text.append("{}'{}': {},".format(spacer * 3, config_name, config_value))
cms_text.append("{0}}},".format(spacer * 2))
cms_text.append("{}],".format(spacer))
cms_text.append("}")
text.append("\n".join(cms_text))
if config_data.bootstrap:
cms_templates = "CMS_TEMPLATES_BOOTSTRAP"
else:
cms_templates = "CMS_TEMPLATES"
text.append(
"CMS_TEMPLATES = (\n{0}{1}\n{0}{2}\n)".format(
spacer,
"## Customize this",
(",\n" + spacer).join(["('{}', '{}')".format(*item) for item in getattr(settings_data, cms_templates)]),
)
)
text.append("X_FRAME_OPTIONS = 'SAMEORIGIN'")
text.append("CMS_PERMISSION = {}".format(settings_data.CMS_PERMISSION))
text.append("CMS_PLACEHOLDER_CONF = {}".format(settings_data.CMS_PLACEHOLDER_CONF))
database = [
"'{}': {}".format(key, format_val(val))
for key, val in sorted(config_data.db_parsed.items(), key=lambda x: x[0])
] # NOQA
text.append(
textwrap.dedent(
"""
DATABASES = {{
'default': {{
{0}
}}
}}"""
)
.strip()
.format((",\n" + spacer * 2).join(database))
) # NOQA
if config_data.filer:
text.append(
"THUMBNAIL_PROCESSORS = (\n{}{}\n)".format(
spacer,
(",\n" + spacer).join(["'{}'".format(var) for var in settings_data.THUMBNAIL_PROCESSORS]),
)
)
return "\n\n".join(text)
[docs]def setup_database(config_data):
"""
Run the migrate command to create the database schema
:param config_data: configuration data
"""
with chdir(config_data.project_directory):
env = deepcopy(dict(os.environ))
env["DJANGO_SETTINGS_MODULE"] = str("{}.settings".format(config_data.project_name))
env["PYTHONPATH"] = str(os.pathsep.join(map(shlex_quote, sys.path)))
commands = []
commands.append([sys.executable, "-W", "ignore", "manage.py", "migrate"])
if config_data.verbose:
sys.stdout.write("Database setup commands: {}\n".format(", ".join([" ".join(cmd) for cmd in commands])))
for command in commands:
try:
output = subprocess.check_output(command, env=env, stderr=subprocess.STDOUT)
sys.stdout.write(output.decode("utf-8"))
except subprocess.CalledProcessError as e: # pragma: no cover
if config_data.verbose:
sys.stdout.write(e.output.decode("utf-8"))
raise
if not config_data.no_user:
sys.stdout.write("Creating admin user\n")
if config_data.noinput:
create_user(config_data)
else: # pragma: no cover
subprocess.check_call(
" ".join([sys.executable, "-W", "ignore", "manage.py", "createsuperuser"]),
shell=True,
stderr=subprocess.STDOUT,
)
[docs]def create_user(config_data):
"""
Create admin user without user input
:param config_data: configuration data
"""
with chdir(os.path.abspath(config_data.project_directory)):
env = deepcopy(dict(os.environ))
env["DJANGO_SETTINGS_MODULE"] = str("{}.settings".format(config_data.project_name))
env["PYTHONPATH"] = str(os.pathsep.join(map(shlex_quote, sys.path)))
subprocess.check_call([sys.executable, "create_user.py"], env=env, stderr=subprocess.STDOUT)
for ext in ["py", "pyc"]:
try:
os.remove("create_user.{}".format(ext))
except OSError:
pass
[docs]def load_starting_page(config_data):
"""
Load starting page into the CMS
:param config_data: configuration data
"""
with chdir(os.path.abspath(config_data.project_directory)):
env = deepcopy(dict(os.environ))
env["DJANGO_SETTINGS_MODULE"] = str("{}.settings".format(config_data.project_name))
env["PYTHONPATH"] = str(os.pathsep.join(map(shlex_quote, sys.path)))
subprocess.check_call([sys.executable, "starting_page.py"], env=env, stderr=subprocess.STDOUT)
for ext in ["py", "pyc", "json"]:
try:
os.remove("starting_page.{}".format(ext))
except OSError:
pass