From 620e0e5cc6f8188f80d810d556fcb503603f3b2a Mon Sep 17 00:00:00 2001 From: EsGeh Date: Mon, 11 Nov 2019 14:33:43 +0100 Subject: [PATCH] basic config copied from eoa2-xmldb repo. ran 'django-admin startproject' --- .dockerignore | 3 + .gitignore | 109 ++++++++++++++++++++++++++ Dockerfile | 17 ++++ docker-compose.yaml | 34 ++++++++ requirements.txt | 2 + res/static | 1 + scripts/config/env.conf | 27 +++++++ scripts/config/load_env.fish | 26 +++++++ scripts/config/load_env.sh | 25 ++++++ scripts/exec_in_container.py | 32 ++++++++ scripts/exit.py | 42 ++++++++++ scripts/init.py | 147 +++++++++++++++++++++++++++++++++++ scripts/run.py | 49 ++++++++++++ scripts/stop.py | 28 +++++++ scripts/utils/functions.py | 24 ++++++ scripts/utils/settings.py | 90 +++++++++++++++++++++ src/eoa/__init__.py | 0 src/eoa/settings.py | 120 ++++++++++++++++++++++++++++ src/eoa/urls.py | 21 +++++ src/eoa/wsgi.py | 16 ++++ src/manage.py | 21 +++++ 21 files changed, 834 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 docker-compose.yaml create mode 100644 requirements.txt create mode 120000 res/static create mode 100644 scripts/config/env.conf create mode 100755 scripts/config/load_env.fish create mode 100755 scripts/config/load_env.sh create mode 100755 scripts/exec_in_container.py create mode 100755 scripts/exit.py create mode 100755 scripts/init.py create mode 100755 scripts/run.py create mode 100755 scripts/stop.py create mode 100644 scripts/utils/functions.py create mode 100644 scripts/utils/settings.py create mode 100644 src/eoa/__init__.py create mode 100644 src/eoa/settings.py create mode 100644 src/eoa/urls.py create mode 100644 src/eoa/wsgi.py create mode 100755 src/manage.py diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..10c44f4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +/runtime_data +/scripts +/env diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab33683 --- /dev/null +++ b/.gitignore @@ -0,0 +1,109 @@ + +# manual entries: +/dependencies/ +/runtime_data/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..554f7fe --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM python:3 + +ENV PYTHONUNBUFFERED 1 +ENV SHELL /bin/bash + +# ------------------------------------------ +# install necessary packages via apt-get: +# ------------------------------------------ +# RUN apt-get update && \ +# apt-get install -y --no-install-recommends \ +# libsaxonhe-java + +# ------------------------------------------ +# install python dependencies: +# ------------------------------------------ +COPY requirements.txt "$INSTALL_DIR/" +RUN pip install -r requirements.txt diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..cfa4c6c --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,34 @@ +version: '3.7' + +services: + + db: + image: postgres:11 + volumes: + - ./${DATABASE_DATA_DIR}:${DATABASE_DATA_DIR_IN_CONTAINER} + container_name: ${DB_CONTAINER} + user: ${USER}:${GROUP} + environment: + # postgres data dir + - PGDATA=${DATABASE_DATA_DIR_IN_CONTAINER} + # postgres user + - POSTGRES_USER=postgres + + # eoa-django + webserver: + depends_on: + - db + build: . + image: ${WEB_SERVER_IMAGE} + container_name: ${WEB_SERVER_CONTAINER} + command: python3 manage.py runserver 0.0.0.0:8000 + volumes: + - ./${SRC_DIR}:${SRC_DIR_IN_CONTAINER} + - ./${RES_DIR_RUNTIME}:${RES_DIR_IN_CONTAINER} + working_dir: ${SRC_DIR_IN_CONTAINER} + environment: + - INSTALL_DIR=${SRC_DIR_IN_CONTAINER} + - RES_DIR=${RES_DIR_IN_CONTAINER} + ports: + - "8000:8000" + user: ${USER}:${GROUP} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..589c526 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +Django==2.2 +psycopg2-binary diff --git a/res/static b/res/static new file mode 120000 index 0000000..982b54e --- /dev/null +++ b/res/static @@ -0,0 +1 @@ +../dependencies/webdesign_platform/dist \ No newline at end of file diff --git a/scripts/config/env.conf b/scripts/config/env.conf new file mode 100644 index 0000000..dcd6948 --- /dev/null +++ b/scripts/config/env.conf @@ -0,0 +1,27 @@ +[config] + +################################################### +# container names: +WEB_SERVER_CONTAINER=eoa_1_5_webserver +DB_CONTAINER=db +WEB_SERVER_IMAGE=eoa_1_5_webserver + +################################################### +# volumes: + +RUNTIME_DIR=runtime_data + +DATABASE_DATA_DIR=${RUNTIME_DIR}/postgres_data +DATABASE_DATA_DIR_IN_CONTAINER=/eoa/postgres_data + +SRC_DIR=src +SRC_DIR_IN_CONTAINER=/eoa/server + +RES_DIR=res +RES_DIR_RUNTIME=${RUNTIME_DIR}/res.rt +RES_DIR_IN_CONTAINER=/eoa/res + +################################################### +# user and group: +# USER=$(id -u) +# GROUP=$(id -g) diff --git a/scripts/config/load_env.fish b/scripts/config/load_env.fish new file mode 100755 index 0000000..6aa711b --- /dev/null +++ b/scripts/config/load_env.fish @@ -0,0 +1,26 @@ +#!/bin/fish + +if not set -q SCRIPT_DIR + echo "ERROR in (status -m): \$SCRIPT_DIR is undefined!" + exit 1 +end +set BASE_DIR "$SCRIPT_DIR/.." + +####################################### +# actual script +####################################### + +set RUNTIME_DIR "runtime_data" + +################################################### +# export all these variables, so +# they can be referenced in the docker-compose.yaml +################################################### + +# convert bash syntax to fish syntax: +set commands (cat ./scripts/config/env.conf | grep -E --invert-match '^#|^[[:space:]]*$' | sed 's/^\(.*\)=\(.*\)/set -x \1 \2/' | sed 's/\$(/(/g') + +for cmd in $commands + echo "cmd: $cmd" + eval "$cmd" +end diff --git a/scripts/config/load_env.sh b/scripts/config/load_env.sh new file mode 100755 index 0000000..d3b40b3 --- /dev/null +++ b/scripts/config/load_env.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +if [[ "$SCRIPT_DIR" == "" ]]; then + echo "ERROR in ./scripts/config/load_env.sh: \$SCRIPT_DIR is undefined!" + exit 1 +fi +BASE_DIR="$SCRIPT_DIR/.." + +####################################### +# actual script +####################################### + + +# these are the default settings used by all scripts: + +RUNTIME_DIR=runtime_data + +################################################### +# export all these variables, so +# they can be referenced in the docker-compose.yaml +################################################### +set -a +source $SCRIPT_DIR/config/env.conf +set -a +# (stop exporting variables) diff --git a/scripts/exec_in_container.py b/scripts/exec_in_container.py new file mode 100755 index 0000000..6fe1480 --- /dev/null +++ b/scripts/exec_in_container.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +from utils.settings import BASE_DIR, load_config +from utils.functions import exec_in_container + +from pathlib import Path +import os + +if __name__ == '__main__': + + from argparse import ArgumentParser + + config = load_config() + + parser = ArgumentParser( + description="open bash to the container, optionally exec CMD there" + ) + parser.add_argument( + "CMD", + nargs='*', + ) + args = parser.parse_args() + + CMDS= \ + (vars(args)['CMD']) + + print( CMDS ) + + exec_in_container( + *CMDS, + env=config + ) diff --git a/scripts/exit.py b/scripts/exit.py new file mode 100755 index 0000000..b7ebbb4 --- /dev/null +++ b/scripts/exit.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 + +from utils.settings import BASE_DIR, load_config + +from pathlib import Path +import shutil +import os + +def rm_dirs( config ): + path = Path( BASE_DIR, config['RUNTIME_DIR'] ) + print( "removing dir '{}'".format( path ) ) + shutil.rmtree( + path, + ignore_errors=True + ) + # os.system('rm -rf "{}"'.format( path )) + +def rm_docker_env_file( config ): + path = Path( + BASE_DIR / ".env" + ) + print( "removing '{}'".format( path ) ) + path.unlink() + + +if __name__ == '__main__': + + from argparse import ArgumentParser + + config = load_config() + + parser = ArgumentParser( + description="clean up the repository: remove xml database, remove sql database" + ) + args = parser.parse_args() + + from stop import stop + + stop( config ) + + rm_dirs( config ) + rm_docker_env_file( config ) diff --git a/scripts/init.py b/scripts/init.py new file mode 100755 index 0000000..c0e94e4 --- /dev/null +++ b/scripts/init.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 + +from utils.settings import BASE_DIR, load_config, create_docker_env_file +from utils.functions import exec_in_container + +from pathlib import Path +import shlex +import shutil +import pwd +import os +import subprocess +from time import sleep + +BASE_DIR = Path( __file__ ).parent.parent +DEP_DIR = BASE_DIR / "dependencies" + + +SQLDB_WAIT_TIME=20 + +def create_dir(dir): + print( "creating dir '{}'".format( dir ) ) + Path(dir).mkdir( + parents = True, + exist_ok = True + ) + +def copy_dir(src, dst): + print( "'{}' -> '{}'".format( src, dst ) ) + if Path(dst).exists(): + shutil.rmtree( + dst + ) + shutil.copytree( + src=src, + dst=dst + ) + +def create_dirs( config ): + create_dir( + BASE_DIR / config['DATABASE_DATA_DIR'] + ) + + """ + this extra copy is necessary in order to + resolve symlinks. Docker won't resolve + them. + """ + copy_dir( + src=config['RES_DIR'], + dst=config['RES_DIR_RUNTIME'] + ) + +def install_git_dep( + repo_name, + repo_uri, + repo_hash, + init_script = None +): + if (DEP_DIR / repo_name).exists(): + shutil.rmtree( DEP_DIR / repo_name ) + + subprocess.check_call( + ["git", "clone", repo_uri, DEP_DIR / repo_name] + ) + subprocess.check_call( + ["git", "checkout", repo_hash], + cwd = DEP_DIR / repo_name + ) + if init_script is not None: + subprocess.check_call( + shlex.split( init_script ), + cwd = DEP_DIR / repo_name + ) + +def init_sqldb( config ): + # print( "env in config:" ) + # subprocess.check_call( + # ["env",], + # env=config + # ) + + try: + try: + subprocess.check_call( + ["docker-compose", "up", "-d"], + env=config + ) + + print( "wait {}s for the sql database to get ready... :-P".format( SQLDB_WAIT_TIME ) ) + sleep( SQLDB_WAIT_TIME ) + + exec_in_container( + "python", "manage.py", "makemigrations", + env=config, + ) + exec_in_container( + "python", "manage.py", "migrate", + env=config, + ) + + except: + subprocess.check_call( + ["docker-compose", "logs"], + env=config, + ) + + finally: + subprocess.check_call( + ["docker-compose", "down"], + env=config, + ) + +if __name__ == '__main__': + + from argparse import ArgumentParser + + parser = ArgumentParser( + description="initialize the repository: create directories, initialize xml database, initialize sql database" + ) + parser.parse_args() + + # orig_config_file -> env_file: + create_docker_env_file() + + # load env_file: + config = load_config() + + install_git_dep( + repo_name = "eoa-publication-model", + repo_uri = "https://github.molgen.mpg.de/EditionOpenAccess/eoa-publication-model.git", + repo_hash = "d76a81feef1ebb708a90376d3f5a7eccb51807b0" + ) + install_git_dep( + repo_name = "EOASkripts", + repo_uri = "https://github.molgen.mpg.de/EditionOpenAccess/EOASkripts.git", + repo_hash = "bb0d7bfc6b77bdec391886b90f3f9ad9f2025423" + ) + install_git_dep( + repo_name = "webdesign_platform", + repo_uri = "https://github.molgen.mpg.de/EditionOpenAccess/webdesign_platform.git", + repo_hash = "7a6c3f7c0db224fdcbb046df3f7edad8fefded3f", + init_script = "./scripts/init.sh" + ) + + create_dirs( config ) + + init_sqldb( config ) diff --git a/scripts/run.py b/scripts/run.py new file mode 100755 index 0000000..dc49b43 --- /dev/null +++ b/scripts/run.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 + +from utils.settings import BASE_DIR, load_config +# from utils.functions import exec_in_xmldb + +import subprocess +import shlex +import os +from time import sleep + +def run( + env, + cmd=[] +): + if len(cmd) > 0: + # run application + subprocess.call( + shlex.split("docker-compose run webserver") + cmd, + env=env + ) + else: + # run application + subprocess.call( + shlex.split("docker-compose up -d"), + env=env + ) + +if __name__ == '__main__': + + from argparse import ArgumentParser + + config = load_config() + + parser = ArgumentParser( + description="run the webserver" + ) + parser.add_argument( + "CMD", + nargs="*" + ) + + args = parser.parse_args() + + CMD= \ + (vars(args)['CMD']) + run( + env=config, + cmd=CMD + ) diff --git a/scripts/stop.py b/scripts/stop.py new file mode 100755 index 0000000..2ff879a --- /dev/null +++ b/scripts/stop.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +from utils.settings import BASE_DIR, load_config + +import subprocess +import os + + +def stop( env ): + # run application + subprocess.call( + "docker-compose down", + shell=True, + env=env + ) + +if __name__ == '__main__': + + from argparse import ArgumentParser + + config = load_config() + + parser = ArgumentParser( + description="stop the webserver" + ) + args = parser.parse_args() + + stop( config ) diff --git a/scripts/utils/functions.py b/scripts/utils/functions.py new file mode 100644 index 0000000..fd39858 --- /dev/null +++ b/scripts/utils/functions.py @@ -0,0 +1,24 @@ + + +import subprocess +import shlex + + +def exec_in_container( + *args, + env +): + from functools import reduce + cmd = \ + [ "docker-compose", "exec", "webserver", "bash" ] + + if len(args) is not 0: + cmd.append( "-c" ) + cmd.append( + reduce(lambda x,y: x + " " + y, args ) + ) + print( "cmd: " + str(cmd) ) + subprocess.check_call( + cmd, + env=env + ) diff --git a/scripts/utils/settings.py b/scripts/utils/settings.py new file mode 100644 index 0000000..d81ca9e --- /dev/null +++ b/scripts/utils/settings.py @@ -0,0 +1,90 @@ +from pathlib import Path +from configparser import ConfigParser, ExtendedInterpolation + +from collections.abc import MutableMapping +import shlex +import os + +BASE_DIR = Path( __file__ ).parent.parent.parent +SCRIPT_DIR = Path( __file__ ).parent.parent + +orig_config_file = SCRIPT_DIR / "config" / "env.conf" +env_file = BASE_DIR / ".env" + + +def load_config( + include_local_path=True +): + from itertools import chain + parser = ConfigParser() + parser.optionxform=str + with open(env_file) as lines: + lines = chain(("[config]",), lines) # This line does the trick. + parser.read_file(lines) + print( "Loaded from {}:".format( env_file ) ) + for k,v in parser['config'].items() : + print( "{k}={v}".format( k = k, v = v ) ) + print( "------------------------------" ) + config = dict(parser['config']) + if include_local_path: + path = os.environ['PATH'] + config = { "PATH": path, ** config } + return config + +def create_docker_env_file(): + import subprocess + user = subprocess.check_output( + shlex.split("id -u"), + universal_newlines=True + ).rstrip("\n") + group = subprocess.check_output( + shlex.split("id -g"), + universal_newlines=True + ).rstrip("\n") + + print( "user id: {}, group id: {}".format( user, group) ) + print( "writing docker .env file to {}, appended user and group".format( env_file ) ) + config = __load_orig_config( orig_config_file ) + config['config']['USER'] = user + config['config']['GROUP'] = group + __write_as_env( + config, + env_file + ) + +def __load_orig_config( filename ): + config = ConfigParser( + interpolation = ExtendedInterpolation() + ) + # make keys case sensitive + config.optionxform=str + # print( "path: %s" % orig_config_file ) + config.read( orig_config_file ) + print( "Loaded from '{}':".format( orig_config_file ) ) + for k,v in config['config'].items() : + print( "{k}={v}".format( k = k, v = v ) ) + print( "------------------------------" ) + return config + + +def __write_as_env( cfg, filename ): + # create a new config parser with the right output options: + # and copy the fields into it: + config = ConfigParser() + config.optionxform=str + config['config'] = {} + for k, v in cfg.items('config'): + config['config'][k] = v + # write the file: + with open(filename, "w") as f: + config.write( + f, + space_around_delimiters=False + ) + # skip the section header '[ bla ]' + with open( filename, "r+" ) as f: + d = f.readlines() + f.seek(0) + for l in d[1:]: + f.write(l) + f.truncate() diff --git a/src/eoa/__init__.py b/src/eoa/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/eoa/settings.py b/src/eoa/settings.py new file mode 100644 index 0000000..b551fcc --- /dev/null +++ b/src/eoa/settings.py @@ -0,0 +1,120 @@ +""" +Django settings for eoa project. + +Generated by 'django-admin startproject' using Django 2.2. + +For more information on this file, see +https://docs.djangoproject.com/en/2.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/2.2/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '+n=px9r7thf$ya+!b0c%61!qnfpmhw1oy3=_#y$6^1ko&=&coa' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'eoa.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'eoa.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/2.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/2.2/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/2.2/howto/static-files/ + +STATIC_URL = '/static/' diff --git a/src/eoa/urls.py b/src/eoa/urls.py new file mode 100644 index 0000000..e7ddb02 --- /dev/null +++ b/src/eoa/urls.py @@ -0,0 +1,21 @@ +"""eoa URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/2.2/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path + +urlpatterns = [ + path('admin/', admin.site.urls), +] diff --git a/src/eoa/wsgi.py b/src/eoa/wsgi.py new file mode 100644 index 0000000..abffc02 --- /dev/null +++ b/src/eoa/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for eoa project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'eoa.settings') + +application = get_wsgi_application() diff --git a/src/manage.py b/src/manage.py new file mode 100755 index 0000000..f5c1380 --- /dev/null +++ b/src/manage.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'eoa.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main()