cookiecutter-django/hooks/post_gen_project.py
Nikita Shupeyko 86e33e8714
Refactor *_gen_project hooks (#1490)
* Fix ./hooks/pre_gen_project.py asking user to select an option once only

+ prettify output

* Fix pre_gen hook not really exiting when it should

* Refactor & prettify ./hooks/post_gen_project.py

* Ensure same POSTGRES_USER is set across environments

+ get rid of env.example in favor of pre-generated .env.
2018-02-07 22:52:52 +03:00

284 lines
7.7 KiB
Python

"""
NOTE:
the below code is to be maintained Python 2.x-compatible
as the whole Cookiecutter Django project initialization
can potentially be run in Python 2.x environment
(at least so we presume in `pre_gen_project.py`).
TODO: ? restrict Cookiecutter Django project initialization to Python 3.x environments only
"""
import os
import random
import shutil
import string
import sys
try:
# Inspired by
# https://github.com/django/django/blob/master/django/utils/crypto.py
random = random.SystemRandom()
using_sysrandom = True
except NotImplementedError:
using_sysrandom = False
PROJECT_DIR_PATH = os.path.realpath(os.path.curdir)
def remove_file(file_path):
if os.path.exists(file_path):
os.remove(file_path)
def remove_open_source_project_only_files():
file_names = [
'CONTRIBUTORS.txt',
]
for file_name in file_names:
os.remove(os.path.join(PROJECT_DIR_PATH, file_name))
def remove_gplv3_files():
file_names = [
'COPYING',
]
for file_name in file_names:
os.remove(os.path.join(PROJECT_DIR_PATH, file_name))
def remove_pycharm_files():
idea_dir_path = os.path.join(PROJECT_DIR_PATH, '.idea')
if os.path.exists(idea_dir_path):
shutil.rmtree(idea_dir_path)
docs_dir_path = os.path.join(PROJECT_DIR_PATH, 'docs', 'pycharm')
if os.path.exists(docs_dir_path):
shutil.rmtree(docs_dir_path)
def remove_docker_files():
shutil.rmtree(os.path.join(PROJECT_DIR_PATH, 'compose'))
file_names = [
'local.yml',
'production.yml',
'.dockerignore',
]
for file_name in file_names:
os.remove(os.path.join(PROJECT_DIR_PATH, file_name))
def remove_heroku_files():
file_names = [
'Procfile',
'runtime.txt',
]
for file_name in file_names:
remove_file(os.path.join(PROJECT_DIR_PATH, file_name))
def remove_paas_files():
none_paas_files_left = True
if '{{ cookiecutter.use_heroku }}'.lower() == 'n':
remove_heroku_files()
none_paas_files_left &= True
else:
none_paas_files_left &= False
if none_paas_files_left:
remove_file(os.path.join(PROJECT_DIR_PATH, 'requirements.txt'))
def remove_grunt_files():
file_names = [
'Gruntfile.js',
]
for file_name in file_names:
os.remove(os.path.join(PROJECT_DIR_PATH, file_name))
def remove_gulp_files():
file_names = [
'gulpfile.js',
]
for file_name in file_names:
os.remove(os.path.join(PROJECT_DIR_PATH, file_name))
def remove_packagejson_file():
file_names = [
'package.json',
]
for file_name in file_names:
os.remove(os.path.join(PROJECT_DIR_PATH, file_name))
def remove_celery_app():
shutil.rmtree(os.path.join(PROJECT_DIR_PATH, '{{ cookiecutter.project_slug }}', 'taskapp'))
def append_to_project_gitignore(path):
gitignore_file_path = os.path.join(PROJECT_DIR_PATH, '.gitignore')
with open(gitignore_file_path, 'a') as gitignore_file:
gitignore_file.write(path)
gitignore_file.write(os.linesep)
def generate_random_string(length,
using_digits=False,
using_ascii_letters=False,
using_punctuation=False):
"""
Example:
opting out for 50 symbol-long, [a-z][A-Z][0-9] string
would yield log_2((26+26+50)^50) ~= 334 bit strength.
"""
if not using_sysrandom:
return None
symbols = []
if using_digits:
symbols += string.digits
if using_ascii_letters:
symbols += string.ascii_letters
if using_punctuation:
symbols += string.punctuation \
.replace('"', '') \
.replace("'", '') \
.replace('\\', '')
return ''.join([random.choice(symbols) for _ in range(length)])
def set_flag(file_path,
flag,
value=None,
*args,
**kwargs):
if value is None:
random_string = generate_random_string(*args, **kwargs)
if random_string is None:
import sys
sys.stdout.write(
"We couldn't find a secure pseudo-random number generator on your system. "
"Please, make sure to manually {} later.".format(flag)
)
random_string = flag
value = random_string
with open(file_path, 'r+') as f:
file_contents = f.read().replace(flag, value)
f.seek(0)
f.write(file_contents)
f.truncate()
return value
def set_django_secret_key(file_path):
django_secret_key = set_flag(
file_path,
'!!!SET DJANGO_SECRET_KEY!!!',
length=50,
using_digits=True,
using_ascii_letters=True
)
return django_secret_key
def set_postgres_user(file_path,
value=None):
postgres_user = set_flag(
file_path,
'!!!SET POSTGRES_USER!!!',
value=value,
length=8,
using_ascii_letters=True
)
return postgres_user
def set_postgres_password(file_path):
postgres_password = set_flag(
file_path,
'!!!SET POSTGRES_PASSWORD!!!',
length=42,
using_digits=True,
using_ascii_letters=True
)
return postgres_password
def initialize_dotenv(postgres_user):
# Initializing `env.example` first.
envexample_file_path = os.path.join(PROJECT_DIR_PATH, 'env.example')
set_django_secret_key(envexample_file_path)
set_postgres_user(envexample_file_path, value=postgres_user)
set_postgres_password(envexample_file_path)
# Renaming `env.example` to `.env`.
dotenv_file_path = os.path.join(PROJECT_DIR_PATH, '.env')
shutil.move(envexample_file_path, dotenv_file_path)
def initialize_localyml(postgres_user):
set_postgres_user(os.path.join(PROJECT_DIR_PATH, 'local.yml'), value=postgres_user)
def initialize_local_settings():
set_django_secret_key(os.path.join(PROJECT_DIR_PATH, 'config', 'settings', 'local.py'))
def initialize_test_settings():
set_django_secret_key(os.path.join(PROJECT_DIR_PATH, 'config', 'settings', 'test.py'))
def main():
postgres_user = generate_random_string(length=16, using_ascii_letters=True)
initialize_dotenv(postgres_user)
initialize_localyml(postgres_user)
initialize_local_settings()
initialize_test_settings()
if '{{ cookiecutter.open_source_license }}' == 'Not open source':
remove_open_source_project_only_files()
elif '{{ cookiecutter.open_source_license}}' != 'GPLv3':
remove_gplv3_files()
if '{{ cookiecutter.use_pycharm }}'.lower() == 'n':
remove_pycharm_files()
if '{{ cookiecutter.use_docker }}'.lower() == 'n':
remove_docker_files()
remove_paas_files()
if '{{ cookiecutter.js_task_runner}}'.lower() == 'gulp':
remove_grunt_files()
elif '{{ cookiecutter.js_task_runner}}'.lower() == 'grunt':
remove_gulp_files()
else:
remove_gulp_files()
remove_grunt_files()
remove_packagejson_file()
if '{{ cookiecutter.js_task_runner }}'.lower() in ['grunt', 'gulp'] \
and '{{ cookiecutter.use_docker }}'.lower() == 'y':
TERMINATOR = "\x1b[0m"
INFO = "\x1b[1;33m [INFO]: "
sys.stdout.write(
INFO +
"Docker and {} JS task runner ".format('{{ cookiecutter.js_task_runner }}'.lower().capitalize()) +
"working together not supported yet. "
"You can continue using the generated project like you normally would, "
"however you would need to add a JS task runner service "
"to your Docker Compose configuration manually." +
TERMINATOR
)
if '{{ cookiecutter.use_celery }}'.lower() == 'n':
remove_celery_app()
if __name__ == '__main__':
main()