import argparse import datetime import importlib import os import sys from .config import DJANGO_SETTINGS_DIR from .utils import get_root_urlconf from .version import VERSION class Program(object): # pylint: disable=too-few-public-methods @staticmethod def _setup_argparser(parser): parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + VERSION) parser.add_argument('-a', '--app', action='append', dest='merge_apps', metavar='MODULE', help='Merge settings from the django app MODULE\n' 'Can be used multiple times') parser.add_argument('-c', '--create', action='store_true', dest='create', help='Create the django project directory') parser.add_argument('project_dir', metavar='PATH', help='The directory, where the django project is or will be installed.') return parser def _parse_args(self, argv=None): if argv is None: argv = sys.argv[1:] if not argv: argv = () return self._argparser.parse_args(argv) @staticmethod def _install_django_files(project_dir, overwrite=False): if os.path.exists(project_dir): if not overwrite: sys.stderr.write('directory already exists: {}\n'.format(project_dir)) return os.EX_NOPERM else: os.makedirs(project_dir) settings_module = DJANGO_SETTINGS_DIR cmd = 'django-admin startproject {name} {path}'.format(name=settings_module, path=project_dir) sys.stdout.write('Installing django files to {path}\n'.format(path=project_dir)) exitval = os.system(cmd) return exitval @staticmethod def _append_to_pythonfile(path, text): py2_cache = path + 'c' with open(path, 'a') as f: f.write(text) if os.path.isfile(py2_cache): os.unlink(py2_cache) # pragma: no cover def _append_to_settings(self, project_dir, code, comment): settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_DIR) settings_file = os.path.join(settings_dir, 'settings.py') text = '\n' + comment + '\n' + code + '\n' return self._append_to_pythonfile(settings_file, text) def _append_to_urlconf(self, project_dir, code, comment): settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_DIR) settings_file = os.path.join(settings_dir, 'urls.py') text = '\n' + comment + '\n' + code + '\n' return self._append_to_pythonfile(settings_file, text) def _add_installed_apps_from_app(self, project_dir, app): settings_from_app = importlib.import_module('{}.django_settings'.format(app)) if hasattr(settings_from_app, 'ADD_INSTALLED_APPS'): wanted_apps = settings_from_app.ADD_INSTALLED_APPS else: wanted_apps = [] if wanted_apps: sys.stdout.write('{app}: wanted apps: {apps}\n'.format(app=app, apps=', '.join(wanted_apps))) else: return os.EX_OK settings_dir = os.path.join(project_dir, DJANGO_SETTINGS_DIR) sys.path.insert(0, settings_dir) settings = importlib.import_module('settings') sys.path.pop(0) already_apps = [] missing_apps = [] for wanted_app in wanted_apps: if wanted_app in settings.INSTALLED_APPS: already_apps.append(wanted_app) else: missing_apps.append(wanted_app) if already_apps: sys.stdout.write('{app}: already in settings.INSTALLED_APPS:' ' {apps}\n'.format(app=app, apps=', '.join(already_apps))) if missing_apps: sys.stdout.write('{app}: adding to settings.INSTALLED_APPS:' ' {apps}\n'.format(app=app, apps=', '.join(missing_apps))) code = 'INSTALLED_APPS += [\n' for missing_app in missing_apps: code += ' \'{}\',\n'.format(missing_app) code += ']\n' timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') comment = '### {app}: added apps with django-deploy at {timestamp}'.format(app=app, timestamp=timestamp) self._append_to_settings(project_dir, code, comment) else: sys.stdout.write('{app}: all wanted apps are already in settings.INSTALLED_APPS\n'.format(app=app)) return os.EX_OK def _add_urlpatterns_from_app(self, project_dir, app): # pylint: disable=too-many-locals,too-many-branches settings_from_app = importlib.import_module('{}.django_settings'.format(app)) if hasattr(settings_from_app, 'ADD_URLPATTERNS'): wanted_urls = {} for wanted in settings_from_app.ADD_URLPATTERNS: wanted_urls[wanted['pattern']] = wanted wanted_patterns = wanted_urls.keys() else: wanted_urls = {} wanted_patterns = [] if wanted_urls: sys.stdout.write('{app}: wanted urlpatterns: {urls}\n'.format(app=app, urls=', '.join(wanted_patterns))) else: return os.EX_OK root_urlconf = get_root_urlconf(project_dir) already_patterns = [] for item in root_urlconf.urlpatterns: if hasattr(item, 'pattern'): pattern = str(item.pattern) else: pattern = item.regex.pattern if pattern in wanted_patterns: already_patterns.append(pattern) missing_patterns = [pattern for pattern in wanted_patterns if pattern not in already_patterns] if already_patterns: sys.stdout.write('{app}: already in urlpatterns:' ' {urls}\n'.format(app=app, urls=', '.join(already_patterns))) if missing_patterns: sys.stdout.write('{app}: adding to urlpatterns:' ' {apps}\n'.format(app=app, apps=', '.join(missing_patterns))) code = '' if not hasattr(root_urlconf, 'url'): code += 'from django.conf.urls import url\n' if not hasattr(root_urlconf, 'include'): code += 'from django.conf.urls import include\n' code += 'urlpatterns += [\n' for pattern in missing_patterns: url = wanted_urls[pattern] if url['type'] == 'include': code += " url(r'{pattern}', include('{module}')),\n".format(pattern=pattern, module=url['module']) else: sys.stderr.write('{app}: not a supported url type: {type}\n'.format(app=app, type=url['type'])) code += ']\n' timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') comment = '### {app}: added urlpatterns with django-deploy at {timestamp}'.format(app=app, timestamp=timestamp) self._append_to_urlconf(project_dir, code, comment) else: sys.stdout.write('{app}: all wanted patterns are already in urlconfig\n'.format(app=app)) return os.EX_OK def _merge_app(self, project_dir, app): exitval = self._add_installed_apps_from_app(project_dir, app) if exitval != os.EX_OK: return exitval exitval = self._add_urlpatterns_from_app(project_dir, app) return exitval def __init__(self): self._argparser = argparse.ArgumentParser() self._setup_argparser(self._argparser) def __call__(self, *args, **kwargs): argv = kwargs.get('argv', None) cmd_args = self._parse_args(argv) exitval = os.EX_OK if cmd_args.create: exitval = self._install_django_files(cmd_args.project_dir) if exitval != os.EX_OK: return exitval if cmd_args.merge_apps: for app in cmd_args.merge_apps: exitval = self._merge_app(cmd_args.project_dir, app) if exitval != os.EX_OK: return exitval return exitval