# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals
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 six import iteritems
from ..config import data, get_settings
from ..utils import chdir, format_val
try:
from shlex import quote as shlex_quote
except ImportError:
from pipes import quote as shlex_quote
[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[str('DJANGO_SETTINGS_MODULE')] = str('{0}.settings'.format(config_data.project_name))
env[str('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: {0}\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):
sys.stderr.write(
'Error while creating target project, '
'please check the given configuration: {0}\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, 'r') 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, 'r') 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'
STATICFILES_DIR = 'os.path.join(BASE_DIR, \'{0}\', \'static\'),'.format(
config_data.project_name
)
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 = (
{0}
)
""".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 = \'{0}\''.format(config_data.languages[0])
)
if config_data.timezone:
original = original.replace(
'TIME_ZONE = \'UTC\'', 'TIME_ZONE = \'{0}\''.format(config_data.timezone)
)
for item in overridden_settings:
item_re = re.compile(r'{0} = [^\]]+\]'.format(item), re.DOTALL | re.MULTILINE)
original = item_re.sub('', original)
# TEMPLATES is special, so custom regexp needed
if declared_django_version >= LooseVersion('2.0'):
item_re = re.compile(r'TEMPLATES = .+\},\n\s+\},\n]$', re.DOTALL | re.MULTILINE)
else:
item_re = re.compile(r'TEMPLATES = .+\]$', 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 = []
vars = get_settings()
vars.MIDDLEWARE_CLASSES.insert(0, vars.APPHOOK_RELOAD_MIDDLEWARE_CLASS)
processors = vars.TEMPLATE_CONTEXT_PROCESSORS + vars.TEMPLATE_CONTEXT_PROCESSORS_3
text.append(data.TEMPLATES_1_8.format(
loaders=(',\n' + spacer * 4).join([
"'{0}'".format(var) for var in vars.TEMPLATE_LOADERS
if (
LooseVersion(config_data.django_version) < LooseVersion('2.0') or
'eggs' not in var
)
]),
processors=(',\n' + spacer * 4).join(["'{0}'".format(var) for var in processors]),
dirs="os.path.join(BASE_DIR, '{0}', 'templates'),".format(config_data.project_name)
))
text.append('MIDDLEWARE = [\n{0}{1}\n]'.format(
spacer, (',\n' + spacer).join(['\'{0}\''.format(var)
for var in vars.MIDDLEWARE_CLASSES])
))
apps = list(vars.INSTALLED_APPS)
apps = list(vars.CMS_3_HEAD) + apps
apps.extend(vars.TREEBEARD_APPS)
apps.extend(vars.CMS_3_APPLICATIONS)
if not config_data.no_plugins:
apps.extend(vars.FILER_PLUGINS_3)
text.append('INSTALLED_APPS = [\n{0}{1}\n]'.format(
spacer, (',\n' + spacer).join(['\'{0}\''.format(var) for var in apps] +
['\'{0}\''.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(vars.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('{0}{1}'.format(spacer, '## Customize this'))
for key, value in iteritems(cms_langs):
if key == 'default':
cms_text.append('{0}\'{1}\': {{'.format(spacer, key))
for config_name, config_value in iteritems(value):
cms_text.append('{0}\'{1}\': {2},'.format(spacer * 2, config_name, config_value))
cms_text.append('{0}}},'.format(spacer))
else:
cms_text.append('{0}{1}: ['.format(spacer, key))
for lang in value:
cms_text.append('{0}{{'.format(spacer * 2))
for config_name, config_value in iteritems(lang):
if config_name == 'code':
cms_text.append('{0}\'{1}\': \'{2}\','.format(spacer * 3, config_name, config_value)) # NOQA
elif config_name == 'name':
cms_text.append('{0}\'{1}\': gettext(\'{2}\'),'.format(spacer * 3, config_name, config_value)) # NOQA
else:
cms_text.append('{0}\'{1}\': {2},'.format(
spacer * 3, config_name, config_value
))
cms_text.append('{0}}},'.format(spacer * 2))
cms_text.append('{0}],'.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(
['(\'{0}\', \'{1}\')'.format(*item) for item in getattr(vars, cms_templates)]
)
))
text.append('X_FRAME_OPTIONS = \'SAMEORIGIN\'')
text.append('CMS_PERMISSION = {0}'.format(vars.CMS_PERMISSION))
text.append('CMS_PLACEHOLDER_CONF = {0}'.format(vars.CMS_PLACEHOLDER_CONF))
database = ['\'{0}\': {1}'.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{0}{1}\n)'.format(
spacer, (',\n' + spacer).join(
['\'{0}\''.format(var) for var in vars.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[str('DJANGO_SETTINGS_MODULE')] = str('{0}.settings'.format(config_data.project_name))
env[str('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: {0}\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:
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[str('DJANGO_SETTINGS_MODULE')] = str('{0}.settings'.format(config_data.project_name))
env[str('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.{0}'.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[str('DJANGO_SETTINGS_MODULE')] = str('{0}.settings'.format(config_data.project_name))
env[str('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.{0}'.format(ext))
except OSError:
pass