From d3c4dd8c9c9d5b10458826e289f82e9dd0559107 Mon Sep 17 00:00:00 2001 From: Krystian Hanek Date: Sat, 3 Mar 2018 15:16:55 +0100 Subject: [PATCH 1/2] Add docker configuration for each enviroment --- .env | 15 ++ .env_example | 15 ++ .gitignore | 5 +- .travis.yml | 15 +- docker-compose.yml | 22 +++ pytest.ini | 3 - requirements.txt | 4 - requirements/base.txt | 7 + requirements/local.txt | 4 + requirements/prod.txt | 2 + requirements/qa.txt | 2 + requirements/test.txt | 8 + requirements_test.txt | 5 - restauth/settings.py | 140 ------------------ restauth/tests/factories.py | 20 --- src/Dockerfile | 23 +++ .../locale}/error_codes/LC_MESSAGES/django.mo | Bin .../locale}/error_codes/LC_MESSAGES/django.po | 0 manage.py => src/manage.py | 5 +- src/pytest.ini | 5 + {restauth => src/restauth}/__init__.py | 0 {restauth => src/restauth}/admin.py | 0 {restauth => src/restauth}/apps.py | 1 + {restauth => src/restauth}/jwt.py | 0 .../restauth}/migrations/0001_initial.py | 0 .../restauth}/migrations/0002_userprofile.py | 0 .../restauth}/migrations/__init__.py | 0 {restauth => src/restauth}/models.py | 0 {restauth => src/restauth}/notifications.py | 0 {restauth => src/restauth}/serializers.py | 0 {restauth => src/restauth}/tests/__init__.py | 0 {restauth => src/restauth}/tests/conftest.py | 0 src/restauth/tests/factories.py | 30 ++++ .../restauth/tests/test_views.py | 5 +- {restauth => src/restauth}/tokens.py | 0 {restauth => src/restauth}/urls.py | 3 +- {restauth => src/restauth}/utils.py | 0 {restauth => src/restauth}/views.py | 0 src/scripts/entrypoint.sh | 30 ++++ src/scripts/run_local.sh | 6 + src/scripts/run_prod.sh | 7 + src/scripts/run_qa.sh | 10 ++ src/scripts/run_test.sh | 5 + src/settings/__init__.py | 0 src/settings/base.py | 134 +++++++++++++++++ src/settings/configs.py | 33 +++++ src/settings/security.py | 15 ++ {restauth => src/settings}/wsgi.py | 5 +- src/uwsgi.ini | 42 ++++++ 49 files changed, 442 insertions(+), 184 deletions(-) create mode 100644 .env create mode 100644 .env_example create mode 100644 docker-compose.yml delete mode 100644 pytest.ini delete mode 100644 requirements.txt create mode 100644 requirements/base.txt create mode 100644 requirements/local.txt create mode 100644 requirements/prod.txt create mode 100644 requirements/qa.txt create mode 100644 requirements/test.txt delete mode 100644 requirements_test.txt delete mode 100644 restauth/settings.py delete mode 100644 restauth/tests/factories.py create mode 100644 src/Dockerfile rename {locale => src/locale}/error_codes/LC_MESSAGES/django.mo (100%) rename {locale => src/locale}/error_codes/LC_MESSAGES/django.po (100%) rename manage.py => src/manage.py (76%) mode change 100644 => 100755 create mode 100644 src/pytest.ini rename {restauth => src/restauth}/__init__.py (100%) rename {restauth => src/restauth}/admin.py (100%) rename {restauth => src/restauth}/apps.py (74%) rename {restauth => src/restauth}/jwt.py (100%) rename {restauth => src/restauth}/migrations/0001_initial.py (100%) rename {restauth => src/restauth}/migrations/0002_userprofile.py (100%) rename {restauth => src/restauth}/migrations/__init__.py (100%) rename {restauth => src/restauth}/models.py (100%) rename {restauth => src/restauth}/notifications.py (100%) rename {restauth => src/restauth}/serializers.py (100%) rename {restauth => src/restauth}/tests/__init__.py (100%) rename {restauth => src/restauth}/tests/conftest.py (100%) create mode 100644 src/restauth/tests/factories.py rename restauth/tests/tests.py => src/restauth/tests/test_views.py (97%) rename {restauth => src/restauth}/tokens.py (100%) rename {restauth => src/restauth}/urls.py (90%) rename {restauth => src/restauth}/utils.py (100%) rename {restauth => src/restauth}/views.py (100%) create mode 100644 src/scripts/entrypoint.sh create mode 100755 src/scripts/run_local.sh create mode 100755 src/scripts/run_prod.sh create mode 100755 src/scripts/run_qa.sh create mode 100755 src/scripts/run_test.sh create mode 100644 src/settings/__init__.py create mode 100644 src/settings/base.py create mode 100644 src/settings/configs.py create mode 100644 src/settings/security.py rename {restauth => src/settings}/wsgi.py (61%) create mode 100644 src/uwsgi.ini diff --git a/.env b/.env new file mode 100644 index 0000000..1b2d455 --- /dev/null +++ b/.env @@ -0,0 +1,15 @@ +# Database config +POSTGRES_DB=backend +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_PORT=5432 +POSTGRES_HOST=db + +# Django config +DJANGO_SECRET_KEY=4y7c-7+bja2@e1$%d@6v+#dir%70!dc7!7_04e2r(d%4$g7+id +DJANGO_HASHID_FIELD_SALT='9q#3t$5gs9ob682b@(6^fdv2kg*0ztr(3doa((w&kyq!d8rbt^' +DJANGO_SETTINGS_MODULE=settings.configs +DJANGO_ALLOWED_HOSTS=localhost +DJANGO_DEBUG=False +DJANGO_CONFIGURATION=Prod +DJANGO_ADMIN_URL=admin \ No newline at end of file diff --git a/.env_example b/.env_example new file mode 100644 index 0000000..abea164 --- /dev/null +++ b/.env_example @@ -0,0 +1,15 @@ +# Database config +POSTGRES_DB=backend +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_PORT=5432 +POSTGRES_HOST=db + +# Django config +DJANGO_SECRET_KEY=4y7c-7+bja2@e1$%d@6v+#dir%70!dc7!7_04e2r(d%4$g7+id +DJANGO_HASHID_FIELD_SALT='9q#3t$5gs9ob682b@(6^fdv2kg*0ztr(3doa((w&kyq!d8rbt^' +DJANGO_SETTINGS_MODULE=settings.configs +DJANGO_ALLOWED_HOSTS=localhost +DJANGO_DEBUG=False +DJANGO_CONFIGURATION=Local +DJANGO_ADMIN_URL=admin \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5f2123d..8d421c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ +db.sqlite3 +.env +.idea/ __pycache__/ *.py[cod] -.idea/ -db.sqlite3 .pytest_cache/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index b916387..18aa327 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,16 @@ +sudo: required +services: + - docker language: python python: - "3.6" -install: - - pip install -r requirements_test.txt +before_install: + - docker-compose -v + - docker -v script: - - pytest \ No newline at end of file + - docker-compose run --rm backend scripts/run_test.sh + - docker-compose run --rm backend python manage.py makemigrations --dry-run --check || { echo "ERROR Migration listed above have not been created"; exit 1; } +notifications: + email: + on_success: change + on_failure: always \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..aff5eb0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,22 @@ +version: "3" + +services: + backend: + build: + context: . + dockerfile: src/Dockerfile + args: + - ENV_NAME=local + env_file: + - .env + depends_on: + - db + ports: + - "8000:8000" + command: scripts/run_local.sh + volumes: + - ./src:/app/backend + db: + image: postgres:10.3-alpine + env_file: + - .env diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 5cbeef8..0000000 --- a/pytest.ini +++ /dev/null @@ -1,3 +0,0 @@ -[pytest] -DJANGO_SETTINGS_MODULE = restauth.settings -python_files = tests.py test_*.py *_tests.py diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 2288429..0000000 --- a/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -Django==2.0.2 -djangorestframework==3.7.7 -djangorestframework-jwt==1.11.0 -django-hashid-field==2.1.0 \ No newline at end of file diff --git a/requirements/base.txt b/requirements/base.txt new file mode 100644 index 0000000..85386b4 --- /dev/null +++ b/requirements/base.txt @@ -0,0 +1,7 @@ +Django==2.0.2 +django-configurations==2.0 +django-hashid-field==2.1.0 +djangorestframework==3.7.7 +djangorestframework-jwt==1.11.0 +dj_database_url==0.5.0 +psycopg2==2.7.4 \ No newline at end of file diff --git a/requirements/local.txt b/requirements/local.txt new file mode 100644 index 0000000..b9fe0ef --- /dev/null +++ b/requirements/local.txt @@ -0,0 +1,4 @@ +-r base.txt +-r prod.txt +-r qa.txt +-r test.txt diff --git a/requirements/prod.txt b/requirements/prod.txt new file mode 100644 index 0000000..2527642 --- /dev/null +++ b/requirements/prod.txt @@ -0,0 +1,2 @@ +-r base.txt +uwsgi==2.0.17 diff --git a/requirements/qa.txt b/requirements/qa.txt new file mode 100644 index 0000000..8c1331b --- /dev/null +++ b/requirements/qa.txt @@ -0,0 +1,2 @@ +-r base.txt +uwsgi==2.0.17 \ No newline at end of file diff --git a/requirements/test.txt b/requirements/test.txt new file mode 100644 index 0000000..7dedb3f --- /dev/null +++ b/requirements/test.txt @@ -0,0 +1,8 @@ +-r base.txt +factory-boy==2.10.0 +flake8==3.5.0 +pytest==3.4.1 +pytest-django==3.1.2 +pytest-env==0.6.2 +pytest-factoryboy==2.0.1 +pytest-sugar==0.9.1 diff --git a/requirements_test.txt b/requirements_test.txt deleted file mode 100644 index f089bb4..0000000 --- a/requirements_test.txt +++ /dev/null @@ -1,5 +0,0 @@ --r requirements.txt -factory-boy==2.10.0 -pytest==3.4.1 -pytest-factoryboy==2.0.1 -pytest-django==3.1.2 \ No newline at end of file diff --git a/restauth/settings.py b/restauth/settings.py deleted file mode 100644 index b4c361e..0000000 --- a/restauth/settings.py +++ /dev/null @@ -1,140 +0,0 @@ -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/1.11/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '4y7c-7+bja2@e1$%d@6v+#dir%70!dc7!7_04e2r(d%4$g7+id' - -# 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', - - 'rest_framework', - - 'restauth', -] - -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 = 'restauth.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 = 'restauth.wsgi.application' - - -# Database -# https://docs.djangoproject.com/en/1.11/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/1.11/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/1.11/topics/i18n/ - -LANGUAGE_CODE = 'error_codes' - -TIME_ZONE = 'UTC' - -USE_I18N = True - -USE_L10N = True - -USE_TZ = True - - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/1.11/howto/static-files/ - -STATIC_URL = '/static/' - -AUTH_USER_MODEL = 'restauth.User' - -LOCALE_PATHS = [ - os.path.join(BASE_DIR, 'locale') -] - -REST_FRAMEWORK = { - 'DEFAULT_PERMISSION_CLASSES': ( - 'rest_framework.permissions.IsAuthenticated', - ), - 'DEFAULT_AUTHENTICATION_CLASSES': ( - 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', - 'rest_framework.authentication.SessionAuthentication', - 'rest_framework.authentication.BasicAuthentication', - ), - 'DEFAULT_THROTTLE_RATES': { - 'anon': '100/day' - } -} - -JWT_AUTH = { - 'JWT_ENCODE_HANDLER': 'restauth.jwt.encode_handler', -} - -HASHID_FIELD_SALT = '9q#3t$5gs9ob682b@(6^fdv2kg*0ztr(3doa((w&kyq!d8rbt^' - -USER_NOTIFICATION_IMPL = 'restauth.notifications.stdout' diff --git a/restauth/tests/factories.py b/restauth/tests/factories.py deleted file mode 100644 index fa5d778..0000000 --- a/restauth/tests/factories.py +++ /dev/null @@ -1,20 +0,0 @@ -import factory - -from .. import models - - -class UserFactory(factory.DjangoModelFactory): - class Meta: - model = models.User - - email = factory.Faker('email') - is_superuser = False - password = factory.PostGenerationMethodCall('set_password', 'secret') - - -class UserProfileFactory(factory.DjangoModelFactory): - class Meta: - model = models.UserProfile - - user = factory.SubFactory(UserFactory) - first_name = factory.Faker('name', locale='pl') diff --git a/src/Dockerfile b/src/Dockerfile new file mode 100644 index 0000000..dc3cf77 --- /dev/null +++ b/src/Dockerfile @@ -0,0 +1,23 @@ +FROM python:3.6-alpine + +ENV APP_DIR /app/backend + +RUN mkdir -p $APP_DIR +WORKDIR $APP_DIR + +RUN apk update && \ + apk add postgresql-libs && \ + apk add --virtual .build-deps build-base gcc linux-headers make python3-dev musl-dev postgresql-dev + +# Available choices: base, prod, qa, test, local +ARG ENV_NAME + +COPY requirements $APP_DIR/requirements +RUN pip install --no-cache-dir -r requirements/${ENV_NAME}.txt && \ + apk --purge del .build-deps + +COPY src $APP_DIR + +ENTRYPOINT ["sh", "/app/backend/scripts/entrypoint.sh"] + +EXPOSE 8000 diff --git a/locale/error_codes/LC_MESSAGES/django.mo b/src/locale/error_codes/LC_MESSAGES/django.mo similarity index 100% rename from locale/error_codes/LC_MESSAGES/django.mo rename to src/locale/error_codes/LC_MESSAGES/django.mo diff --git a/locale/error_codes/LC_MESSAGES/django.po b/src/locale/error_codes/LC_MESSAGES/django.po similarity index 100% rename from locale/error_codes/LC_MESSAGES/django.po rename to src/locale/error_codes/LC_MESSAGES/django.po diff --git a/manage.py b/src/manage.py old mode 100644 new mode 100755 similarity index 76% rename from manage.py rename to src/manage.py index 92f2492..d04b965 --- a/manage.py +++ b/src/manage.py @@ -3,9 +3,10 @@ import sys if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "restauth.settings") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.configs") + os.environ.setdefault('DJANGO_CONFIGURATION', 'Prod') try: - from django.core.management import execute_from_command_line + from configurations.management import execute_from_command_line except ImportError: # The above import may fail for some other reason. Ensure that the # issue is really that Django is missing to avoid masking other diff --git a/src/pytest.ini b/src/pytest.ini new file mode 100644 index 0000000..07f73fd --- /dev/null +++ b/src/pytest.ini @@ -0,0 +1,5 @@ +[pytest] +python_files = tests.py test_*.py *_tests.py +env = + DJANGO_SETTINGS_MODULE = settings.configs + DJANGO_CONFIGURATION = Test diff --git a/restauth/__init__.py b/src/restauth/__init__.py similarity index 100% rename from restauth/__init__.py rename to src/restauth/__init__.py diff --git a/restauth/admin.py b/src/restauth/admin.py similarity index 100% rename from restauth/admin.py rename to src/restauth/admin.py diff --git a/restauth/apps.py b/src/restauth/apps.py similarity index 74% rename from restauth/apps.py rename to src/restauth/apps.py index 6c86dc2..3e36574 100644 --- a/restauth/apps.py +++ b/src/restauth/apps.py @@ -3,3 +3,4 @@ class RestauthConfig(AppConfig): name = 'restauth' + verbose_name = 'Rest Auth' diff --git a/restauth/jwt.py b/src/restauth/jwt.py similarity index 100% rename from restauth/jwt.py rename to src/restauth/jwt.py diff --git a/restauth/migrations/0001_initial.py b/src/restauth/migrations/0001_initial.py similarity index 100% rename from restauth/migrations/0001_initial.py rename to src/restauth/migrations/0001_initial.py diff --git a/restauth/migrations/0002_userprofile.py b/src/restauth/migrations/0002_userprofile.py similarity index 100% rename from restauth/migrations/0002_userprofile.py rename to src/restauth/migrations/0002_userprofile.py diff --git a/restauth/migrations/__init__.py b/src/restauth/migrations/__init__.py similarity index 100% rename from restauth/migrations/__init__.py rename to src/restauth/migrations/__init__.py diff --git a/restauth/models.py b/src/restauth/models.py similarity index 100% rename from restauth/models.py rename to src/restauth/models.py diff --git a/restauth/notifications.py b/src/restauth/notifications.py similarity index 100% rename from restauth/notifications.py rename to src/restauth/notifications.py diff --git a/restauth/serializers.py b/src/restauth/serializers.py similarity index 100% rename from restauth/serializers.py rename to src/restauth/serializers.py diff --git a/restauth/tests/__init__.py b/src/restauth/tests/__init__.py similarity index 100% rename from restauth/tests/__init__.py rename to src/restauth/tests/__init__.py diff --git a/restauth/tests/conftest.py b/src/restauth/tests/conftest.py similarity index 100% rename from restauth/tests/conftest.py rename to src/restauth/tests/conftest.py diff --git a/src/restauth/tests/factories.py b/src/restauth/tests/factories.py new file mode 100644 index 0000000..0c85fc9 --- /dev/null +++ b/src/restauth/tests/factories.py @@ -0,0 +1,30 @@ +import factory +from django.contrib.auth.hashers import make_password + +from .. import models + + +class UserFactory(factory.DjangoModelFactory): + class Meta: + model = models.User + django_get_or_create = ('email',) + + email = factory.Faker('email') + is_superuser = False + + @classmethod + def _create(cls, model_class, **kwargs): + raw_password = kwargs.pop('password', 'secret') + user = super(UserFactory, cls)._create(model_class, + password=make_password(raw_password), + **kwargs) + user._password = raw_password + return user + + +class UserProfileFactory(factory.DjangoModelFactory): + class Meta: + model = models.UserProfile + + user = factory.SubFactory(UserFactory) + first_name = factory.Faker('name', locale='pl') diff --git a/restauth/tests/tests.py b/src/restauth/tests/test_views.py similarity index 97% rename from restauth/tests/tests.py rename to src/restauth/tests/test_views.py index dbc0452..40e23b2 100644 --- a/restauth/tests/tests.py +++ b/src/restauth/tests/test_views.py @@ -96,11 +96,12 @@ def test_token_correct_url(self, api_client, user): class TestChangePassword: - def test_correct_password(self, api_client, user): + def test_correct_password(self, api_client, user_factory): + user = user_factory(password='secret') api_client.force_authenticate(user) response = api_client.post(reverse('change_password'), { 'user': user.pk, - 'old_password': factories.UserFactory.password, + 'old_password': user._password, 'new_password': 'bvbb1234'}) u = dj_auth.get_user_model().objects.get(pk=user.pk) diff --git a/restauth/tokens.py b/src/restauth/tokens.py similarity index 100% rename from restauth/tokens.py rename to src/restauth/tokens.py diff --git a/restauth/urls.py b/src/restauth/urls.py similarity index 90% rename from restauth/urls.py rename to src/restauth/urls.py index 0f45a73..ce408b8 100644 --- a/restauth/urls.py +++ b/src/restauth/urls.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.conf.urls import url from django.contrib import admin from django.urls import path @@ -22,7 +23,7 @@ ] urlpatterns = [ - url(r'^admin/', admin.site.urls), + url(r'^{settings.ADMIN_URL}/'.format(settings=settings), admin.site.urls), path('user/', include(user_patterns)), path('password-reset/', include(password_reset_patterns)) diff --git a/restauth/utils.py b/src/restauth/utils.py similarity index 100% rename from restauth/utils.py rename to src/restauth/utils.py diff --git a/restauth/views.py b/src/restauth/views.py similarity index 100% rename from restauth/views.py rename to src/restauth/views.py diff --git a/src/scripts/entrypoint.sh b/src/scripts/entrypoint.sh new file mode 100644 index 0000000..ea8ce8f --- /dev/null +++ b/src/scripts/entrypoint.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -e +cmd="$@" + +postgres_ready() { + python << END +import sys +import psycopg2 +try: + conn = psycopg2.connect(dbname="$POSTGRES_DB", + user="$POSTGRES_USER", + password="$POSTGRES_PASSWORD", + host="$POSTGRES_HOST", + port="$POSTGRES_PORT") +except psycopg2.OperationalError: + sys.exit(-1) +sys.exit(0) +END +} + +until postgres_ready; do + >&2 echo "PostgreSQL is unavailable - sleeping" + >&2 echo $POSTGRES_DB $POSTGRES_USER $POSTGRES_PASSWORD $POSTGRES_HOST $POSTGRES_PORT + sleep 1 +done + +>&2 echo "PostgreSQL is up - continuing..." + +export DATABASE_URL=postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_USER +exec $cmd \ No newline at end of file diff --git a/src/scripts/run_local.sh b/src/scripts/run_local.sh new file mode 100755 index 0000000..f614c60 --- /dev/null +++ b/src/scripts/run_local.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env sh + +set -x + +python manage.py migrate +python manage.py runserver 0.0.0.0:8000 --settings=settings.configs --configuration=Local \ No newline at end of file diff --git a/src/scripts/run_prod.sh b/src/scripts/run_prod.sh new file mode 100755 index 0000000..104c0d9 --- /dev/null +++ b/src/scripts/run_prod.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env sh + +set -x + +python manage.py check --deploy +python manage.py migrate +uwsgi --ini $APP_DIR/uwsgi.ini \ No newline at end of file diff --git a/src/scripts/run_qa.sh b/src/scripts/run_qa.sh new file mode 100755 index 0000000..16ce4a6 --- /dev/null +++ b/src/scripts/run_qa.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env sh + +set -x + +python manage.py check --deploy +python manage.py migrate + +# Custom scripts for example for creating fake users, data etc. + +uwsgi --ini $APP_DIR/uwsgi.ini \ No newline at end of file diff --git a/src/scripts/run_test.sh b/src/scripts/run_test.sh new file mode 100755 index 0000000..ddf97a6 --- /dev/null +++ b/src/scripts/run_test.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env sh + +set -x + +pytest \ No newline at end of file diff --git a/src/settings/__init__.py b/src/settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/settings/base.py b/src/settings/base.py new file mode 100644 index 0000000..d474f5c --- /dev/null +++ b/src/settings/base.py @@ -0,0 +1,134 @@ +import os + +from configurations import Configuration, values + + +class Base(Configuration): + # 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/1.11/howto/deployment/checklist/ + + # SECURITY WARNING: keep the secret key used in production secret! + SECRET_KEY = values.Value() + + # SECURITY WARNING: don't run with debug turned on in production! + DEBUG = values.BooleanValue(False) + + ALLOWED_HOSTS = values.ListValue([]) + + # Application definition + + INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + 'rest_framework', + + 'restauth.apps.RestauthConfig', + ] + + 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 = 'restauth.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 = 'settings.wsgi.application' + + # Database + # https://docs.djangoproject.com/en/1.11/ref/settings/#databases + + DATABASES = values.DatabaseURLValue() + + # Password validation + # https://docs.djangoproject.com/en/1.11/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/1.11/topics/i18n/ + + LANGUAGE_CODE = 'error_codes' + + TIME_ZONE = 'UTC' + + USE_I18N = True + + USE_L10N = True + + USE_TZ = True + + # Static files (CSS, JavaScript, Images) + # https://docs.djangoproject.com/en/1.11/howto/static-files/ + + STATIC_URL = '/static/' + + AUTH_USER_MODEL = 'restauth.User' + + LOCALE_PATHS = [ + os.path.join(BASE_DIR, 'locale') + ] + + REST_FRAMEWORK = { + 'DEFAULT_PERMISSION_CLASSES': ( + 'rest_framework.permissions.IsAuthenticated', + ), + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', + 'rest_framework.authentication.SessionAuthentication', + 'rest_framework.authentication.BasicAuthentication', + ), + 'DEFAULT_THROTTLE_RATES': { + 'anon': '100/day' + } + } + + JWT_AUTH = { + 'JWT_ENCODE_HANDLER': 'restauth.jwt.encode_handler', + } + + HASHID_FIELD_SALT = values.Value() + + ADMIN_URL = values.Value('admin') + USER_NOTIFICATION_IMPL = 'restauth.notifications.stdout' \ No newline at end of file diff --git a/src/settings/configs.py b/src/settings/configs.py new file mode 100644 index 0000000..fae89b5 --- /dev/null +++ b/src/settings/configs.py @@ -0,0 +1,33 @@ +from . import base +from . import security + +from configurations import values + + +class Prod(security.Security, base.Base): + pass + + +class QA(security.Security, base.Base): + pass + + +class Local(base.Base): + SECRET_KEY = values.Value('secretkey') + HASHID_FIELD_SALT = values.Value('hashidfieldsalt') + + +class Test(base.Base): + DEBUG = False + TEMPLATE_DEBUG = False + PASSWORD_HASHERS = [ + 'django.contrib.auth.hashers.MD5PasswordHasher', # Use fast password hasher so tests run faster + ] + CACHES = { + "default": { + "BACKEND": 'django.core.cache.backends.locmem.LocMemCache', + } + } + SECRET_KEY = values.Value('SECRET_KEY') + HASHID_FIELD_SALT = values.Value('HASHID_FIELD_SALT') + diff --git a/src/settings/security.py b/src/settings/security.py new file mode 100644 index 0000000..8f3bbdc --- /dev/null +++ b/src/settings/security.py @@ -0,0 +1,15 @@ +from configurations import values + + +class Security: + SECURE_HSTS_SECONDS = values.IntegerValue(60) + SECURE_HSTS_INCLUDE_SUBDOMAINS = values.BooleanValue(True) + SECURE_CONTENT_TYPE_NOSNIFF = values.BooleanValue(True) + SECURE_HSTS_PRELOAD = values.BooleanValue(True) + SECURE_BROWSER_XSS_FILTER = values.BooleanValue(True) + SESSION_COOKIE_SECURE = values.BooleanValue(True) + SESSION_COOKIE_HTTPONLY = values.BooleanValue(True) + SECURE_SSL_REDIRECT = values.BooleanValue(True) + CSRF_COOKIE_SECURE = values.BooleanValue(True) + CSRF_COOKIE_HTTPONLY = values.BooleanValue(True) + X_FRAME_OPTIONS = values.Value('DENY') diff --git a/restauth/wsgi.py b/src/settings/wsgi.py similarity index 61% rename from restauth/wsgi.py rename to src/settings/wsgi.py index b8c0184..69a3d11 100644 --- a/restauth/wsgi.py +++ b/src/settings/wsgi.py @@ -9,8 +9,9 @@ import os -from django.core.wsgi import get_wsgi_application +from configurations.wsgi import get_wsgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "restauth.settings") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.configs") +os.environ.setdefault('DJANGO_CONFIGURATION', 'Prod') application = get_wsgi_application() diff --git a/src/uwsgi.ini b/src/uwsgi.ini new file mode 100644 index 0000000..d3efd06 --- /dev/null +++ b/src/uwsgi.ini @@ -0,0 +1,42 @@ +[uwsgi] + +# master +master = true + +# processes +enable-threads = true +# With cheaper = 1 we activate the The uWSGI cheaper subsystem which allows to +# dynamically scale the number of running workers (processes). +cheaper = 1 +# The minimum load uWSGI will spawn just one workers. +processes = %(%k + 1) +threads = 2 + +# sockets +http-socket = :8000 + +# wsgi +chdir = /app/backend +module = settings.wsgi + +# gracefully and sequentially reload workers when this file is touched +touch-chain-reload = /tmp/backend.pid + +# customization +die-on-term = true + +# clear environment on exit +vacuum = true +buffer-size = 32768 + +# reload if rss memory is higher than specified megabytes +reload-on-rss = 512 + +# force the master to reload a worker if its rss memory is higher than specified megabytes +evil-reload-on-rss = 1024 + +# reload workers after the specified amount of managed requests +max-requests = 100 + +# reload workers after the specified amount of seconds +max-worker-lifetime = 1800 \ No newline at end of file From 20a06ad0f17090ad1fa35e763e52fd90bc93a73b Mon Sep 17 00:00:00 2001 From: Krystian Hanek Date: Sun, 4 Mar 2018 11:17:45 +0100 Subject: [PATCH 2/2] Add missing user migration --- .../migrations/0003_auto_20180304_1017.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/restauth/migrations/0003_auto_20180304_1017.py diff --git a/src/restauth/migrations/0003_auto_20180304_1017.py b/src/restauth/migrations/0003_auto_20180304_1017.py new file mode 100644 index 0000000..724e0b9 --- /dev/null +++ b/src/restauth/migrations/0003_auto_20180304_1017.py @@ -0,0 +1,24 @@ +# Generated by Django 2.0.2 on 2018-03-04 10:17 + +from django.db import migrations, models +import hashid_field.field + + +class Migration(migrations.Migration): + + dependencies = [ + ('restauth', '0002_userprofile'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='id', + field=hashid_field.field.HashidAutoField(alphabet='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', min_length=7, primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='user', + name='is_active', + field=models.BooleanField(default=True), + ), + ]