initial commit

This commit is contained in:
2021-07-28 02:15:48 +03:00
commit 735633853a
6607 changed files with 1084121 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
"""
This is a django-split-settings main file.
For more information read this:
https://github.com/sobolevn/django-split-settings
https://sobolevn.me/2017/04/managing-djangos-settings
To change settings file:
`DJANGO_ENV=production python manage.py runserver`
"""
from os import environ
import django_stubs_ext
from split_settings.tools import include, optional
# Monkeypatching Django, so stubs will work for all generics,
# see: https://github.com/typeddjango/django-stubs
django_stubs_ext.monkeypatch()
# Managing environment via `DJANGO_ENV` variable:
environ.setdefault('DJANGO_ENV', 'development')
_ENV = environ['DJANGO_ENV']
_base_settings = (
'components/common.py',
'components/logging.py',
'components/csp.py',
'components/caches.py',
'components/email.py',
# Select the right env:
'environments/{0}.py'.format(_ENV),
# Optionally override some settings:
optional('environments/local.py'),
)
# Include settings:
include(*_base_settings)

View File

@@ -0,0 +1,12 @@
from pathlib import Path
from decouple import AutoConfig
# Build paths inside the project like this: BASE_DIR.joinpath('some')
# `pathlib` is better than writing: dirname(dirname(dirname(__file__)))
# BASE_DIR = PurePath(__file__).parent.parent.parent.parent
BASE_DIR = Path.cwd()
# Loading `.env` files
# See docs: https://gitlab.com/mkleehammer/autoconfig
config = AutoConfig(search_path=BASE_DIR.joinpath('config'))

View File

@@ -0,0 +1,16 @@
# Caching
# https://docs.djangoproject.com/en/2.2/topics/cache/
CACHES = {
'default': {
# TODO: use some other cache in production,
# like https://github.com/jazzband/django-redis
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
},
}
# django-axes
# https://django-axes.readthedocs.io/en/latest/4_configuration.html#configuring-caches
AXES_CACHE = 'default'

View File

@@ -0,0 +1,211 @@
# """
# Django settings for server project.
#
# For more information on this file, see
# https://docs.djangoproject.com/en/2.2/topics/settings/
#
# For the full list of settings and their config, see
# https://docs.djangoproject.com/en/2.2/ref/settings/
# """
import os
from typing import Dict, List, Tuple, Union
DEBUG = True
from django.utils.translation import ugettext_lazy as _
from server.settings.components import BASE_DIR, config
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
SECRET_KEY = config('DJANGO_SECRET_KEY')
LOGIN_REDIRECT_URL = '/main/hello'
GIT_API_URL = 'https://api.github.com/users'
# Application definition:
INSTALLED_APPS: Tuple[str, ...] = (
# Your apps go here:
'server.apps.main',
# Default django apps:
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# django-admin:
'django.contrib.admin',
'django.contrib.admindocs',
# Security:
# 'axes',
# Health checks:
# You may want to enable other checks as well,
# see: https://github.com/KristianOellegaard/django-health-check
'health_check',
'health_check.db',
'health_check.cache',
'health_check.storage',
)
MIDDLEWARE: Tuple[str, ...] = (
# # Content Security Policy:
# 'csp.middleware.CSPMiddleware',
# Django:
'django.middleware.security.SecurityMiddleware',
# django-permissions-policy
'django_permissions_policy.PermissionsPolicyMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# Axes:
# 'axes.middleware.AxesMiddleware',
# Django HTTP Referrer Policy:
'django_http_referrer_policy.middleware.ReferrerPolicyMiddleware',
)
ROOT_URLCONF = 'server.urls'
WSGI_APPLICATION = 'server.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': config('POSTGRES_DB'),
'USER': config('POSTGRES_USER'),
'PASSWORD': config('POSTGRES_PASSWORD'),
'HOST': config('DJANGO_DATABASE_HOST'),
'PORT': config('DJANGO_DATABASE_PORT', cast=int),
'CONN_MAX_AGE': config('CONN_MAX_AGE', cast=int, default=60),
'OPTIONS': {
'connect_timeout': 10,
},
},
}
# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/
LANGUAGE_CODE = 'ru-RU' # 'en-us'
USE_I18N = True
USE_L10N = True
LANGUAGES = (
('ru', _('Russian')),
# ('en', _('English')),
)
LOCALE_PATHS = (
'locale/',
)
USE_TZ = True
TIME_ZONE = 'UTC'
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
# STATIC_URL = '/static/'
#
# STATICFILES_FINDERS = (
# 'django.contrib.staticfiles.finders.FileSystemFinder',
# 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
# )
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'shared_dir')
# Templates
# https://docs.djangoproject.com/en/2.2/ref/templates/api
TEMPLATES = [{
'APP_DIRS': True,
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
# Contains plain text templates, like `robots.txt`:
BASE_DIR.joinpath('server', 'templates'),
],
'OPTIONS': {
'context_processors': [
# Default template context processors:
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.request',
],
},
}]
# Media files
# Media root dir is commonly changed in production
# (see development.py and production.py).
# https://docs.djangoproject.com/en/2.2/topics/files/
# MEDIA_URL = '/media/'
# MEDIA_ROOT = BASE_DIR.joinpath('media')
# Django authentication system
# https://docs.djangoproject.com/en/2.2/topics/auth/
AUTHENTICATION_BACKENDS = (
# 'axes.backends.AxesBackend',
'django.contrib.auth.backends.ModelBackend',
)
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
'django.contrib.auth.hashers.BCryptPasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher',
]
# Security
# https://docs.djangoproject.com/en/2.2/topics/security/
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_HTTPONLY = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'
# https://github.com/DmytroLitvinov/django-http-referrer-policy
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
REFERRER_POLICY = 'same-origin'
# https://github.com/adamchainz/django-permissions-policy#setting
PERMISSIONS_POLICY: Dict[str, Union[str, List[str]]] = {} # noqa: WPS234
# Timeouts
# https://docs.djangoproject.com/en/2.2/ref/settings/#std:setting-EMAIL_TIMEOUT
EMAIL_TIMEOUT = 5

View File

@@ -0,0 +1,15 @@
"""
This file contains a definition for Content-Security-Policy headers.
Read more about it:
https://developer.mozilla.org/ru/docs/Web/HTTP/Headers/Content-Security-Policy
We are using `django-csp` to provide these headers.
Docs: https://github.com/mozilla/django-csp
"""
CSP_SCRIPT_SRC = ("'self'",)
CSP_IMG_SRC = ("'self'",)
CSP_FONT_SRC = ("'self'",)
CSP_STYLE_SRC = ("'self'",)
CSP_DEFAULT_SRC = ("'none'",)

View File

@@ -0,0 +1,19 @@
from server.settings.components import config
ACCOUNT_ACTIVATION_DAYS = 2
EMAIL_HOST = config('EMAIL_HOST')
EMAIL_PORT = config('EMAIL_PORT', cast=int)
EMAIL_USE_SSL = config('EMAIL_USE_SSL', cast=bool)
EMAIL_USE_TLS = config('EMAIL_USE_TLS', cast=bool)
EMAIL_HOST_USER = config('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD')
# Is used to set sender name
# https://docs.djangoproject.com/en/1.11/ref/settings/#default-from-email
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
SERVER_EMAIL = EMAIL_HOST_USER
EMAIL_TIMEOUT = 20

View File

@@ -0,0 +1,77 @@
# Logging
# https://docs.djangoproject.com/en/2.2/topics/logging/
# See also:
# 'Do not log' by Nikita Sobolev (@sobolevn)
# https://sobolevn.me/2020/03/do-not-log
import structlog
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
# We use these formatters in our `'handlers'` configuration.
# Probably, you won't need to modify these lines.
# Unless, you know what you are doing.
'formatters': {
'json_formatter': {
'()': structlog.stdlib.ProcessorFormatter,
'processor': structlog.processors.JSONRenderer(),
},
'console': {
'()': structlog.stdlib.ProcessorFormatter,
'processor': structlog.processors.KeyValueRenderer(
key_order=['timestamp', 'level', 'event', 'logger'],
),
},
},
# You can easily swap `key/value` (default) output and `json` ones.
# Use `'json_console'` if you need `json` logs.
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'console',
},
'json_console': {
'class': 'logging.StreamHandler',
'formatter': 'json_formatter',
},
},
# These loggers are required by our app:
# - django is required when using `logger.getLogger('django')`
# - security is required by `axes`
'loggers': {
'django': {
'handlers': ['console'],
'propagate': True,
'level': 'INFO',
},
'security': {
'handlers': ['console'],
'level': 'ERROR',
'propagate': False,
},
},
}
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.processors.TimeStamper(fmt='iso'),
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.stdlib.PositionalArgumentsFormatter(),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.processors.UnicodeDecoder(),
structlog.processors.ExceptionPrettyPrinter(),
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
],
context_class=structlog.threadlocal.wrap_dict(dict),
logger_factory=structlog.stdlib.LoggerFactory(),
wrapper_class=structlog.stdlib.BoundLogger,
cache_logger_on_first_use=True,
)

View File

@@ -0,0 +1,2 @@
"""Overriding settings based on the environment."""

View File

@@ -0,0 +1,150 @@
"""
This file contains all the settings that defines the development server.
SECURITY WARNING: don't run with debug turned on in production!
"""
import logging
from typing import List
from server.settings.components import config
from server.settings.components.common import (
DATABASES,
INSTALLED_APPS,
MIDDLEWARE,
)
# Setting the development status:
DEBUG = True
ALLOWED_HOSTS = [
config('DOMAIN_NAME'),
'localhost',
'0.0.0.0', # noqa: S104
'127.0.0.1',
'[::1]',
]
# Installed apps for development only:
INSTALLED_APPS += (
# Better debug:
'debug_toolbar',
'nplusone.ext.django',
# Linting migrations:
'django_migration_linter',
# django-test-migrations:
'django_test_migrations.contrib.django_checks.AutoNames',
# This check might be useful in production as well,
# so it might be a good idea to move `django-test-migrations`
# to prod dependencies and use this check in the main `settings.py`.
# This will check that your database is configured properly,
# when you run `python manage.py check` before deploy.
'django_test_migrations.contrib.django_checks.DatabaseConfiguration',
# django-extra-checks:
'extra_checks',
)
# Static files:
# https://docs.djangoproject.com/en/2.2/ref/settings/#std:setting-STATICFILES_DIRS
# STATICFILES_DIRS: List[str] = []
# Django debug toolbar:
# https://django-debug-toolbar.readthedocs.io
MIDDLEWARE += (
'debug_toolbar.middleware.DebugToolbarMiddleware',
# https://github.com/bradmontgomery/django-querycount
# Prints how many queries were executed, useful for the APIs.
'querycount.middleware.QueryCountMiddleware',
)
def _custom_show_toolbar(request):
"""Only show the debug toolbar to users with the superuser flag."""
return DEBUG and request.user.is_superuser
DEBUG_TOOLBAR_CONFIG = {
'SHOW_TOOLBAR_CALLBACK':
'server.settings.environments.development._custom_show_toolbar',
}
# This will make debug toolbar to work with django-csp,
# since `ddt` loads some scripts from `ajax.googleapis.com`:
CSP_SCRIPT_SRC = ("'self'", 'ajax.googleapis.com')
CSP_IMG_SRC = ("'self'", 'data:')
CSP_CONNECT_SRC = ("'self'",)
# nplusone
# https://github.com/jmcarp/nplusone
# Should be the first in line:
MIDDLEWARE = ( # noqa: WPS440
'nplusone.ext.django.NPlusOneMiddleware',
) + MIDDLEWARE
# Logging N+1 requests:
NPLUSONE_RAISE = True # comment out if you want to allow N+1 requests
NPLUSONE_LOGGER = logging.getLogger('django')
NPLUSONE_LOG_LEVEL = logging.WARN
NPLUSONE_WHITELIST = [
{'model': 'admin.*'},
]
# django-test-migrations
# https://github.com/wemake-services/django-test-migrations
# Set of badly named migrations to ignore:
DTM_IGNORED_MIGRATIONS = frozenset((
('axes', '*'),
))
# django-extra-checks
# https://github.com/kalekseev/django-extra-checks
EXTRA_CHECKS = {
'checks': [
# Forbid `unique_together`:
'no-unique-together',
# Require non empty `upload_to` argument:
'field-file-upload-to',
# Use the indexes option instead:
'no-index-together',
# Each model must be registered in admin:
'model-admin',
# FileField/ImageField must have non empty `upload_to` argument:
'field-file-upload-to',
# Text fields shouldn't use `null=True`:
'field-text-null',
# Prefer using BooleanField(null=True) instead of NullBooleanField:
'field-boolean-null',
# Don't pass `null=False` to model fields (this is django default)
'field-null',
# ForeignKey fields must specify db_index explicitly if used in
# other indexes:
{'id': 'field-foreign-key-db-index', 'when': 'indexes'},
# If field nullable `(null=True)`,
# then default=None argument is redundant and should be removed:
'field-default-null',
# Fields with choices must have companion CheckConstraint
# to enforce choices on database level
'field-choices-constraint',
],
}
# Disable persistent DB connections
# https://docs.djangoproject.com/en/2.2/ref/databases/#caveats
DATABASES['default']['CONN_MAX_AGE'] = 0

View File

@@ -0,0 +1 @@
"""Override any custom settings here."""

View File

@@ -0,0 +1,75 @@
"""
This file contains all the settings used in production.
This file is required and if development.py is present these
values are overridden.
"""
from server.settings.components import config
# Production flags:
# https://docs.djangoproject.com/en/2.2/howto/deployment/
DEBUG = False
ALLOWED_HOSTS = [
# TODO: check production hosts
config('DOMAIN_NAME'),
# We need this value for `healthcheck` to work:
'localhost',
]
# Staticfiles
# https://docs.djangoproject.com/en/2.2/ref/contrib/staticfiles/
# This is a hack to allow a special flag to be used with `--dry-run`
# to test things locally.
_COLLECTSTATIC_DRYRUN = config(
'DJANGO_COLLECTSTATIC_DRYRUN', cast=bool, default=False,
)
# Adding STATIC_ROOT to collect static files via 'collectstatic':
STATIC_ROOT = '.static' if _COLLECTSTATIC_DRYRUN else '/var/www/django/static'
STATICFILES_STORAGE = (
# This is a string, not a tuple,
# but it does not fit into 80 characters rule.
'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
)
# Media files
# https://docs.djangoproject.com/en/2.2/topics/files/
MEDIA_ROOT = '/var/www/django/media'
# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
_PASS = 'django.contrib.auth.password_validation' # noqa: S105
AUTH_PASSWORD_VALIDATORS = [
{'NAME': '{0}.UserAttributeSimilarityValidator'.format(_PASS)},
{'NAME': '{0}.MinimumLengthValidator'.format(_PASS)},
{'NAME': '{0}.CommonPasswordValidator'.format(_PASS)},
{'NAME': '{0}.NumericPasswordValidator'.format(_PASS)},
]
# Security
# https://docs.djangoproject.com/en/2.2/topics/security/
SECURE_HSTS_SECONDS = 31536000 # the same as Caddy has
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_SSL_REDIRECT = True
SECURE_REDIRECT_EXEMPT = [
# This is required for healthcheck to work:
'^health/',
]
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True