mirror of
https://github.com/Balshgit/public.git
synced 2025-09-11 18:00:42 +03:00
add async search to celery task and logger
This commit is contained in:
parent
d3d862eaef
commit
9f8c186128
1
celery-rabbit-example/.gitignore
vendored
1
celery-rabbit-example/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
!.env
|
|
@ -1,34 +0,0 @@
|
|||||||
FROM python:3.8.6-buster
|
|
||||||
|
|
||||||
ENV PYTHONFAULTHANDLER=1 \
|
|
||||||
PYTHONUNBUFFERED=1 \
|
|
||||||
PYTHONHASHSEED=random \
|
|
||||||
PYTHONDONTWRITEBYTECODE=1 \
|
|
||||||
# pip:
|
|
||||||
PIP_NO_CACHE_DIR=off \
|
|
||||||
PIP_DISABLE_PIP_VERSION_CHECK=on \
|
|
||||||
PIP_DEFAULT_TIMEOUT=100
|
|
||||||
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get install --no-install-recommends -y \
|
|
||||||
bash \
|
|
||||||
build-essential \
|
|
||||||
curl \
|
|
||||||
gettext \
|
|
||||||
git \
|
|
||||||
libpq-dev \
|
|
||||||
nano
|
|
||||||
|
|
||||||
WORKDIR /code
|
|
||||||
|
|
||||||
# Copy and install dependencies:
|
|
||||||
COPY requirements.txt /code/
|
|
||||||
RUN python -m pip install --upgrade pip
|
|
||||||
RUN pip install --no-cache-dir -r /code/requirements.txt
|
|
||||||
|
|
||||||
# Copy source files:
|
|
||||||
COPY . /code/
|
|
||||||
# COPY app.py /code/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
|||||||
# celery first example
|
|
||||||
|
|
||||||
Steps:
|
|
||||||
1. Run `docker-compose up`
|
|
||||||
2. Show logs
|
|
||||||
3. In a new terminal run `docker-compose exec worker python`
|
|
@ -1,3 +0,0 @@
|
|||||||
# from app_celery import app as my_celery_app
|
|
||||||
#
|
|
||||||
# __all__ = ('my_celery_app', )
|
|
@ -1,25 +0,0 @@
|
|||||||
from celery import Celery
|
|
||||||
from pathlib import Path
|
|
||||||
from decouple import AutoConfig
|
|
||||||
|
|
||||||
BASE_DIR = Path.cwd().parent
|
|
||||||
config = AutoConfig(search_path=BASE_DIR.joinpath('config'))
|
|
||||||
|
|
||||||
|
|
||||||
RABBITMQ_DEFAULT_USER = config('RABBITMQ_DEFAULT_USER')
|
|
||||||
RABBITMQ_DEFAULT_PASS = config('RABBITMQ_DEFAULT_PASS')
|
|
||||||
RABBITMQ_PORT = config('RABBITMQ_PORT', cast=int, default=5672)
|
|
||||||
RABBITMQ_HOST = config('RABBITMQ_HOST')
|
|
||||||
|
|
||||||
|
|
||||||
app_celery_instance = Celery(
|
|
||||||
'tasks',
|
|
||||||
broker='amqp://{login}:{password}@{host}:{port}'.format(
|
|
||||||
login=RABBITMQ_DEFAULT_USER,
|
|
||||||
password=RABBITMQ_DEFAULT_PASS,
|
|
||||||
host=RABBITMQ_HOST,
|
|
||||||
port=RABBITMQ_PORT,
|
|
||||||
),
|
|
||||||
# TODO: try to get async results with and without backend configured
|
|
||||||
backend='rpc://',
|
|
||||||
)
|
|
@ -1,6 +0,0 @@
|
|||||||
# RabbitMQ settings:
|
|
||||||
|
|
||||||
RABBITMQ_DEFAULT_USER=rabbit_admin
|
|
||||||
RABBITMQ_DEFAULT_PASS=mypass
|
|
||||||
RABBITMQ_PORT=5672
|
|
||||||
RABBITMQ_HOST=rabbitmq_host
|
|
@ -1,28 +0,0 @@
|
|||||||
version: '3.7'
|
|
||||||
|
|
||||||
services:
|
|
||||||
|
|
||||||
rabbitmq:
|
|
||||||
hostname: rabbitmq_host
|
|
||||||
image: 'rabbitmq:3.8.18-management-alpine'
|
|
||||||
container_name: first_rabbit
|
|
||||||
env_file: config/.env
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- 8080:15672
|
|
||||||
- 5672:5672
|
|
||||||
|
|
||||||
worker:
|
|
||||||
container_name: first_celery
|
|
||||||
build: .
|
|
||||||
command: celery --app=my_app:app_celery_instance worker --loglevel=INFO
|
|
||||||
env_file: config/.env
|
|
||||||
depends_on:
|
|
||||||
- rabbitmq
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
networks:
|
|
||||||
default:
|
|
||||||
name: celery_network
|
|
||||||
driver: bridge
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
|||||||
from celery_config.app_celery import app_celery_instance
|
|
||||||
|
|
||||||
|
|
||||||
@app_celery_instance.task
|
|
||||||
def add(first: int, second: int) -> int:
|
|
||||||
print(first + second)
|
|
||||||
return first + second
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: try with `@app.task(throws=(ZeroDivisionError,))`
|
|
||||||
@app_celery_instance.task
|
|
||||||
def div(first: int, second: int) -> float:
|
|
||||||
# TODO: show how errors work
|
|
||||||
return first / second
|
|
@ -1,2 +0,0 @@
|
|||||||
celery==5.0.2
|
|
||||||
python-decouple==3.3
|
|
@ -4,7 +4,7 @@ DOMAIN_NAME=localhost
|
|||||||
TLS_EMAIL=webmaster@localhost
|
TLS_EMAIL=webmaster@localhost
|
||||||
|
|
||||||
GITHUB_USERNAME=
|
GITHUB_USERNAME=
|
||||||
GITHUB_PASSWORD=
|
GITHUB_TOKEN=
|
||||||
|
|
||||||
|
|
||||||
# === Django ===
|
# === Django ===
|
||||||
|
@ -13,7 +13,7 @@ services:
|
|||||||
- webnet
|
- webnet
|
||||||
env_file: ./config/.env
|
env_file: ./config/.env
|
||||||
ports:
|
ports:
|
||||||
- 5433:5432
|
- "5433:5432"
|
||||||
|
|
||||||
rabbitmq:
|
rabbitmq:
|
||||||
hostname: rabbitmq_host
|
hostname: rabbitmq_host
|
||||||
@ -24,8 +24,8 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- webnet
|
- webnet
|
||||||
ports:
|
ports:
|
||||||
- 8080:15672
|
- "8080:15672"
|
||||||
- 5672:5672
|
- "5672:5672"
|
||||||
|
|
||||||
web:
|
web:
|
||||||
image: "github-repos"
|
image: "github-repos"
|
||||||
@ -45,7 +45,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- .:/code
|
- .:/code
|
||||||
ports:
|
ports:
|
||||||
- 8000:8000
|
- "8000:8000"
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
networks:
|
networks:
|
||||||
@ -93,7 +93,7 @@ services:
|
|||||||
- web
|
- web
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- 5555:5555
|
- "5555:5555"
|
||||||
networks:
|
networks:
|
||||||
- webnet
|
- webnet
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM python:3.8.9-slim-buster
|
FROM python:3.9.7-slim-buster
|
||||||
|
|
||||||
ENV BUILD_ONLY_PACKAGES='wget' \
|
ENV BUILD_ONLY_PACKAGES='wget' \
|
||||||
# python:
|
# python:
|
||||||
@ -11,7 +11,7 @@ ENV BUILD_ONLY_PACKAGES='wget' \
|
|||||||
PIP_DISABLE_PIP_VERSION_CHECK=on \
|
PIP_DISABLE_PIP_VERSION_CHECK=on \
|
||||||
PIP_DEFAULT_TIMEOUT=100 \
|
PIP_DEFAULT_TIMEOUT=100 \
|
||||||
# poetry:
|
# poetry:
|
||||||
POETRY_VERSION=1.1.4 \
|
POETRY_VERSION=1.1.11 \
|
||||||
POETRY_NO_INTERACTION=1 \
|
POETRY_NO_INTERACTION=1 \
|
||||||
POETRY_VIRTUALENVS_CREATE=false \
|
POETRY_VIRTUALENVS_CREATE=false \
|
||||||
POETRY_CACHE_DIR='/var/cache/pypoetry' \
|
POETRY_CACHE_DIR='/var/cache/pypoetry' \
|
||||||
|
626
github-stars/poetry.lock
generated
626
github-stars/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -15,7 +15,7 @@ authors = ["balsh"]
|
|||||||
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "3.8.9"
|
python = "3.9.7"
|
||||||
django = "^3"
|
django = "^3"
|
||||||
django-split-settings = "^1.0"
|
django-split-settings = "^1.0"
|
||||||
django-axes = "^5.20"
|
django-axes = "^5.20"
|
||||||
@ -34,7 +34,7 @@ structlog = "^21.1"
|
|||||||
celery = "5.1.2"
|
celery = "5.1.2"
|
||||||
flower = "^1.0.0"
|
flower = "^1.0.0"
|
||||||
celery_progress = "0.1.1"
|
celery_progress = "0.1.1"
|
||||||
|
aiohttp = "3.7.4.post0"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
django-debug-toolbar = "^3.2"
|
django-debug-toolbar = "^3.2"
|
||||||
|
@ -1,93 +1,120 @@
|
|||||||
import requests
|
|
||||||
from requests.models import Response
|
|
||||||
from requests.auth import HTTPBasicAuth
|
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
from functools import lru_cache
|
import sys
|
||||||
from typing import Dict, Optional
|
from typing import Dict, Optional, Tuple
|
||||||
from server.apps.main.celery_config import celery_app
|
from server.apps.main.celery_config import celery_app
|
||||||
from server.settings.components.common import GIT_API_URL
|
from server.settings.components.common import GIT_API_URL
|
||||||
from celery_progress.backend import ProgressRecorder
|
from celery_progress.backend import ProgressRecorder
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from server.settings.components import config
|
from server.settings.components import config
|
||||||
|
import aiohttp
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
console_logger = logging.getLogger(__name__)
|
||||||
|
formatter = logging.Formatter(datefmt="%Y.%m.%d %H:%M:%S",
|
||||||
|
fmt='%(asctime)s | message: %(message)s')
|
||||||
|
# fmt='%(asctime)s | %(levelname)s | process: %(process)d | module name: %(name)s | '
|
||||||
|
# 'func name: %(funcName)s | line number: %(lineno)s | message: %(message)s',)
|
||||||
|
handler = logging.StreamHandler(sys.stdout)
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
console_logger.setLevel(logging.INFO)
|
||||||
|
console_logger.addHandler(handler)
|
||||||
|
|
||||||
|
|
||||||
def current_page(response: Response, link: str) -> int:
|
class GitHubScanner:
|
||||||
url = str(response.links[f'{link}']['url'])
|
def __init__(self, user: str, token: str):
|
||||||
page_count = int(str(re.findall(pattern=r'page=\d+', string=url)[1])
|
self.auth = aiohttp.BasicAuth(user, token)
|
||||||
.replace('page=', ''))
|
self.data = {}
|
||||||
return page_count
|
self.semaphore = asyncio.Semaphore(200)
|
||||||
|
|
||||||
|
def _data_count(self) -> int:
|
||||||
def github_request(url: str) -> Response:
|
repos_count = 0
|
||||||
auth = HTTPBasicAuth(config('GITHUB_USERNAME'), config('GITHUB_PASSWORD'))
|
|
||||||
counter = 0
|
|
||||||
while True:
|
|
||||||
try:
|
try:
|
||||||
counter += 1
|
for data_set in self.data.values():
|
||||||
if auth == HTTPBasicAuth('', ''):
|
repos_count += len(data_set['data'])
|
||||||
response = requests.get(url)
|
except ValueError:
|
||||||
else:
|
console_logger.info(f'Data is empty')
|
||||||
response = requests.get(url, auth=auth)
|
return repos_count
|
||||||
return response
|
|
||||||
except ConnectionError as connection_error:
|
@staticmethod
|
||||||
if counter < 5:
|
def _page_count(url: str) -> int:
|
||||||
time.sleep(10)
|
page = int(str(re.findall(pattern=r'&page=\d+', string=url)[-1]).replace('&page=', ''))
|
||||||
else:
|
return page
|
||||||
raise connection_error
|
|
||||||
|
async def _github_request(self, session: aiohttp.ClientSession, url: str) -> Dict:
|
||||||
|
async with self.semaphore:
|
||||||
|
counter = 0
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
counter += 1
|
||||||
|
resp = await session.get(url)
|
||||||
|
async with resp:
|
||||||
|
if resp.status == 200:
|
||||||
|
self.data[self._page_count(url)] = {'response': resp, 'data': await resp.json()}
|
||||||
|
return self.data[self._page_count(url)]
|
||||||
|
if resp.status >= 400:
|
||||||
|
return {'response': None, 'data': None}
|
||||||
|
except Exception as connection_error:
|
||||||
|
if counter < 5:
|
||||||
|
await asyncio.sleep(10)
|
||||||
|
else:
|
||||||
|
raise connection_error
|
||||||
|
|
||||||
|
async def get_data(self, celery_task, username: str) -> None:
|
||||||
|
base_url = f'{GIT_API_URL}/{username}/repos?per_page=100&page=' + '{}'
|
||||||
|
progress_recorder = ProgressRecorder(celery_task)
|
||||||
|
connector = aiohttp.TCPConnector(limit=500)
|
||||||
|
async with aiohttp.ClientSession(auth=self.auth, connector=connector) as session:
|
||||||
|
url = base_url.format(1)
|
||||||
|
tasks = []
|
||||||
|
try:
|
||||||
|
resp = await self._github_request(session, url)
|
||||||
|
self.data[1] = resp
|
||||||
|
last_page = self._page_count(dict(resp['response'].headers).get('Link'))
|
||||||
|
last_page_url = str(resp['response'].links['last']['url'])
|
||||||
|
if last_page:
|
||||||
|
data_last_page = await self._github_request(session, last_page_url)
|
||||||
|
repos_count = (last_page - 1) * 100 + len(data_last_page['data'])
|
||||||
|
for i in range(1, last_page):
|
||||||
|
url = base_url.format(i + 1)
|
||||||
|
current_repos_count = self._data_count()
|
||||||
|
percent = round(current_repos_count / repos_count * 100)
|
||||||
|
progress_recorder.set_progress(current_repos_count, repos_count,
|
||||||
|
description=f'Processing: {percent}%')
|
||||||
|
task = asyncio.create_task(self._github_request(session, url))
|
||||||
|
tasks.append(task)
|
||||||
|
else:
|
||||||
|
tasks.append(asyncio.create_task(self._github_request(session, url)))
|
||||||
|
except Exception as e:
|
||||||
|
console_logger.error(e)
|
||||||
|
await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
|
||||||
@shared_task(bind=True)
|
@celery_app.task(bind=True)
|
||||||
def get_github_stars(self, username: str) -> Dict[str, Optional[int]]:
|
def get_github_stars(celery_task, username: str) -> Dict[str, Optional[int]]:
|
||||||
|
github = GitHubScanner(config('GITHUB_USERNAME'), config('GITHUB_TOKEN'))
|
||||||
url = f'{GIT_API_URL}/{username}/repos?per_page=100&page=1'
|
loop = asyncio.get_event_loop()
|
||||||
print(url)
|
loop.run_until_complete(github.get_data(celery_task, username))
|
||||||
progress_recorder = ProgressRecorder(self)
|
repos_data = github.data
|
||||||
|
data = {}
|
||||||
response = github_request(url)
|
try:
|
||||||
if response.status_code >= 400:
|
for value in repos_data.values():
|
||||||
|
for item in value['data']:
|
||||||
|
data[item["name"]] = item["stargazers_count"]
|
||||||
|
result = dict(sorted(data.items(), key=lambda x: x[1], reverse=True))
|
||||||
|
except TypeError:
|
||||||
result = {}
|
result = {}
|
||||||
else:
|
|
||||||
repos = response.json()
|
|
||||||
|
|
||||||
try:
|
|
||||||
page_count = current_page(response, 'last')
|
|
||||||
repos_count = (page_count - 1) * 100 + \
|
|
||||||
len(github_request(response.links['last']['url']).json())
|
|
||||||
except KeyError as e:
|
|
||||||
page_count = 1
|
|
||||||
repos_count = len(repos)
|
|
||||||
|
|
||||||
i = 0
|
|
||||||
while 'next' in response.links.keys():
|
|
||||||
i += 1
|
|
||||||
response = github_request(response.links['next']['url'])
|
|
||||||
repos.extend(response.json())
|
|
||||||
current = i * 100 + len(response.json())
|
|
||||||
|
|
||||||
# Progress bar
|
|
||||||
percent = round(100 / page_count * i)
|
|
||||||
progress_recorder.set_progress(current, repos_count,
|
|
||||||
description=f'Processing: {percent}%')
|
|
||||||
|
|
||||||
# Fetching repos and stars in dict
|
|
||||||
data: Dict[str, int] = {}
|
|
||||||
try:
|
|
||||||
for item in repos:
|
|
||||||
data[item['name']] = int(item['stargazers_count'])
|
|
||||||
result = dict(sorted(data.items(), key=lambda x: x[1], reverse=True))
|
|
||||||
except TypeError:
|
|
||||||
result = {}
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# Demo task
|
||||||
@shared_task(bind=True)
|
@shared_task(bind=True)
|
||||||
def process_download(self):
|
def process_download(task) -> str:
|
||||||
print('Task started')
|
print('Task started')
|
||||||
# Create the progress recorder instance
|
# Create the progress recorder instance
|
||||||
# which we'll use to update the web page
|
# which we'll use to update the web page
|
||||||
progress_recorder = ProgressRecorder(self)
|
progress_recorder = ProgressRecorder(task)
|
||||||
|
|
||||||
print('Start')
|
print('Start')
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
{% block demo %}{% endblock %}
|
{% block demo %}
|
||||||
|
{% endblock %}
|
||||||
<!-- JQuery -->
|
<!-- JQuery -->
|
||||||
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
|
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
|
||||||
<!-- Bootstrap JS -->
|
<!-- Bootstrap JS -->
|
||||||
@ -19,7 +20,8 @@
|
|||||||
<!-- Celery Progress -->
|
<!-- Celery Progress -->
|
||||||
<script src="{% static 'celery_progress/celery_progress.js' %}"></script>
|
<script src="{% static 'celery_progress/celery_progress.js' %}"></script>
|
||||||
|
|
||||||
{% block progress_bar_js %}{% endblock progress_bar_js %}
|
{% block progress_bar_js %}
|
||||||
|
{% endblock progress_bar_js %}
|
||||||
<div class="container text-center" style="padding-top: 20px;">
|
<div class="container text-center" style="padding-top: 20px;">
|
||||||
{{ message }}
|
{{ message }}
|
||||||
{% for repo, stars in data.items %}
|
{% for repo, stars in data.items %}
|
||||||
|
@ -34,7 +34,8 @@
|
|||||||
<!-- Download Status -->
|
<!-- Download Status -->
|
||||||
<div class="container" style="padding-top: 20px;">
|
<div class="container" style="padding-top: 20px;">
|
||||||
<div class="card" style="height: 120px;">
|
<div class="card" style="height: 120px;">
|
||||||
{% block progress %}{% endblock progress %}
|
{% block progress %}
|
||||||
|
{% endblock progress %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect
|
||||||
from django.urls import reverse
|
from django.urls import reverse, reverse_lazy
|
||||||
from .forms import GithubForm
|
from .forms import GithubForm
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from .commands import get_github_stars, process_download
|
from .commands import get_github_stars, console_logger, process_download
|
||||||
from django.views.decorators.http import require_http_methods
|
from django.views.decorators.http import require_http_methods
|
||||||
from celery.result import AsyncResult
|
from celery.result import AsyncResult
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
|
|
||||||
task_id = {}
|
task_id = {}
|
||||||
@ -29,10 +30,9 @@ def github(request: HttpRequest) -> HttpResponse:
|
|||||||
try:
|
try:
|
||||||
email = getattr((User.objects.get(username=username)),
|
email = getattr((User.objects.get(username=username)),
|
||||||
'email', 'default@email.ru')
|
'email', 'default@email.ru')
|
||||||
|
|
||||||
except ObjectDoesNotExist as e:
|
except ObjectDoesNotExist as e:
|
||||||
error = 'That user doesnt exists or not log on'
|
error = 'That user doesnt exists or not log on'
|
||||||
print(error, e)
|
console_logger.error(error, e)
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ def github(request: HttpRequest) -> HttpResponse:
|
|||||||
result = get_github_stars.delay(github_username)
|
result = get_github_stars.delay(github_username)
|
||||||
task_id[username] = result.task_id
|
task_id[username] = result.task_id
|
||||||
|
|
||||||
return redirect(reverse('github_result'))
|
return redirect(reverse_lazy('github_result'))
|
||||||
|
|
||||||
form = GithubForm
|
form = GithubForm
|
||||||
return render(request, 'main/github.html',
|
return render(request, 'main/github.html',
|
||||||
@ -53,18 +53,21 @@ def github_result(request: HttpRequest) -> HttpResponse:
|
|||||||
username = str(request.user.username)
|
username = str(request.user.username)
|
||||||
data = AsyncResult(task_id[username])
|
data = AsyncResult(task_id[username])
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
message = ''
|
||||||
if data.ready():
|
if data.ready():
|
||||||
message = "Result Ready"
|
message = "Result Ready"
|
||||||
result = data.get()
|
result = data.get()
|
||||||
print('result ready')
|
console_logger.info('result ready')
|
||||||
else:
|
else:
|
||||||
print('result not ready')
|
console_logger.info('result not ready')
|
||||||
|
|
||||||
return render(request, 'main/github_result.html',
|
return render(request, 'main/github_result.html',
|
||||||
context={'data': result,
|
context={'data': result,
|
||||||
'message': message})
|
'message': message})
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=10)
|
||||||
def demo_view(request: HttpRequest) -> HttpResponse:
|
def demo_view(request: HttpRequest) -> HttpResponse:
|
||||||
username = str(request.user.username)
|
username = str(request.user.username)
|
||||||
form = GithubForm
|
form = GithubForm
|
||||||
@ -79,11 +82,11 @@ def demo_view(request: HttpRequest) -> HttpResponse:
|
|||||||
message = f'Total repos: {len(result)}\n'
|
message = f'Total repos: {len(result)}\n'
|
||||||
if len(result) == 0:
|
if len(result) == 0:
|
||||||
result = {'Error': 'User has no repositories!'}
|
result = {'Error': 'User has no repositories!'}
|
||||||
print('Result ready! Please refresh page')
|
console_logger.info('Result ready! Please refresh page')
|
||||||
else:
|
else:
|
||||||
print('result not ready')
|
console_logger.info('result not ready')
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
print(e)
|
console_logger.error(e)
|
||||||
finally:
|
finally:
|
||||||
# Return demo view
|
# Return demo view
|
||||||
return render(request, 'progress.html',
|
return render(request, 'progress.html',
|
||||||
@ -99,7 +102,7 @@ def demo_view(request: HttpRequest) -> HttpResponse:
|
|||||||
# Get ID
|
# Get ID
|
||||||
task_id[username] = result.task_id
|
task_id[username] = result.task_id
|
||||||
# Print Task ID
|
# Print Task ID
|
||||||
print(f'Celery Task ID: {task_id[username]}')
|
console_logger.info(f'Celery Task ID: {task_id[username]}')
|
||||||
# Return demo view with Task ID
|
# Return demo view with Task ID
|
||||||
return render(request, 'progress.html',
|
return render(request, 'progress.html',
|
||||||
context={'task_id': task_id[username],
|
context={'task_id': task_id[username],
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
# Byte-compiled / optimized / DLL files
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
*$py.class
|
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Distribution / packaging
|
|
||||||
.Python
|
|
||||||
env/
|
|
||||||
build/
|
|
||||||
develop-eggs/
|
|
||||||
dist/
|
|
||||||
downloads/
|
|
||||||
eggs/
|
|
||||||
.eggs/
|
|
||||||
lib/
|
|
||||||
lib64/
|
|
||||||
parts/
|
|
||||||
sdist/
|
|
||||||
var/
|
|
||||||
*.egg-info/
|
|
||||||
.installed.cfg
|
|
||||||
*.egg
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
*.pot
|
|
||||||
|
|
||||||
# Django stuff:
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# Sphinx documentation
|
|
||||||
docs/_build/
|
|
||||||
|
|
||||||
# PyBuilder
|
|
||||||
target/
|
|
||||||
|
|
||||||
# Docker
|
|
||||||
Dockerfile
|
|
||||||
docker-compose.yml
|
|
||||||
docker-compose.override.yml
|
|
||||||
docker/docker-compose.prod.yml
|
|
||||||
|
|
||||||
# JetBrains
|
|
||||||
.idea/
|
|
@ -1,25 +0,0 @@
|
|||||||
# Check http://editorconfig.org for more information
|
|
||||||
# This is the main config file for this project:
|
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
charset = utf-8
|
|
||||||
end_of_line = lf
|
|
||||||
insert_final_newline = true
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
|
|
||||||
[*.py]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
|
||||||
|
|
||||||
[*.pyi]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
|
||||||
|
|
||||||
[Makefile]
|
|
||||||
indent_style = tab
|
|
||||||
|
|
||||||
[*.md]
|
|
||||||
trim_trailing_whitespace = false
|
|
247
new-github-repos/.gitignore
vendored
247
new-github-repos/.gitignore
vendored
@ -1,247 +0,0 @@
|
|||||||
#### joe made this: https://goel.io/joe
|
|
||||||
|
|
||||||
# Git style-guide:
|
|
||||||
# https://github.com/agis-/git-style-guide
|
|
||||||
|
|
||||||
#####=== OSX ===#####
|
|
||||||
.DS_Store
|
|
||||||
.AppleDouble
|
|
||||||
.LSOverride
|
|
||||||
|
|
||||||
# Icon must end with two \r
|
|
||||||
Icon
|
|
||||||
|
|
||||||
|
|
||||||
# Thumbnails
|
|
||||||
._*
|
|
||||||
|
|
||||||
# Files that might appear on external disk
|
|
||||||
.Spotlight-V100
|
|
||||||
.Trashes
|
|
||||||
|
|
||||||
# Directories potentially created on remote AFP share
|
|
||||||
.AppleDB
|
|
||||||
.AppleDesktop
|
|
||||||
Network Trash Folder
|
|
||||||
Temporary Items
|
|
||||||
.apdisk
|
|
||||||
|
|
||||||
#####=== Windows ===#####
|
|
||||||
# Windows image file caches
|
|
||||||
Thumbs.db
|
|
||||||
ehthumbs.db
|
|
||||||
|
|
||||||
# Folder config file
|
|
||||||
Desktop.ini
|
|
||||||
|
|
||||||
# Recycle Bin used on file shares
|
|
||||||
$RECYCLE.BIN/
|
|
||||||
|
|
||||||
# Windows Installer files
|
|
||||||
*.cab
|
|
||||||
*.msi
|
|
||||||
*.msm
|
|
||||||
*.msp
|
|
||||||
|
|
||||||
# Windows shortcuts
|
|
||||||
*.lnk
|
|
||||||
|
|
||||||
#####=== Python ===#####
|
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Distribution / packaging
|
|
||||||
.Python
|
|
||||||
env/
|
|
||||||
develop-eggs/
|
|
||||||
dist/
|
|
||||||
downloads/
|
|
||||||
eggs/
|
|
||||||
lib/
|
|
||||||
lib64/
|
|
||||||
parts/
|
|
||||||
sdist/
|
|
||||||
var/
|
|
||||||
*.egg-info/
|
|
||||||
.installed.cfg
|
|
||||||
*.egg
|
|
||||||
|
|
||||||
# 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
|
|
||||||
.cache
|
|
||||||
nosetests.xml
|
|
||||||
coverage.xml
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
*.pot
|
|
||||||
|
|
||||||
# Django stuff:
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# Sphinx documentation
|
|
||||||
docs/_build/
|
|
||||||
|
|
||||||
# PyBuilder
|
|
||||||
target/
|
|
||||||
|
|
||||||
#####=== Sass ===#####
|
|
||||||
|
|
||||||
.sass-cache
|
|
||||||
*.css.map
|
|
||||||
|
|
||||||
#####=== Yeoman ===#####
|
|
||||||
|
|
||||||
node_modules/
|
|
||||||
bower_components/
|
|
||||||
*.log
|
|
||||||
|
|
||||||
build/
|
|
||||||
dist/
|
|
||||||
|
|
||||||
#####=== Vagrant ===#####
|
|
||||||
.vagrant/
|
|
||||||
|
|
||||||
#####=== Node ===#####
|
|
||||||
|
|
||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# Runtime data
|
|
||||||
pids
|
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
||||||
lib-cov
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
coverage
|
|
||||||
|
|
||||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
.grunt
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directory
|
|
||||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
|
|
||||||
node_modules
|
|
||||||
|
|
||||||
# Debug log from npm
|
|
||||||
npm-debug.log
|
|
||||||
|
|
||||||
#### jetbrains ####
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
# You can uncomment these lines to enable configuration sharing between
|
|
||||||
# team members, or you can restrict `.idea/` folder at all (default).
|
|
||||||
|
|
||||||
# User-specific stuff:
|
|
||||||
# .idea/**/workspace.xml
|
|
||||||
# .idea/**/tasks.xml
|
|
||||||
# .idea/dictionaries
|
|
||||||
|
|
||||||
# # Sensitive or high-churn files:
|
|
||||||
# .idea/**/dataSources/
|
|
||||||
# .idea/**/dataSources.ids
|
|
||||||
# .idea/**/dataSources.xml
|
|
||||||
# .idea/**/dataSources.local.xml
|
|
||||||
# .idea/**/sqlDataSources.xml
|
|
||||||
# .idea/**/dynamic.xml
|
|
||||||
# .idea/**/uiDesigner.xml
|
|
||||||
|
|
||||||
# # Gradle:
|
|
||||||
# .idea/**/gradle.xml
|
|
||||||
# .idea/**/libraries
|
|
||||||
|
|
||||||
# # Mongo Explorer plugin:
|
|
||||||
# .idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# # Cursive Clojure plugin
|
|
||||||
# .idea/replstate.xml
|
|
||||||
|
|
||||||
# Restrict `.idea/` folder at all:
|
|
||||||
.idea/
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-debug/
|
|
||||||
|
|
||||||
## File-based project format:
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
## Plugin-specific files:
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
/out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
|
|
||||||
#####=== Custom ===#####
|
|
||||||
# Directories:
|
|
||||||
media/
|
|
||||||
.static/
|
|
||||||
/static/
|
|
||||||
|
|
||||||
# File types:
|
|
||||||
*.sqlite3
|
|
||||||
# .db
|
|
||||||
|
|
||||||
|
|
||||||
# Configuration file with private data:
|
|
||||||
# *.env
|
|
||||||
# .env
|
|
||||||
|
|
||||||
# Local settings files:
|
|
||||||
*local.py
|
|
||||||
|
|
||||||
# Deploy files for Docker:
|
|
||||||
docker-compose.deploy.yml
|
|
||||||
|
|
||||||
# Certificates:
|
|
||||||
docker/caddy/certs/
|
|
||||||
|
|
||||||
# Artifacts:
|
|
||||||
.gitlab/.svn/
|
|
||||||
artifacts/
|
|
||||||
|
|
||||||
# mypy:
|
|
||||||
.mypy_cache/
|
|
||||||
|
|
||||||
# pytest:
|
|
||||||
.pytest_cache/
|
|
||||||
|
|
||||||
# ipython:
|
|
||||||
.ipython/
|
|
@ -1,96 +0,0 @@
|
|||||||
---
|
|
||||||
|
|
||||||
variables:
|
|
||||||
GROUP_NAME: "balsh"
|
|
||||||
PROJECT_NAME: "github-repos"
|
|
||||||
REGISTRY: "registry.gitlab.com"
|
|
||||||
IMAGE_FULL_NAME: "${REGISTRY}/${GROUP_NAME}/${PROJECT_NAME}"
|
|
||||||
|
|
||||||
|
|
||||||
# Base scripts
|
|
||||||
# ============
|
|
||||||
|
|
||||||
.docker:
|
|
||||||
# We use a custom dind image that is based on raw `docker`,
|
|
||||||
# it has all the dependencies required.
|
|
||||||
# By using it we reduce the build time significantly.
|
|
||||||
# You can fallback to use raw `docker` image, see:
|
|
||||||
# https://github.com/wemake-services/wemake-dind/
|
|
||||||
image: wemakeservices/wemake-dind:latest
|
|
||||||
interruptible: true
|
|
||||||
services:
|
|
||||||
- docker:dind
|
|
||||||
variables:
|
|
||||||
DOCKER_DRIVER: overlay2
|
|
||||||
before_script: &docker-before-script
|
|
||||||
# Making sure we are in the right directory, does nothing by default:
|
|
||||||
- pwd && echo "$CI_PROJECT_DIR" && cd "$CI_PROJECT_DIR"
|
|
||||||
# Creating `.env` configuration file:
|
|
||||||
- dump-env -t config/.env.template -p 'SECRET_' > config/.env
|
|
||||||
# Login into Docker registry:
|
|
||||||
- echo "$CI_JOB_TOKEN" | docker login "$REGISTRY"
|
|
||||||
-u gitlab-ci-token --password-stdin
|
|
||||||
# Debug information:
|
|
||||||
- docker info && docker-compose --version && git --version
|
|
||||||
|
|
||||||
|
|
||||||
# Test scripts
|
|
||||||
# ============
|
|
||||||
|
|
||||||
test:
|
|
||||||
stage: test
|
|
||||||
extends: .docker
|
|
||||||
before_script:
|
|
||||||
- *docker-before-script
|
|
||||||
# Pulling cache:
|
|
||||||
- docker pull "${IMAGE_FULL_NAME}:dev" || true
|
|
||||||
- docker tag "${IMAGE_FULL_NAME}:dev" "${PROJECT_NAME}:dev" || true
|
|
||||||
script:
|
|
||||||
# Checking config:
|
|
||||||
- docker-compose -f docker-compose.yml
|
|
||||||
-f docker/docker-compose.prod.yml config --quiet
|
|
||||||
|
|
||||||
# The logic itself:
|
|
||||||
- docker-compose build
|
|
||||||
- docker-compose run --user=root --rm web sh ./docker/ci.sh
|
|
||||||
- disl "${PROJECT_NAME}:dev" 950MiB
|
|
||||||
|
|
||||||
# Pushing back the result for future runs:
|
|
||||||
- docker tag "${PROJECT_NAME}:dev" "${IMAGE_FULL_NAME}:dev"
|
|
||||||
- docker push "${IMAGE_FULL_NAME}:dev"
|
|
||||||
only:
|
|
||||||
- merge_requests
|
|
||||||
|
|
||||||
|
|
||||||
# Release scripts
|
|
||||||
# ===============
|
|
||||||
|
|
||||||
# Releasing image, when in `master` branch,
|
|
||||||
# can be replaced with `kira-release` bot:
|
|
||||||
# https://github.com/wemake-services/kira-release
|
|
||||||
release-image:
|
|
||||||
extends: .docker
|
|
||||||
stage: deploy
|
|
||||||
allow_failure: false
|
|
||||||
before_script:
|
|
||||||
# Build local image to be released to gitlab registry,
|
|
||||||
# modify it to suite your needs as you wish.
|
|
||||||
# We only care about the name of the image:
|
|
||||||
- *docker-before-script
|
|
||||||
|
|
||||||
# Now we need the latest images for cache and improved build times:
|
|
||||||
- docker pull "${IMAGE_FULL_NAME}:latest" || true
|
|
||||||
- docker pull "${IMAGE_FULL_NAME}:dev" || true
|
|
||||||
# Create correct tags:
|
|
||||||
- docker tag "${IMAGE_FULL_NAME}:latest" "${PROJECT_NAME}:latest" || true
|
|
||||||
- docker tag "${IMAGE_FULL_NAME}:dev" "${PROJECT_NAME}:dev" || true
|
|
||||||
|
|
||||||
# Building the image itself:
|
|
||||||
- docker-compose -f docker-compose.yml
|
|
||||||
-f docker/docker-compose.prod.yml build
|
|
||||||
script:
|
|
||||||
- docker push "${IMAGE_FULL_NAME}:latest"
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
environment:
|
|
||||||
name: production # used to track time with 'cycle analytics'
|
|
@ -1 +0,0 @@
|
|||||||
3.8.9
|
|
@ -1,32 +0,0 @@
|
|||||||
# github-repos
|
|
||||||
|
|
||||||
github repos
|
|
||||||
|
|
||||||
This project was generated with [`wemake-django-template`](https://github.com/wemake-services/wemake-django-template). Current template version is: [281e5b90040a710092c5be2dbd6b381c464cdf87](https://github.com/wemake-services/wemake-django-template/tree/281e5b90040a710092c5be2dbd6b381c464cdf87). See what is [updated](https://github.com/wemake-services/wemake-django-template/compare/281e5b90040a710092c5be2dbd6b381c464cdf87...master) since then.
|
|
||||||
|
|
||||||
|
|
||||||
[](https://wemake.services)
|
|
||||||
[](https://github.com/wemake-services/wemake-python-styleguide)
|
|
||||||
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
You will need:
|
|
||||||
|
|
||||||
- `python3.8` (see `pyproject.toml` for full version)
|
|
||||||
- `postgresql` with version `9.6`
|
|
||||||
- `docker` with [version at least](https://docs.docker.com/compose/compose-file/#compose-and-docker-compatibility-matrix) `18.02`
|
|
||||||
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
When developing locally, we use:
|
|
||||||
|
|
||||||
- [`editorconfig`](http://editorconfig.org/) plugin (**required**)
|
|
||||||
- [`poetry`](https://github.com/python-poetry/poetry) (**required**)
|
|
||||||
- `pycharm 2017+` or `vscode`
|
|
||||||
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
Full documentation is available here: [`docs/`](docs).
|
|
@ -1,43 +0,0 @@
|
|||||||
# === General ===
|
|
||||||
|
|
||||||
DOMAIN_NAME=localhost
|
|
||||||
TLS_EMAIL=webmaster@localhost
|
|
||||||
|
|
||||||
|
|
||||||
# === Django ===
|
|
||||||
# Generate yours with:
|
|
||||||
# python3 -c 'from django.utils.crypto import get_random_string; print(get_random_string(50))'
|
|
||||||
|
|
||||||
DJANGO_SECRET_KEY=I^[!b6gyNlXmaI,/{tagz+>:4V]%HJNW(=(@:*T~)g-t47tc7y
|
|
||||||
|
|
||||||
|
|
||||||
# === Database ===
|
|
||||||
|
|
||||||
# These variables are special, since they are consumed
|
|
||||||
# by both django and postgres docker image.
|
|
||||||
# Cannot be renamed if you use postgres in docker.
|
|
||||||
# See: https://hub.docker.com/_/postgres
|
|
||||||
|
|
||||||
POSTGRES_DB=github-repos
|
|
||||||
POSTGRES_USER=github-admin
|
|
||||||
POSTGRES_PASSWORD=admin_password
|
|
||||||
|
|
||||||
# Used only by django:
|
|
||||||
DJANGO_DATABASE_HOST=db
|
|
||||||
DJANGO_DATABASE_PORT=5432
|
|
||||||
|
|
||||||
# ==== Email =======
|
|
||||||
|
|
||||||
EMAIL_HOST=smtp.yandex.ru
|
|
||||||
EMAIL_HOST_USER=balsh-django@yandex.ru
|
|
||||||
EMAIL_HOST_PASSWORD=nifwotooabmfauld
|
|
||||||
EMAIL_PORT=465
|
|
||||||
EMAIL_USE_SSL=True
|
|
||||||
EMAIL_USE_TLS=False
|
|
||||||
|
|
||||||
# =======RabbitMQ=======
|
|
||||||
|
|
||||||
RABBITMQ_DEFAULT_USER=rabbit_admin
|
|
||||||
RABBITMQ_DEFAULT_PASS=mypass
|
|
||||||
RABBITMQ_PORT=5672
|
|
||||||
RABBITMQ_HOST=rabbitmq_docker
|
|
@ -1,70 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
version: "3.7"
|
|
||||||
|
|
||||||
services:
|
|
||||||
db:
|
|
||||||
image: "postgres:12"
|
|
||||||
container_name: github_repos_db
|
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- pgdata:/var/lib/postgresql/data
|
|
||||||
networks:
|
|
||||||
- webnet
|
|
||||||
env_file: ./config/.env
|
|
||||||
ports:
|
|
||||||
- 5433:5432
|
|
||||||
|
|
||||||
web:
|
|
||||||
image: "github-repos"
|
|
||||||
container_name: github_repos_web
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: ./docker/django/Dockerfile
|
|
||||||
# args:
|
|
||||||
# DJANGO_ENV: development
|
|
||||||
# cache_from:
|
|
||||||
# - "github-repos:dev"
|
|
||||||
# - "github-repos:latest"
|
|
||||||
# - "*"
|
|
||||||
# volumes:
|
|
||||||
# - django-static:/var/www/django/static
|
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- .:/code
|
|
||||||
ports:
|
|
||||||
- 8000:8000
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
networks:
|
|
||||||
- webnet
|
|
||||||
env_file: ./config/.env
|
|
||||||
command: >
|
|
||||||
bash -c "python manage.py migrate --noinput
|
|
||||||
&& python -Wd manage.py runserver 0.0.0.0:8000"
|
|
||||||
# healthcheck:
|
|
||||||
# # We use `$$` here because:
|
|
||||||
# # one `$` goes to shell,
|
|
||||||
# # one `$` goes to `docker-compose.yml` escaping
|
|
||||||
# test: |
|
|
||||||
# /usr/bin/test $$(
|
|
||||||
# /usr/bin/curl --fail http://localhost:8000/health/?format=json
|
|
||||||
# --write-out "%{http_code}" --silent --output /dev/null
|
|
||||||
# ) -eq 200
|
|
||||||
# interval: 10s
|
|
||||||
# timeout: 5s
|
|
||||||
# retries: 5
|
|
||||||
# start_period: 30s
|
|
||||||
|
|
||||||
# This task is an example of how to extend existing ones:
|
|
||||||
# some_worker:
|
|
||||||
# <<: *web
|
|
||||||
# command: python manage.py worker_process
|
|
||||||
|
|
||||||
networks:
|
|
||||||
# Network for your internals, use it by default:
|
|
||||||
webnet:
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
pgdata:
|
|
||||||
django-static:
|
|
@ -1,40 +0,0 @@
|
|||||||
# See https://caddyserver.com/docs
|
|
||||||
|
|
||||||
# Email for Let's Encrypt expiration notices
|
|
||||||
{
|
|
||||||
email {$TLS_EMAIL}
|
|
||||||
}
|
|
||||||
|
|
||||||
# "www" redirect to "non-www" version
|
|
||||||
www.{$DOMAIN_NAME} {
|
|
||||||
redir https://{$DOMAIN_NAME}{uri}
|
|
||||||
}
|
|
||||||
|
|
||||||
{$DOMAIN_NAME} {
|
|
||||||
# HTTPS options:
|
|
||||||
header Strict-Transport-Security max-age=31536000;
|
|
||||||
|
|
||||||
# Removing some headers for improved security:
|
|
||||||
header -Server
|
|
||||||
|
|
||||||
# Exclude matcher for Django assets
|
|
||||||
@excludeDirs {
|
|
||||||
not path /static/* /media/*
|
|
||||||
}
|
|
||||||
|
|
||||||
# Serving dynamic requests:
|
|
||||||
reverse_proxy @excludeDirs web:8000
|
|
||||||
|
|
||||||
# Serves static files, should be the same as `STATIC_ROOT` setting:
|
|
||||||
file_server {
|
|
||||||
root /var/www/django
|
|
||||||
}
|
|
||||||
|
|
||||||
# Allows to use `.gz` files when available:
|
|
||||||
encode gzip
|
|
||||||
|
|
||||||
# Logs:
|
|
||||||
log {
|
|
||||||
output stdout
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
|
|
||||||
set -o errexit
|
|
||||||
set -o nounset
|
|
||||||
|
|
||||||
# Initializing global variables and functions:
|
|
||||||
: "${DJANGO_ENV:=development}"
|
|
||||||
|
|
||||||
# Fail CI if `DJANGO_ENV` is not set to `development`:
|
|
||||||
if [ "$DJANGO_ENV" != 'development' ]; then
|
|
||||||
echo 'DJANGO_ENV is not set to development. Running tests is not safe.'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
pyclean () {
|
|
||||||
# Cleaning cache:
|
|
||||||
find . \
|
|
||||||
| grep -E '(__pycache__|\.hypothesis|\.perm|\.cache|\.static|\.py[cod]$)' \
|
|
||||||
| xargs rm -rf
|
|
||||||
}
|
|
||||||
|
|
||||||
run_ci () {
|
|
||||||
echo '[ci started]'
|
|
||||||
set -x # we want to print commands during the CI process.
|
|
||||||
|
|
||||||
# Testing filesystem and permissions:
|
|
||||||
touch .perm && rm -f .perm
|
|
||||||
touch '/var/www/django/media/.perm' && rm -f '/var/www/django/media/.perm'
|
|
||||||
touch '/var/www/django/static/.perm' && rm -f '/var/www/django/static/.perm'
|
|
||||||
|
|
||||||
# Checking `.env` files:
|
|
||||||
dotenv-linter config/.env config/.env.template
|
|
||||||
|
|
||||||
# Running linting for all python files in the project:
|
|
||||||
flake8 .
|
|
||||||
|
|
||||||
# Running type checking, see https://github.com/typeddjango/django-stubs
|
|
||||||
mypy manage.py server $(find tests -name '*.py')
|
|
||||||
|
|
||||||
# Running tests:
|
|
||||||
pytest --dead-fixtures
|
|
||||||
pytest
|
|
||||||
|
|
||||||
# Run checks to be sure we follow all django's best practices:
|
|
||||||
python manage.py check --fail-level WARNING
|
|
||||||
|
|
||||||
# Run checks to be sure settings are correct (production flag is required):
|
|
||||||
DJANGO_ENV=production python manage.py check --deploy --fail-level WARNING
|
|
||||||
|
|
||||||
# Check that staticfiles app is working fine:
|
|
||||||
DJANGO_ENV=production DJANGO_COLLECTSTATIC_DRYRUN=1 \
|
|
||||||
python manage.py collectstatic --no-input --dry-run
|
|
||||||
|
|
||||||
# Check that all migrations worked fine:
|
|
||||||
python manage.py makemigrations --dry-run --check
|
|
||||||
|
|
||||||
# Check that all migrations are backwards compatible:
|
|
||||||
python manage.py lintmigrations --exclude-apps=axes --warnings-as-errors
|
|
||||||
|
|
||||||
# Checking if all the dependencies are secure and do not have any
|
|
||||||
# known vulnerabilities:
|
|
||||||
safety check --full-report
|
|
||||||
|
|
||||||
# Checking `pyproject.toml` file contents:
|
|
||||||
poetry check
|
|
||||||
|
|
||||||
# Checking dependencies status:
|
|
||||||
pip check
|
|
||||||
|
|
||||||
# Checking docs:
|
|
||||||
doc8 -q docs
|
|
||||||
|
|
||||||
# Checking `yaml` files:
|
|
||||||
yamllint -d '{"extends": "default", "ignore": ".venv"}' -s .
|
|
||||||
|
|
||||||
# Checking translation files, ignoring ordering and locations:
|
|
||||||
polint -i location,unsorted locale
|
|
||||||
|
|
||||||
# Also checking translation files for syntax errors:
|
|
||||||
if find locale -name '*.po' -print0 | grep -q "."; then
|
|
||||||
# Only executes when there is at least one `.po` file:
|
|
||||||
dennis-cmd lint --errorsonly locale
|
|
||||||
fi
|
|
||||||
|
|
||||||
set +x
|
|
||||||
echo '[ci finished]'
|
|
||||||
}
|
|
||||||
|
|
||||||
# Remove any cache before the script:
|
|
||||||
pyclean
|
|
||||||
|
|
||||||
# Clean everything up:
|
|
||||||
trap pyclean EXIT INT TERM
|
|
||||||
|
|
||||||
# Run the CI process:
|
|
||||||
run_ci
|
|
@ -1,46 +0,0 @@
|
|||||||
FROM python:3.8.9-slim-buster
|
|
||||||
|
|
||||||
ENV BUILD_ONLY_PACKAGES='wget' \
|
|
||||||
# python:
|
|
||||||
PYTHONFAULTHANDLER=1 \
|
|
||||||
PYTHONUNBUFFERED=1 \
|
|
||||||
PYTHONHASHSEED=random \
|
|
||||||
PYTHONDONTWRITEBYTECODE=1 \
|
|
||||||
# pip:
|
|
||||||
PIP_NO_CACHE_DIR=off \
|
|
||||||
PIP_DISABLE_PIP_VERSION_CHECK=on \
|
|
||||||
PIP_DEFAULT_TIMEOUT=100 \
|
|
||||||
# poetry:
|
|
||||||
POETRY_VERSION=1.1.4 \
|
|
||||||
POETRY_NO_INTERACTION=1 \
|
|
||||||
POETRY_VIRTUALENVS_CREATE=false \
|
|
||||||
POETRY_CACHE_DIR='/var/cache/pypoetry' \
|
|
||||||
PATH="$PATH:/root/.poetry/bin"
|
|
||||||
|
|
||||||
# System deps:
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get install --no-install-recommends -y \
|
|
||||||
bash \
|
|
||||||
build-essential \
|
|
||||||
curl \
|
|
||||||
gettext \
|
|
||||||
git \
|
|
||||||
libpq-dev \
|
|
||||||
nano \
|
|
||||||
# Defining build-time-only dependencies:
|
|
||||||
$BUILD_ONLY_PACKAGES \
|
|
||||||
&& curl -sSL 'https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py' | python \
|
|
||||||
&& poetry --version \
|
|
||||||
# Removing build-time-only dependencies:
|
|
||||||
&& apt-get remove -y $BUILD_ONLY_PACKAGES \
|
|
||||||
# Cleaning cache:
|
|
||||||
&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
|
|
||||||
&& apt-get clean -y && rm -rf /var/lib/apt/lists/* \
|
|
||||||
&& rm -rf $POETRY_CACHE_DIR
|
|
||||||
|
|
||||||
WORKDIR /code
|
|
||||||
|
|
||||||
# Copy only requirements, to cache them in docker layer
|
|
||||||
COPY ./poetry.lock ./pyproject.toml /code/
|
|
||||||
RUN poetry install
|
|
||||||
COPY . /code
|
|
@ -1,24 +0,0 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
|
|
||||||
set -o errexit
|
|
||||||
set -o nounset
|
|
||||||
|
|
||||||
readonly cmd="$*"
|
|
||||||
|
|
||||||
postgres_ready () {
|
|
||||||
# Check that postgres is up and running on port `5432`:
|
|
||||||
dockerize -wait 'tcp://db:5432' -timeout 5s
|
|
||||||
}
|
|
||||||
|
|
||||||
# We need this line to make sure that this container is started
|
|
||||||
# after the one with postgres:
|
|
||||||
until postgres_ready; do
|
|
||||||
>&2 echo 'Postgres is unavailable - sleeping'
|
|
||||||
done
|
|
||||||
|
|
||||||
# It is also possible to wait for other services as well: redis, elastic, mongo
|
|
||||||
>&2 echo 'Postgres is up - continuing...'
|
|
||||||
|
|
||||||
# Evaluating passed command (do not touch):
|
|
||||||
# shellcheck disable=SC2086
|
|
||||||
exec $cmd
|
|
@ -1,41 +0,0 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
|
|
||||||
set -o errexit
|
|
||||||
set -o nounset
|
|
||||||
|
|
||||||
# We are using `gunicorn` for production, see:
|
|
||||||
# http://docs.gunicorn.org/en/stable/configure.html
|
|
||||||
|
|
||||||
# Check that $DJANGO_ENV is set to "production",
|
|
||||||
# fail otherwise, since it may break things:
|
|
||||||
|
|
||||||
|
|
||||||
echo "DJANGO_ENV is $DJANGO_ENV"
|
|
||||||
if [ "$DJANGO_ENV" != 'production' ]; then
|
|
||||||
echo 'Error: DJANGO_ENV is not set to "production".'
|
|
||||||
echo 'Application will not start.'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
export DJANGO_ENV
|
|
||||||
|
|
||||||
|
|
||||||
# Run python specific scripts:
|
|
||||||
# Running migrations in startup script might not be the best option, see:
|
|
||||||
# docs/pages/template/production-checklist.rst
|
|
||||||
python /code/manage.py migrate --noinput
|
|
||||||
python /code/manage.py collectstatic --noinput
|
|
||||||
python /code/manage.py compilemessages
|
|
||||||
|
|
||||||
# Start gunicorn:
|
|
||||||
# Docs: http://docs.gunicorn.org/en/stable/settings.html
|
|
||||||
# Concerning `workers` setting see:
|
|
||||||
# https://github.com/wemake-services/wemake-django-template/issues/1022
|
|
||||||
/usr/local/bin/gunicorn server.wsgi \
|
|
||||||
--workers=4 `# Sync worker settings` \
|
|
||||||
--max-requests=2000 \
|
|
||||||
--max-requests-jitter=400 \
|
|
||||||
--bind='0.0.0.0:8000' `# Run Django on 8000 port` \
|
|
||||||
--chdir='/code' `# Locations` \
|
|
||||||
--log-file=- \
|
|
||||||
--worker-tmp-dir='/dev/shm'
|
|
@ -1,65 +0,0 @@
|
|||||||
---
|
|
||||||
|
|
||||||
# This compose-file is production only. So, it should not be called directly.
|
|
||||||
#
|
|
||||||
# Instead, it should be a part of your deploy strategy.
|
|
||||||
# This setup is supposed to be used with `docker-swarm`.
|
|
||||||
# See `./docs/pages/template/production.rst` docs.
|
|
||||||
|
|
||||||
version: "3.6"
|
|
||||||
services:
|
|
||||||
caddy:
|
|
||||||
image: "caddy:2.2.1"
|
|
||||||
restart: unless-stopped
|
|
||||||
env_file: ./config/.env
|
|
||||||
volumes:
|
|
||||||
- ./docker/caddy/Caddyfile:/etc/caddy/Caddyfile # configuration
|
|
||||||
- caddy-config:/config # configuration autosaves
|
|
||||||
- caddy-data:/data # saving certificates
|
|
||||||
- django-static:/var/www/django/static # serving django's statics
|
|
||||||
- django-media:/var/www/django/media # serving django's media
|
|
||||||
ports:
|
|
||||||
- "80:80"
|
|
||||||
- "443:443"
|
|
||||||
depends_on:
|
|
||||||
- web
|
|
||||||
networks:
|
|
||||||
- proxynet
|
|
||||||
|
|
||||||
web:
|
|
||||||
<<: &web
|
|
||||||
# Image for production:
|
|
||||||
image: "registry.gitlab.com/balsh/github-repos:latest"
|
|
||||||
build:
|
|
||||||
target: production_build
|
|
||||||
args:
|
|
||||||
DJANGO_ENV: production
|
|
||||||
|
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- django-media:/var/www/django/media # since in dev it is app's folder
|
|
||||||
- django-locale:/code/locale # since in dev it is app's folder
|
|
||||||
|
|
||||||
command: sh ./docker/django/gunicorn.sh
|
|
||||||
networks:
|
|
||||||
- proxynet
|
|
||||||
expose:
|
|
||||||
- 8000
|
|
||||||
|
|
||||||
# This task is an example of how to extend existing ones:
|
|
||||||
# some_wroker:
|
|
||||||
# <<: *web
|
|
||||||
# command: python manage.py worker_process
|
|
||||||
# deploy:
|
|
||||||
# replicas: 2
|
|
||||||
|
|
||||||
networks:
|
|
||||||
# Network for your proxy server and application to connect them,
|
|
||||||
# do not use it for anything else!
|
|
||||||
proxynet:
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
django-media:
|
|
||||||
django-locale:
|
|
||||||
caddy-config:
|
|
||||||
caddy-data:
|
|
@ -1,20 +0,0 @@
|
|||||||
# Minimal makefile for Sphinx documentation
|
|
||||||
#
|
|
||||||
|
|
||||||
# You can set these variables from the command line.
|
|
||||||
SPHINXOPTS =
|
|
||||||
SPHINXBUILD = python -msphinx
|
|
||||||
SPHINXPROJ = wemake-django-template
|
|
||||||
SOURCEDIR = .
|
|
||||||
BUILDDIR = _build
|
|
||||||
|
|
||||||
# Put it first so that "make" without argument is like "make help".
|
|
||||||
help:
|
|
||||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
||||||
|
|
||||||
.PHONY: help Makefile
|
|
||||||
|
|
||||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
|
||||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
|
||||||
%: Makefile
|
|
||||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
@ -1,18 +0,0 @@
|
|||||||
# Starting with the docs
|
|
||||||
|
|
||||||
We are using [Sphinx](http://www.sphinx-doc.org) to manage our documentation.
|
|
||||||
If you have never worked with `Sphinx` this guide
|
|
||||||
will cover the most common uses cases.
|
|
||||||
|
|
||||||
|
|
||||||
## Quickstart
|
|
||||||
|
|
||||||
1. Clone this repository
|
|
||||||
2. Install dependencies, [here's how to do it](pages/template/development.rst)
|
|
||||||
3. Run `cd docs && make html`
|
|
||||||
4. Open `_build/html/index.html` with your browser
|
|
||||||
|
|
||||||
|
|
||||||
## Where to go next
|
|
||||||
|
|
||||||
Read the main page of the opened documentation website. It will guide you.
|
|
0
new-github-repos/docs/_static/.gitkeep
vendored
0
new-github-repos/docs/_static/.gitkeep
vendored
28
new-github-repos/docs/_templates/moreinfo.html
vendored
28
new-github-repos/docs/_templates/moreinfo.html
vendored
@ -1,28 +0,0 @@
|
|||||||
<h3>
|
|
||||||
Links
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/wemake-services/wemake-django-template">
|
|
||||||
GitHub
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="http://wemake.services/meta">
|
|
||||||
Meta
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="http://wemake.services/">
|
|
||||||
wemake.services
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a aria-label="Star wemake-services/wemake-django-template on GitHub" data-count-aria-label="# stargazers on GitHub" data-count-api="/repos/wemake-services/wemake-django-template#stargazers_count" data-count-href="wemake-services/wemake-django-template/stargazers" data-style="mega" data-icon="octicon-star" href="https://github.com/wemake-services/wemake-django-template" class="github-button">Star</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<script async defer src="https://buttons.github.io/buttons.js"></script>
|
|
@ -1,136 +0,0 @@
|
|||||||
# wemake-django-template documentation build configuration file, created by
|
|
||||||
# sphinx-quickstart on Sat Sep 30 12:42:34 2017.
|
|
||||||
#
|
|
||||||
# This file is execfile()d with the current directory set to its
|
|
||||||
# containing dir.
|
|
||||||
#
|
|
||||||
# Note that not all possible configuration values are present in this
|
|
||||||
# autogenerated file.
|
|
||||||
#
|
|
||||||
# All configuration values have a default; values that are commented out
|
|
||||||
# serve to show the default.
|
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from contextlib import suppress
|
|
||||||
|
|
||||||
import tomlkit
|
|
||||||
|
|
||||||
sys.path.insert(0, os.path.abspath('..'))
|
|
||||||
|
|
||||||
# We need this block, because
|
|
||||||
# django might be not installed, maybe we are running our
|
|
||||||
# build process in ReadTheDocs?
|
|
||||||
# https://github.com/wemake-services/wemake-django-template/issues/133
|
|
||||||
with suppress(ImportError):
|
|
||||||
import django # noqa: WPS433
|
|
||||||
|
|
||||||
# Normal django setup. That's how it should be in development:
|
|
||||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'server.settings'
|
|
||||||
django.setup()
|
|
||||||
|
|
||||||
|
|
||||||
# -- Project information -----------------------------------------------------
|
|
||||||
|
|
||||||
def _get_project_meta():
|
|
||||||
with open('../pyproject.toml') as pyproject:
|
|
||||||
file_contents = pyproject.read()
|
|
||||||
|
|
||||||
return tomlkit.parse(file_contents)['tool']['poetry']
|
|
||||||
|
|
||||||
|
|
||||||
pkg_meta = _get_project_meta()
|
|
||||||
project = str(pkg_meta['name'])
|
|
||||||
author = str(pkg_meta['authors'][0])
|
|
||||||
copyright = author # noqa: WPS125
|
|
||||||
|
|
||||||
# The short X.Y version
|
|
||||||
version = str(pkg_meta['version'])
|
|
||||||
# The full version, including alpha/beta/rc tags
|
|
||||||
release = version
|
|
||||||
|
|
||||||
|
|
||||||
# -- General configuration ------------------------------------------------
|
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
|
||||||
needs_sphinx = '3.3'
|
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
|
||||||
# ones.
|
|
||||||
extensions = [
|
|
||||||
'sphinx.ext.autodoc',
|
|
||||||
'sphinx.ext.doctest',
|
|
||||||
'sphinx.ext.todo',
|
|
||||||
'sphinx.ext.coverage',
|
|
||||||
'sphinx.ext.viewcode',
|
|
||||||
'sphinx.ext.githubpages',
|
|
||||||
'sphinx.ext.napoleon',
|
|
||||||
|
|
||||||
# 3rd party, order matters:
|
|
||||||
# https://github.com/wemake-services/wemake-django-template/issues/159
|
|
||||||
'sphinx_autodoc_typehints',
|
|
||||||
]
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
|
||||||
templates_path = ['_templates']
|
|
||||||
|
|
||||||
# The suffix(es) of source filenames.
|
|
||||||
# You can specify multiple suffix as a list of string:
|
|
||||||
source_suffix = ['.rst']
|
|
||||||
|
|
||||||
# The master toctree document.
|
|
||||||
master_doc = 'index'
|
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
|
||||||
# for a list of supported languages.
|
|
||||||
#
|
|
||||||
# This is also used if you do content translation via gettext catalogs.
|
|
||||||
# Usually you set "language" from the command line for these cases.
|
|
||||||
language = None
|
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
|
||||||
# directories to ignore when looking for source files.
|
|
||||||
# This patterns also effect to html_static_path and html_extra_path
|
|
||||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
|
||||||
pygments_style = 'sphinx'
|
|
||||||
|
|
||||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
|
||||||
todo_include_todos = True
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output ----------------------------------------------
|
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
|
||||||
# a list of builtin themes.
|
|
||||||
html_theme = 'alabaster'
|
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
|
||||||
# further. For a list of options available for each theme, see the
|
|
||||||
# documentation.
|
|
||||||
html_theme_options = {}
|
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
|
||||||
html_static_path = ['_static']
|
|
||||||
|
|
||||||
# Custom sidebar templates, must be a dictionary that maps document names
|
|
||||||
# to template names.
|
|
||||||
#
|
|
||||||
# This is required for the alabaster theme
|
|
||||||
# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
|
|
||||||
html_sidebars = {
|
|
||||||
'**': [
|
|
||||||
'about.html',
|
|
||||||
'navigation.html',
|
|
||||||
'moreinfo.html',
|
|
||||||
'searchbox.html',
|
|
||||||
],
|
|
||||||
}
|
|
@ -1,102 +0,0 @@
|
|||||||
Welcome to wemake-django-template's documentation!
|
|
||||||
==================================================
|
|
||||||
|
|
||||||
|
|
||||||
What this project is all about?
|
|
||||||
The main idea of this project is to provide a fully configured
|
|
||||||
template for ``django`` projects, where code quality, testing,
|
|
||||||
documentation, security, and scalability are number one priorities.
|
|
||||||
|
|
||||||
This template is a result of implementing
|
|
||||||
`our processes <https://github.com/wemake-services/meta>`_,
|
|
||||||
it should not be considered as an independent part.
|
|
||||||
|
|
||||||
|
|
||||||
Goals
|
|
||||||
-----
|
|
||||||
|
|
||||||
When developing this template we had several goals in mind:
|
|
||||||
|
|
||||||
- Development environment should be bootstrapped easily,
|
|
||||||
so we use ``docker-compose`` for that
|
|
||||||
- Development should be consistent, so we use strict quality and style checks
|
|
||||||
- Development, testing, and production should have the same environment,
|
|
||||||
so again we develop, test, and run our apps in ``docker`` containers
|
|
||||||
- Documentation and codebase are the only sources of truth
|
|
||||||
|
|
||||||
|
|
||||||
Limitations
|
|
||||||
-----------
|
|
||||||
|
|
||||||
This project implies that:
|
|
||||||
|
|
||||||
- You are using ``docker`` for deployment
|
|
||||||
- You are using Gitlab and Gitlab CI
|
|
||||||
- You are not using any frontend assets in ``django``,
|
|
||||||
you store your frontend separately
|
|
||||||
|
|
||||||
|
|
||||||
Should I choose this template?
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
This template is oriented on big projects,
|
|
||||||
when there are multiple people working on it for a long period of time.
|
|
||||||
|
|
||||||
If you want to simply create a working prototype without all these
|
|
||||||
limitations and workflows - feel free to choose any
|
|
||||||
`other template <https://github.com/audreyr/cookiecutter#python-django>`_.
|
|
||||||
|
|
||||||
|
|
||||||
How to start
|
|
||||||
------------
|
|
||||||
|
|
||||||
You should start with reading the documentation.
|
|
||||||
Reading order is important.
|
|
||||||
|
|
||||||
There are multiple processes that you need to get familiar with:
|
|
||||||
|
|
||||||
- First time setup phase: what system requirements you must fulfill,
|
|
||||||
how to install dependencies, how to start your project
|
|
||||||
- Active development phase: how to make changes, run tests,
|
|
||||||
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
:caption: Setting things up:
|
|
||||||
|
|
||||||
pages/template/overview.rst
|
|
||||||
pages/template/development.rst
|
|
||||||
pages/template/django.rst
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
:caption: Quality assurance:
|
|
||||||
|
|
||||||
pages/template/documentation.rst
|
|
||||||
pages/template/linters.rst
|
|
||||||
pages/template/testing.rst
|
|
||||||
pages/template/security.rst
|
|
||||||
pages/template/gitlab-ci.rst
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
:caption: Production:
|
|
||||||
|
|
||||||
pages/template/production-checklist.rst
|
|
||||||
pages/template/production.rst
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
:caption: Extras:
|
|
||||||
|
|
||||||
pages/template/upgrading-template.rst
|
|
||||||
pages/template/faq.rst
|
|
||||||
pages/template/troubleshooting.rst
|
|
||||||
|
|
||||||
|
|
||||||
Indexes and tables
|
|
||||||
==================
|
|
||||||
|
|
||||||
* :ref:`genindex`
|
|
||||||
* :ref:`modindex`
|
|
||||||
* :ref:`search`
|
|
@ -1,36 +0,0 @@
|
|||||||
@ECHO OFF
|
|
||||||
|
|
||||||
pushd %~dp0
|
|
||||||
|
|
||||||
REM Command file for Sphinx documentation
|
|
||||||
|
|
||||||
if "%SPHINXBUILD%" == "" (
|
|
||||||
set SPHINXBUILD=sphinx-build
|
|
||||||
)
|
|
||||||
set SOURCEDIR=.
|
|
||||||
set BUILDDIR=_build
|
|
||||||
set SPHINXPROJ=wemake-django-template
|
|
||||||
|
|
||||||
if "%1" == "" goto help
|
|
||||||
|
|
||||||
%SPHINXBUILD% >NUL 2>NUL
|
|
||||||
if errorlevel 9009 (
|
|
||||||
echo.
|
|
||||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
|
||||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
|
||||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
|
||||||
echo.may add the Sphinx directory to PATH.
|
|
||||||
echo.
|
|
||||||
echo.If you don't have Sphinx installed, grab it from
|
|
||||||
echo.http://sphinx-doc.org/
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
|
||||||
goto end
|
|
||||||
|
|
||||||
:help
|
|
||||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
|
||||||
|
|
||||||
:end
|
|
||||||
popd
|
|
@ -1,178 +0,0 @@
|
|||||||
Development
|
|
||||||
===========
|
|
||||||
|
|
||||||
Our development process is focused on high quality and development comfort.
|
|
||||||
We use tools that are proven to be the best in class.
|
|
||||||
|
|
||||||
There are two possible ways to develop your apps.
|
|
||||||
|
|
||||||
1. local development
|
|
||||||
2. development inside ``docker``
|
|
||||||
|
|
||||||
You can choose one or use both at the same time.
|
|
||||||
How to choose what method should you use?
|
|
||||||
|
|
||||||
Local development is much easier and much faster.
|
|
||||||
You can choose it if you don't have too many infrastructure dependencies.
|
|
||||||
That's a default option for the new projects.
|
|
||||||
|
|
||||||
Choosing ``docker`` development means that you already have a complex
|
|
||||||
setup of different technologies, containers, networks, etc.
|
|
||||||
This is a default option for older and more complicated projects.
|
|
||||||
|
|
||||||
|
|
||||||
Dependencies
|
|
||||||
------------
|
|
||||||
|
|
||||||
We use ``poetry`` to manage dependencies.
|
|
||||||
So, please do not use ``virtualenv`` or ``pip`` directly.
|
|
||||||
Before going any further, please,
|
|
||||||
take a moment to read the `official documentation <https://poetry.eustace.io/>`_
|
|
||||||
about ``poetry`` to know some basics.
|
|
||||||
|
|
||||||
If you are using ``docker`` then prepend ``docker-compose run --rm web``
|
|
||||||
before any of those commands to execute them.
|
|
||||||
|
|
||||||
Please, note that you don't need almost all of them with ``docker``.
|
|
||||||
You can just skip this sub-section completely.
|
|
||||||
Go right to `Development with docker`_.
|
|
||||||
|
|
||||||
Installing dependencies
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
You do not need to run any of these command for ``docker`` based development,
|
|
||||||
since it is already executed inside ``Dockerfile``.
|
|
||||||
|
|
||||||
Please, note that ``poetry`` will automatically create a ``virtualenv`` for
|
|
||||||
this project. It will use you current ``python`` version.
|
|
||||||
To install all existing dependencies run:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
poetry install
|
|
||||||
|
|
||||||
To install dependencies for production use, you will need to run:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
poetry install --no-dev
|
|
||||||
|
|
||||||
And to activate ``virtualenv`` created by ``poetry`` run:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
poetry shell
|
|
||||||
|
|
||||||
Adding new dependencies
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
To add a new dependency you can run:
|
|
||||||
|
|
||||||
- ``poetry add django`` to install ``django`` as a production dependency
|
|
||||||
- ``poetry add --dev pytest`` to install ``pytest``
|
|
||||||
as a development dependency
|
|
||||||
|
|
||||||
This command might be used with ``docker``.
|
|
||||||
|
|
||||||
Updating poetry version
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Package managers should also be pinned very strictly.
|
|
||||||
We had a lot of problems in production
|
|
||||||
because we were not pinning package manager versions.
|
|
||||||
|
|
||||||
This can result in broken ``lock`` files, inconsistent installation process,
|
|
||||||
bizarre bugs, and missing packages. You do not want to experience that!
|
|
||||||
|
|
||||||
How can we have the same ``poetry`` version for all users in a project?
|
|
||||||
That's where ``[build-system]`` tag shines. It specifies the exact version of
|
|
||||||
your ``poetry`` installation that must be used for the project.
|
|
||||||
Version mismatch will fail your build.
|
|
||||||
|
|
||||||
When you want to update ``poetry``, you have to bump it in several places:
|
|
||||||
|
|
||||||
1. ``pyproject.toml``
|
|
||||||
2. ``docker/django/Dockerfile``
|
|
||||||
|
|
||||||
Then you are fine!
|
|
||||||
|
|
||||||
|
|
||||||
Development with docker
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
To start development server inside ``docker`` you will need to run:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
docker-compose build
|
|
||||||
docker-compose run --rm web python manage.py migrate
|
|
||||||
docker-compose up
|
|
||||||
|
|
||||||
Running scripts inside docker
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
As we have already mentioned inside the previous section
|
|
||||||
we use ``docker-compose run`` to run scripts inside docker.
|
|
||||||
|
|
||||||
What do you need to know about it?
|
|
||||||
|
|
||||||
1. You can run anything you want: ``poetry``, ``python``, ``sh``, etc
|
|
||||||
2. Most likely it will have a permanent effect, due to ``docker volumes``
|
|
||||||
3. You need to use ``--rm`` to automatically remove this container afterward
|
|
||||||
|
|
||||||
**Note**: ``docker`` commands do not need to use ``virtualenv`` at all.
|
|
||||||
|
|
||||||
Extra configuration
|
|
||||||
~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
You might want to tweak ``INTERNAL_IPS`` ``django`` setting
|
|
||||||
to include your ``docker`` container address into it.
|
|
||||||
Otherwise ``django-debug-toolbar`` might not show up.
|
|
||||||
|
|
||||||
To get your ``docker`` ip run:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
docker inspect your-container-name | grep -e '"Gateway"'
|
|
||||||
|
|
||||||
You can also configure a permanent hostname inside your ``/etc/hosts`` to
|
|
||||||
access your ``docker`` containers with a permanent hostname.
|
|
||||||
|
|
||||||
|
|
||||||
Local development
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
When cloning a project for the first time you may
|
|
||||||
need to configure it properly,
|
|
||||||
see :ref:`django` section for more information.
|
|
||||||
|
|
||||||
**Note**, that you will need to activate ``virtualenv`` created
|
|
||||||
by ``poetry`` before running any of these commands.
|
|
||||||
**Note**, that you only need to run these commands once per project.
|
|
||||||
|
|
||||||
Local database
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
When using local development environment without ``docker``,
|
|
||||||
you will need a ``postgres`` up and running.
|
|
||||||
To create new development database run
|
|
||||||
(make sure that database and user names are correct for your case):
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
psql postgres -U postgres -f sql/create_database.sql
|
|
||||||
|
|
||||||
Then migrate your database:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
python manage.py migrate
|
|
||||||
|
|
||||||
Running project
|
|
||||||
~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
If you have reached this point, you should be able to run the project.
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
python manage.py runserver
|
|
@ -1,167 +0,0 @@
|
|||||||
.. _django:
|
|
||||||
|
|
||||||
Django
|
|
||||||
======
|
|
||||||
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
-------------
|
|
||||||
|
|
||||||
We share the same configuration structure for almost every possible
|
|
||||||
environment.
|
|
||||||
|
|
||||||
We use:
|
|
||||||
|
|
||||||
- ``django-split-settings`` to organize ``django``
|
|
||||||
settings into multiple files and directories
|
|
||||||
- ``.env`` files to store secret configuration
|
|
||||||
- ``python-decouple`` to load ``.env`` files into ``django``
|
|
||||||
|
|
||||||
Components
|
|
||||||
~~~~~~~~~~
|
|
||||||
|
|
||||||
If you have some specific components like ``celery`` or ``mailgun`` installed,
|
|
||||||
they could be configured in separate files.
|
|
||||||
Just create a new file in ``server/settings/components/``.
|
|
||||||
Then add it into ``server/settings/__init__.py``.
|
|
||||||
|
|
||||||
Environments
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
To run ``django`` on different environments just
|
|
||||||
specify ``DJANGO_ENV`` environment variable.
|
|
||||||
It must have the same name as one of the files
|
|
||||||
from ``server/settings/environments/``.
|
|
||||||
Then, values from this file will override other settings.
|
|
||||||
|
|
||||||
Local settings
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
If you need some specific local configuration tweaks,
|
|
||||||
you can create file ``server/settings/environments/local.py.template``
|
|
||||||
to ``server/settings/environments/local.py``.
|
|
||||||
It will be loaded into your settings automatically if exists.
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
cp server/settings/environments/local.py.template server/settings/environments/local.py
|
|
||||||
|
|
||||||
See ``local.py.template`` version for the reference.
|
|
||||||
|
|
||||||
|
|
||||||
Secret settings
|
|
||||||
---------------
|
|
||||||
|
|
||||||
We share the same mechanism for secret settings for all our tools.
|
|
||||||
We use ``.env`` files for ``django``, ``postgres``, ``docker``, etc.
|
|
||||||
|
|
||||||
Initially, you will need to copy file
|
|
||||||
``config/.env.template`` to ``config/.env``:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
cp config/.env.template config/.env
|
|
||||||
|
|
||||||
When adding any new secret ``django`` settings you will need to:
|
|
||||||
|
|
||||||
1. Add new key and value to ``config/.env``
|
|
||||||
2. Add new key without value to ``config/.env.template``,
|
|
||||||
add a comment on how to get this value for other users
|
|
||||||
3. Add new variable inside ``django`` settings
|
|
||||||
4. Use ``python-decouple`` to load this ``env`` variable like so:
|
|
||||||
``MY_SECRET = config('MY_SECRET')``
|
|
||||||
|
|
||||||
|
|
||||||
Secret settings in production
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
We do not store our secret settings inside our source code.
|
|
||||||
All sensible settings are stored in ``config/.env`` file,
|
|
||||||
which is not tracked by the version control.
|
|
||||||
|
|
||||||
So, how do we store secrets? We store them as secret environment variables
|
|
||||||
in `GitLab CI <https://docs.gitlab.com/ce/ci/variables/README.html#secret-variables>`_.
|
|
||||||
Then we use `dump-env <https://github.com/sobolevn/dump-env>`_
|
|
||||||
to dump variables from both environment and ``.env`` file template.
|
|
||||||
Then, this file is copied inside ``docker`` image and when
|
|
||||||
this image is built - everything is ready for production.
|
|
||||||
|
|
||||||
Here's an example:
|
|
||||||
|
|
||||||
1. We add a ``SECRET_DJANGO_SECRET_KEY`` variable to Gitlab CI secret variables
|
|
||||||
2. Then ``dump-env`` dumps ``SECRET_DJANGO_SECRET_KEY``
|
|
||||||
as ``DJANGO_SECRET_KEY`` and writes it to ``config/.env`` file
|
|
||||||
3. Then it is loaded by ``django`` inside the settings:
|
|
||||||
``SECRET_KEY = config('DJANGO_SECRET_KEY')``
|
|
||||||
|
|
||||||
However, there are different options to store secret settings:
|
|
||||||
|
|
||||||
- `ansible-vault <https://docs.ansible.com/ansible/2.4/vault.html>`_
|
|
||||||
- `git-secret <https://github.com/sobolevn/git-secret>`_
|
|
||||||
- `Vault <https://www.vaultproject.io/>`_
|
|
||||||
|
|
||||||
Depending on a project we use different tools.
|
|
||||||
With ``dump-env`` being the default and the simplest one.
|
|
||||||
|
|
||||||
|
|
||||||
Extensions
|
|
||||||
----------
|
|
||||||
|
|
||||||
We use different ``django`` extensions that make your life easier.
|
|
||||||
Here's a full list of the extensions for both development and production:
|
|
||||||
|
|
||||||
- `django-split-settings`_ - organize
|
|
||||||
``django`` settings into multiple files and directories.
|
|
||||||
Easily override and modify settings.
|
|
||||||
Use wildcards in settings file paths and mark settings files as optional
|
|
||||||
- `django-axes`_ - keep track
|
|
||||||
of failed login attempts in ``django`` powered sites
|
|
||||||
- `django-csp`_ - `Content Security Policy`_ for ``django``
|
|
||||||
- `django-referrer-policy`_ - middleware implementing the `Referrer-Policy`_
|
|
||||||
- `django-health-check`_ - checks for various conditions and provides reports
|
|
||||||
when anomalous behavior is detected
|
|
||||||
- `django-add-default-value`_ - this django Migration Operation can be used to
|
|
||||||
transfer a Fields default value to the database scheme
|
|
||||||
- `django-deprecate-fields`_ - this package allows deprecating model fields and
|
|
||||||
allows removing them in a backwards compatible manner
|
|
||||||
- `django-migration-linter`_ - detect backward incompatible migrations for
|
|
||||||
your django project
|
|
||||||
- `zero-downtime-migrations`_ - apply ``django`` migrations on PostgreSql
|
|
||||||
without long locks on tables
|
|
||||||
|
|
||||||
Development only extensions:
|
|
||||||
|
|
||||||
- `django-debug-toolbar`_ - a configurable set of panels that
|
|
||||||
display various debug information about the current request/response
|
|
||||||
- `django-querycount`_ - middleware that prints the number
|
|
||||||
of DB queries to the runserver console
|
|
||||||
- `nplusone`_ - auto-detecting the `n+1 queries problem`_ in ``django``
|
|
||||||
|
|
||||||
.. _django-split-settings: https://github.com/sobolevn/django-split-settings
|
|
||||||
.. _django-axes: https://github.com/jazzband/django-axes
|
|
||||||
.. _django-csp: https://github.com/mozilla/django-csp
|
|
||||||
.. _`Content Security Policy`: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
|
|
||||||
.. _django-referrer-policy: https://github.com/ubernostrum/django-referrer-policy
|
|
||||||
.. _`Referrer-Policy`: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
|
|
||||||
.. _django-health-check: https://github.com/KristianOellegaard/django-health-check
|
|
||||||
.. _django-add-default-value: https://github.com/3YOURMIND/django-add-default-value
|
|
||||||
.. _django-deprecate-fields: https://github.com/3YOURMIND/django-deprecate-fields
|
|
||||||
.. _django-migration-linter: https://github.com/3YOURMIND/django-migration-linter
|
|
||||||
.. _zero-downtime-migrations: https://github.com/yandex/zero-downtime-migrations
|
|
||||||
.. _django-debug-toolbar: https://github.com/jazzband/django-debug-toolbar
|
|
||||||
.. _django-querycount: https://github.com/bradmontgomery/django-querycount
|
|
||||||
.. _nplusone: https://github.com/jmcarp/nplusone
|
|
||||||
.. _`n+1 queries problem`: https://stackoverflow.com/questions/97197/what-is-the-n1-select-query-issue
|
|
||||||
|
|
||||||
|
|
||||||
Further reading
|
|
||||||
---------------
|
|
||||||
|
|
||||||
- `django-split-settings tutorial <https://medium.com/wemake-services/managing-djangos-settings-e2b7f496120d>`_
|
|
||||||
- `docker env-file docs <https://docs.docker.com/compose/env-file/>`_
|
|
||||||
|
|
||||||
|
|
||||||
Django admin
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
- `Django Admin Cookbook <https://books.agiliq.com/projects/django-admin-cookbook/en/latest/>`_
|
|
@ -1,98 +0,0 @@
|
|||||||
Documentation
|
|
||||||
=============
|
|
||||||
|
|
||||||
`We <https://github.com/wemake-services/meta>`_ write a lot of documentation.
|
|
||||||
Since we believe, that documentation is a crucial factor
|
|
||||||
which defines project success or failure.
|
|
||||||
|
|
||||||
Here's how we write docs for ``django`` projects.
|
|
||||||
|
|
||||||
|
|
||||||
Dependencies
|
|
||||||
------------
|
|
||||||
|
|
||||||
We are using ``sphinx`` as a documentation builder.
|
|
||||||
We use ``sphinx.ext.napoleon`` to write
|
|
||||||
pretty docstrings inside the source code.
|
|
||||||
We also use ``sphinx_autodoc_typehints`` to inject type annotations into docs.
|
|
||||||
|
|
||||||
We also use two sources of truth for the dependencies here:
|
|
||||||
|
|
||||||
- ``docs/requirements.txt``
|
|
||||||
- ``pyproject.toml``
|
|
||||||
|
|
||||||
Why? Because we are using ReadTheDocs
|
|
||||||
for this template (only for original Github repo), and it
|
|
||||||
does only support traditional ``requirements.txt``.
|
|
||||||
|
|
||||||
|
|
||||||
Structure
|
|
||||||
---------
|
|
||||||
|
|
||||||
We use a clear structure for this documentation.
|
|
||||||
|
|
||||||
- ``pages/template`` contains docs
|
|
||||||
from `wemake-django-template <https://github.com/wemake-services/wemake-django-template>`_.
|
|
||||||
These files should not be modified locally.
|
|
||||||
If you have any kind of question or problems,
|
|
||||||
just open an issue `on github <https://github.com/wemake-services/wemake-django-template/issues>`_
|
|
||||||
- ``pages/project`` contains everything related to the project itself.
|
|
||||||
Usage examples, an auto-generated documentation from your source code,
|
|
||||||
configuration, business, and project goals
|
|
||||||
- ``documents`` contains different non-sphinx documents
|
|
||||||
like ``doc`` files, spreadsheets, and mockups
|
|
||||||
|
|
||||||
Please, do not mix it up.
|
|
||||||
|
|
||||||
How to structure project docs
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
It is a good practice to write a single ``rst`` document
|
|
||||||
for every single ``py`` file.
|
|
||||||
Obviously, ``rst`` structure fully copies the structure of your source code.
|
|
||||||
This way it is very easy to navigate through the docs,
|
|
||||||
since you already know the structure.
|
|
||||||
|
|
||||||
For each ``django`` application we tend to create
|
|
||||||
a file called ``index.rst`` which is considered
|
|
||||||
the main file for the application.
|
|
||||||
|
|
||||||
And ``pages/project/index.rst`` is the main file for the whole project.
|
|
||||||
|
|
||||||
|
|
||||||
How to contribute
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
We enforce everyone to write clean and explaining documentation.
|
|
||||||
However, there are several rules about writing styling.
|
|
||||||
|
|
||||||
We are using `doc8 <https://pypi.python.org/pypi/doc8>`_ to validate our docs.
|
|
||||||
So, here's the command to do it:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
doc8 ./docs
|
|
||||||
|
|
||||||
This is also used in our CI process, so your build will fail
|
|
||||||
if there are violations.
|
|
||||||
|
|
||||||
|
|
||||||
Useful plugins
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Some ``sphinx`` plugins are not included, since they are very specific.
|
|
||||||
However, they are very useful:
|
|
||||||
|
|
||||||
- `sphinxcontrib-mermaid <https://github.com/mgaitan/sphinxcontrib-mermaid>`_ - sphinx plugin to create general flowcharts, sequence and gantt diagrams
|
|
||||||
- `sphinxcontrib-plantuml <https://github.com/sphinx-contrib/plantuml/>`_ - sphinx plugin to create UML diagrams
|
|
||||||
- `nbsphinx <https://github.com/spatialaudio/nbsphinx>`_ - sphinx plugin to embed ``ipython`` notebooks into your docs
|
|
||||||
|
|
||||||
|
|
||||||
Further reading
|
|
||||||
---------------
|
|
||||||
|
|
||||||
- `sphinx <http://www.sphinx-doc.org/en/stable/>`_
|
|
||||||
- `sphinx with django <https://docs.djangoproject.com/en/2.2/internals/contributing/writing-documentation/#getting-started-with-sphinx>`_
|
|
||||||
- `sphinx-autodoc-typehints <https://github.com/agronholm/sphinx-autodoc-typehints>`_
|
|
||||||
- `Architecture Decision Record (ADR) <https://github.com/joelparkerhenderson/architecture_decision_record>`_
|
|
||||||
- `adr-tools <https://github.com/npryce/adr-tools>`_
|
|
@ -1,29 +0,0 @@
|
|||||||
Frequently asked questions
|
|
||||||
==========================
|
|
||||||
|
|
||||||
Will you ever support drf / celery / flask / gevent?
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
No. This template is focused on bringing best practices to ``django``
|
|
||||||
projects. It only includes workflow and configuration for this framework.
|
|
||||||
|
|
||||||
Other tools are not mandatory. And can easily be added by a developer.
|
|
||||||
|
|
||||||
Will you have an build-time option to include or change anything?
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
No, we believe that options bring inconsistencies to the project.
|
|
||||||
You can also make the wrong choice. So, we are protecting you from that.
|
|
||||||
|
|
||||||
You can only have options that are already present in this template.
|
|
||||||
Fork it, if you do not agree with this policy.
|
|
||||||
|
|
||||||
This code quality is unbearable! Can I turn it off?
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Of course, no one can stop you from that.
|
|
||||||
But what the point in using this template then?
|
|
||||||
|
|
||||||
Our code quality defined by this template is minimally acceptable.
|
|
||||||
We know tools to make it even better. But they are not included.
|
|
||||||
Since they are literally hardcore.
|
|
@ -1,56 +0,0 @@
|
|||||||
Gitlab CI
|
|
||||||
=========
|
|
||||||
|
|
||||||
We use ``Gitlab CI`` to build our containers, test it,
|
|
||||||
and store them in the internal registry.
|
|
||||||
|
|
||||||
These images are then pulled into the production servers.
|
|
||||||
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
-------------
|
|
||||||
|
|
||||||
All configuration is done inside ``.gitlab-ci.yml``.
|
|
||||||
|
|
||||||
|
|
||||||
Pipelines
|
|
||||||
---------
|
|
||||||
|
|
||||||
We have two pipelines configured: for ``master`` and other branches.
|
|
||||||
That's how it works: we only run testing for feature branches and do the whole
|
|
||||||
building/testing/deploying process for the ``master`` branch.
|
|
||||||
|
|
||||||
This allows us to speed up development process.
|
|
||||||
|
|
||||||
|
|
||||||
Automatic dependencies update
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
You can use `dependabot <https://github.com/dependabot/dependabot-script>`_
|
|
||||||
to enable automatic dependencies updates via Pull Requests to your repository.
|
|
||||||
Similar to the original template repository: `list of pull requests <https://github.com/wemake-services/wemake-django-template/pulls?q=is%3Apr+author%3Aapp%2Fdependabot>`_.
|
|
||||||
|
|
||||||
It is available to both Github and Gitlab.
|
|
||||||
But, for Gitlab version you currently have to update your `.gitlab-ci.yml <https://github.com/dependabot/dependabot-script/blob/master/.gitlab-ci.example.yml>`_.
|
|
||||||
|
|
||||||
|
|
||||||
Secret variables
|
|
||||||
----------------
|
|
||||||
|
|
||||||
If some real secret variables are required, then you can use `gitlab secrets <https://docs.gitlab.com/ee/ci/variables/#secret-variables>`_.
|
|
||||||
And these kind of variables are required *most* of the time.
|
|
||||||
|
|
||||||
See :ref:`django` on how to use ``dump-env`` and ``gitlab-ci`` together.
|
|
||||||
|
|
||||||
|
|
||||||
Documentation
|
|
||||||
-------------
|
|
||||||
After each deploy from master branch this documentation compiles into nice looking html page.
|
|
||||||
See `gitlab pages info <https://docs.gitlab.com/ee/user/project/pages/>`_.
|
|
||||||
|
|
||||||
|
|
||||||
Further reading
|
|
||||||
---------------
|
|
||||||
|
|
||||||
- `Container Registry <https://gitlab.com/help/user/project/container_registry>`_
|
|
||||||
- `Gitlab CI/CD <https://about.gitlab.com/features/gitlab-ci-cd/>`_
|
|
@ -1,130 +0,0 @@
|
|||||||
.. _linters:
|
|
||||||
|
|
||||||
Linters
|
|
||||||
=======
|
|
||||||
|
|
||||||
This project uses several linters to make coding style consistent.
|
|
||||||
All configuration is stored inside ``setup.cfg``.
|
|
||||||
|
|
||||||
|
|
||||||
wemake-python-styleguide
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
``wemake-python-styleguide`` is a ``flake8`` based plugin.
|
|
||||||
And it is also the strictest and most opinionated python linter ever.
|
|
||||||
See `wemake-python-styleguide <https://wemake-python-styleguide.readthedocs.io/en/latest/>`_
|
|
||||||
docs.
|
|
||||||
|
|
||||||
Things that are included in the linting process:
|
|
||||||
|
|
||||||
- `flake8 <http://flake8.pycqa.org/>`_ is used a general tool for linting
|
|
||||||
- `isort <https://github.com/timothycrosley/isort>`_ is used to validate ``import`` order
|
|
||||||
- `bandit <https://github.com/PyCQA/bandit>`_ for static security checks
|
|
||||||
- `eradicate <https://github.com/myint/eradicate>`_ to find dead code
|
|
||||||
- and more!
|
|
||||||
|
|
||||||
Running linting process for all ``python`` files in the project:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
flake8 .
|
|
||||||
|
|
||||||
Extra plugins
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
We also use some extra plugins for ``flake8``
|
|
||||||
that are not bundled with ``wemake-python-styleguide``:
|
|
||||||
|
|
||||||
- `flake8-pytest <https://github.com/vikingco/flake8-pytest>`_ - ensures that ``pytest`` best practices are used
|
|
||||||
- `flake8-pytest-style <https://github.com/m-burst/flake8-pytest-style>`_ - ensures that ``pytest`` tests and fixtures are written in a single style
|
|
||||||
- `flake8-django <https://github.com/rocioar/flake8-django>`_ - plugin to enforce best practices in a ``django`` project
|
|
||||||
|
|
||||||
|
|
||||||
django-migration-linter
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
We use ``django-migration-linter`` to find backward incompatible migrations.
|
|
||||||
It allows us to write 0-downtime friendly code.
|
|
||||||
|
|
||||||
See `django-migration-linter <https://github.com/3YOURMIND/django-migration-linter>`_
|
|
||||||
docs, it contains a lot of useful information about ways and tools to do it.
|
|
||||||
|
|
||||||
That's how this check is executed:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
python manage.py lintmigrations --exclude-apps=axes
|
|
||||||
|
|
||||||
Important note: you might want to exclude some packages with broken migrations.
|
|
||||||
Sometimes, there's nothing we can do about it.
|
|
||||||
|
|
||||||
|
|
||||||
yamllint
|
|
||||||
--------
|
|
||||||
|
|
||||||
Is used to lint your ``yaml`` files.
|
|
||||||
See `yamllint <https://github.com/adrienverge/yamllint>`_ docs.
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
yamllint -d '{"extends": "default", "ignore": ".venv"}' -s .
|
|
||||||
|
|
||||||
|
|
||||||
dotenv-linter
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Is used to lint your ``.env`` files.
|
|
||||||
See `dotenv-linter <https://github.com/wemake-services/dotenv-linter>`_ docs.
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
dotenv-linter config/.env config/.env.template
|
|
||||||
|
|
||||||
|
|
||||||
polint and dennis
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
Are used to lint your ``.po`` files.
|
|
||||||
See `polint <https://github.com/ziima/polint>`_ docs.
|
|
||||||
Also see `dennis <https://dennis.readthedocs.io/en/latest/linting.html>`_ docs.
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
polint -i location,unsorted locale
|
|
||||||
dennis-cmd lint --errorsonly locale
|
|
||||||
|
|
||||||
|
|
||||||
Packaging
|
|
||||||
---------
|
|
||||||
|
|
||||||
We also use ``pip`` and ``poetry`` self checks to be sure
|
|
||||||
that packaging works correctly.
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
poetry check && pip check
|
|
||||||
|
|
||||||
|
|
||||||
Linters that are not included
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
Sometimes we use several other linters that are not included.
|
|
||||||
That's because they require another technology stack to be installed
|
|
||||||
or just out of scope.
|
|
||||||
|
|
||||||
We also recommend to check the list of linters
|
|
||||||
`recommended by wemake-python-styleguide <https://wemake-python-stylegui.de/en/latest/pages/usage/integrations/extras.html>`_.
|
|
||||||
|
|
||||||
Here's the list of these linters. You may still find them useful.
|
|
||||||
|
|
||||||
shellcheck
|
|
||||||
~~~~~~~~~~
|
|
||||||
|
|
||||||
This linter is used to lint your ``.sh`` files.
|
|
||||||
See `shellcheck <https://www.shellcheck.net/>`_ docs.
|
|
||||||
|
|
||||||
hadolint
|
|
||||||
~~~~~~~~
|
|
||||||
|
|
||||||
This linter is used to lint your ``Dockerfile`` syntax.
|
|
||||||
See `hadolint <https://github.com/hadolint/hadolint>`_
|
|
@ -1,135 +0,0 @@
|
|||||||
Overview
|
|
||||||
========
|
|
||||||
|
|
||||||
|
|
||||||
System requirements
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
- ``git`` with a version at least ``2.16`` or higher
|
|
||||||
- ``docker`` with a version at least ``18.02`` or higher
|
|
||||||
- ``docker-compose`` with a version at least ``1.21`` or higher
|
|
||||||
- ``python`` with exact version, see ``pyproject.toml``
|
|
||||||
|
|
||||||
|
|
||||||
Architecture
|
|
||||||
------------
|
|
||||||
|
|
||||||
config
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
- ``config/.env.template`` - a basic example of what keys must be contained in
|
|
||||||
your ``.env`` file, this file is committed to VCS
|
|
||||||
and must not contain private or secret values
|
|
||||||
- ``config/.env`` - main file for secret configuration,
|
|
||||||
contains private and secret values, should not be committed to VCS
|
|
||||||
|
|
||||||
root project
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
- ``README.md`` - main readme file, it specifies the entry
|
|
||||||
point to the project's documentation
|
|
||||||
- ``.dockerignore`` - specifies what files should not be
|
|
||||||
copied to the ``docker`` image
|
|
||||||
- ``.editorconfig`` - file with format specification.
|
|
||||||
You need to install the required plugin for your IDE in order to enable it
|
|
||||||
- ``.gitignore`` - file that specifies
|
|
||||||
what should we commit into the repository and we should not
|
|
||||||
- ``.gitlab-ci.yml`` - GitLab CI configuration file.
|
|
||||||
It basically defines what to do with your project
|
|
||||||
after pushing it to the repository. Currently it is used for testing
|
|
||||||
and releasing a ``docker`` image
|
|
||||||
- ``docker-compose.yml`` - this the file specifies ``docker`` services
|
|
||||||
that are needed for development and testing
|
|
||||||
- ``docker-compose.override.yml`` - local override for ``docker-compose``.
|
|
||||||
Is applied automatically and implicitly when
|
|
||||||
no arguments provided to ``docker-compose`` command
|
|
||||||
- ``manage.py`` - main file for your ``django`` project.
|
|
||||||
Used as an entry point for the ``django`` project
|
|
||||||
- ``pyproject.toml`` - main file of the project.
|
|
||||||
It defines the project's dependencies.
|
|
||||||
- ``poetry.lock`` - lock file for dependencies.
|
|
||||||
It is used to install exactly the same versions of dependencies on each build
|
|
||||||
- ``setup.cfg`` - configuration file, that is used by all tools in this project
|
|
||||||
- ``locale/`` - helper folder, that is used to store locale data,
|
|
||||||
empty by default
|
|
||||||
- ``sql/`` - helper folder, that contains ``sql`` script for database setup
|
|
||||||
and teardown for local development
|
|
||||||
|
|
||||||
server
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
- ``server/__init__.py`` - package definition, empty file
|
|
||||||
- ``server/urls.py`` - ``django`` `urls definition <https://docs.djangoproject.com/en/2.2/topics/http/urls/>`_
|
|
||||||
- ``server/wsgi.py`` - ``django`` `wsgi definition <https://en.wikipedia.org/wiki/Web_Server_Gateway_Interface>`_
|
|
||||||
- ``server/apps/`` - place to put all your apps into
|
|
||||||
- ``server/apps/main`` - ``django`` application, used as an example,
|
|
||||||
could be removed
|
|
||||||
- ``server/settings`` - settings defined with ``django-split-settings``,
|
|
||||||
see this `tutorial <https://medium.com/wemake-services/managing-djangos-settings-e2b7f496120d>`_
|
|
||||||
for more information
|
|
||||||
- ``server/templates`` - external folder for ``django`` templates,
|
|
||||||
used for simple files as ``robots.txt`` and so on
|
|
||||||
|
|
||||||
docker
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
- ``docker/ci.sh`` - file that specifies all possible checks that
|
|
||||||
we execute during our CI process
|
|
||||||
- ``docker/docker-compose.prod.yml`` - additional service definition file
|
|
||||||
used for production
|
|
||||||
- ``docker/django/Dockerfile`` - ``django`` container definition,
|
|
||||||
used both for development and production
|
|
||||||
- ``docker/django/entrypoint.sh`` - entry point script that is used
|
|
||||||
when ``django`` container is starting
|
|
||||||
- ``docker/django/gunicorn.sh`` - production script for ``django``,
|
|
||||||
that's how we configure ``gunicorn`` runner
|
|
||||||
- ``docker/caddy/Caddyfile`` - configuration file for Caddy webserver
|
|
||||||
|
|
||||||
tests
|
|
||||||
~~~~~
|
|
||||||
|
|
||||||
- ``tests/test_server`` - tests that ensures that basic ``django``
|
|
||||||
stuff is working, should not be removed
|
|
||||||
- ``tests/test_apps/test_main`` - example tests for the ``django`` app,
|
|
||||||
could be removed
|
|
||||||
- ``tests/conftest.py`` - main configuration file for ``pytest`` runner
|
|
||||||
|
|
||||||
docs
|
|
||||||
~~~~
|
|
||||||
|
|
||||||
- ``docs/Makefile`` - command file that builds the documentation for Unix
|
|
||||||
- ``docs/make.bat`` - command file for Windows
|
|
||||||
- ``docs/conf.py`` - ``sphinx`` configuration file
|
|
||||||
- ``docs/index.rst`` - main documentation file, used as an entry point
|
|
||||||
- ``docs/pages/project`` - folder that will contain
|
|
||||||
documentation written by you!
|
|
||||||
- ``docs/pages/template`` - folder that contains documentation that
|
|
||||||
is common for each project built with this template
|
|
||||||
- ``docs/documents`` - folder that should contain any documents you have:
|
|
||||||
spreadsheets, images, requirements, presentations, etc
|
|
||||||
- ``docs/requirements.txt`` - helper file, contains dependencies
|
|
||||||
for ``readthedocs`` service. Can be removed
|
|
||||||
- ``docs/README.rst`` - helper file for this directory,
|
|
||||||
just tells what to do next
|
|
||||||
|
|
||||||
|
|
||||||
Container internals
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
We use the ``docker-compose`` to link different containers together.
|
|
||||||
We also utilize different ``docker`` networks to control access.
|
|
||||||
|
|
||||||
Some containers might have long starting times, for example:
|
|
||||||
|
|
||||||
- ``postgres``
|
|
||||||
- ``rabbitmq``
|
|
||||||
- frontend, like ``node.js``
|
|
||||||
|
|
||||||
To be sure that container is started at the right time,
|
|
||||||
we utilize ``dockerize`` `script <https://github.com/jwilder/dockerize>`_.
|
|
||||||
It is executed inside ``docker/django/entrypoint.sh`` file.
|
|
||||||
|
|
||||||
We start containers with ``tini``.
|
|
||||||
Because this way we have a proper signal handling
|
|
||||||
and eliminate zombie processes.
|
|
||||||
Read the `official docs <https://github.com/krallin/tini>`_ to know more.
|
|
@ -1,186 +0,0 @@
|
|||||||
.. _`going-to-production`:
|
|
||||||
|
|
||||||
Going to production
|
|
||||||
===================
|
|
||||||
|
|
||||||
This section covers everything you need to know before going to production.
|
|
||||||
|
|
||||||
|
|
||||||
Django
|
|
||||||
------
|
|
||||||
|
|
||||||
Checks
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
Before going to production make sure you have checked everything:
|
|
||||||
|
|
||||||
1. Migrations are up-to-date
|
|
||||||
2. Static files are all present
|
|
||||||
3. There are no security or other ``django`` warnings
|
|
||||||
|
|
||||||
Checking migrations, static files,
|
|
||||||
and security is done inside ``ci.sh`` script.
|
|
||||||
|
|
||||||
We check that there are no unapplied migrations:
|
|
||||||
|
|
||||||
.. code :: bash
|
|
||||||
|
|
||||||
python manage.py makemigrations --dry-run --check
|
|
||||||
|
|
||||||
If you have forgotten to create a migration and changed the model,
|
|
||||||
you will see an error on this line.
|
|
||||||
|
|
||||||
We also check that static files can be collected:
|
|
||||||
|
|
||||||
.. code :: bash
|
|
||||||
|
|
||||||
DJANGO_ENV=production python manage.py collectstatic --no-input --dry-run
|
|
||||||
|
|
||||||
However, this check does not cover all the cases.
|
|
||||||
Sometimes ``ManifestStaticFilesStorage`` will fail on real cases,
|
|
||||||
but will pass with ``--dry-run`` option.
|
|
||||||
You can disable ``--dry-run`` option if you know what you are doing.
|
|
||||||
Be careful with this option, when working with auto-uploading
|
|
||||||
your static files to any kind of CDNs.
|
|
||||||
|
|
||||||
That's how we check ``django`` warnings:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
DJANGO_ENV=production python manage.py check --deploy --fail-level WARNING
|
|
||||||
|
|
||||||
These warnings are raised by ``django``
|
|
||||||
when it detects any configuration issues.
|
|
||||||
|
|
||||||
This command should give not warnings or errors.
|
|
||||||
It is bundled into `docker`, so the container will not work with any warnings.
|
|
||||||
|
|
||||||
Static and media files
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
We use ``/var/www/django`` folder to store our media
|
|
||||||
and static files in production as ``/var/www/django/static``
|
|
||||||
and ``/var/www/django/media``.
|
|
||||||
Docker uses these two folders as named volumes.
|
|
||||||
And later these volumes are also mounted to ``caddy``
|
|
||||||
with ``ro`` mode so it possible to read their contents.
|
|
||||||
|
|
||||||
To find the exact location of these files on your host
|
|
||||||
you will need to do the following:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
docker volume ls # to find volumes' names
|
|
||||||
docker volume inspect VOLUME_NAME
|
|
||||||
|
|
||||||
Sometimes storing your media files inside a container is not a good idea.
|
|
||||||
Use ``CDN`` when you have a lot of user content
|
|
||||||
or it is very important not to lose it.
|
|
||||||
There are `helper libraries <http://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html>`_
|
|
||||||
to bind ``django`` and these services.
|
|
||||||
|
|
||||||
If you don't need ``media`` files support, just remove the volumes.
|
|
||||||
|
|
||||||
Migrations
|
|
||||||
~~~~~~~~~~
|
|
||||||
|
|
||||||
We do run migration in the ``gunicorn.sh`` by default.
|
|
||||||
Why do we do this? Because that's probably the easiest way to do it.
|
|
||||||
But it clearly has some disadvantages:
|
|
||||||
|
|
||||||
- When scaling your container for multiple nodes you will have multiple
|
|
||||||
threads running the same migrations. And it might be a problem since
|
|
||||||
migrations do not guarantee that it will work this way.
|
|
||||||
- You can perform some operations multiple times
|
|
||||||
- Possible other evil things may happen
|
|
||||||
|
|
||||||
So, what to do in this case?
|
|
||||||
Well, you can do whatever it takes to run migrations in a single thread.
|
|
||||||
For example, you can create a separate container to do just that.
|
|
||||||
Other options are fine as well.
|
|
||||||
|
|
||||||
|
|
||||||
Postgres
|
|
||||||
--------
|
|
||||||
|
|
||||||
Sometimes using ``postgres`` inside a container
|
|
||||||
`is not a good idea <https://myopsblog.wordpress.com/2017/02/06/why-databases-is-not-for-containers/>`_.
|
|
||||||
So, what should be done in this case?
|
|
||||||
|
|
||||||
First of all, move your database ``docker`` service definition
|
|
||||||
inside ``docker-compose.override.yml``.
|
|
||||||
Doing so will not affect development,
|
|
||||||
but will remove database service from production.
|
|
||||||
Next, you will need to specify `extra_hosts <https://docs.docker.com/compose/compose-file/#extra_hosts>`_
|
|
||||||
to contain your ``postgresql`` address.
|
|
||||||
Lastly, you would need to add new hosts to ``pg_hba.conf``.
|
|
||||||
|
|
||||||
`Here <http://winstonkotzan.com/blog/2017/06/01/connecting-to-external-postgres-database-with-docker.html>`_
|
|
||||||
is a nice tutorial about this topic.
|
|
||||||
|
|
||||||
|
|
||||||
Caddy
|
|
||||||
-----
|
|
||||||
|
|
||||||
Let's Encrypt
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
We are using ``Caddy`` and ``Let's Encrypt`` for HTTPS.
|
|
||||||
The Caddy webserver used in the default configuration will get
|
|
||||||
you a valid certificate from ``Let's Encrypt`` and update it automatically.
|
|
||||||
All you need to do to enable this is to make sure
|
|
||||||
that your DNS records are pointing to the server Caddy runs on.
|
|
||||||
|
|
||||||
Read more: `Automatic HTTPS <https://caddyserver.com/docs/automatic-https>`_
|
|
||||||
in Caddy docs.
|
|
||||||
|
|
||||||
Caddyfile validation
|
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
You can also run ``-validate`` command to validate ``Caddyfile`` contents.
|
|
||||||
|
|
||||||
Here's it would look like:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
docker-compose -f docker-compose.yml -f docker/docker-compose.prod.yml
|
|
||||||
run --rm caddy -validate
|
|
||||||
|
|
||||||
This check is not included in the pipeline by default,
|
|
||||||
because it is quite long to start all the machinery for this single check.
|
|
||||||
|
|
||||||
Disabling HTTPS
|
|
||||||
~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
You would need to `disable <https://caddyserver.com/docs/tls>`_
|
|
||||||
``https`` inside ``Caddy`` and in production settings for Django.
|
|
||||||
Because Django itself also redirects to `https`.
|
|
||||||
See `docs <https://docs.djangoproject.com/en/2.2/ref/settings/#secure-ssl-redirect>`_.
|
|
||||||
|
|
||||||
You would also need to disable ``manage.py check``
|
|
||||||
in ``docker/ci.sh``.
|
|
||||||
Otherwise, your application won't start,
|
|
||||||
it would not pass ``django``'s security checks.
|
|
||||||
|
|
||||||
Disabling WWW subdomain
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
If you for some reason do not require ``www.`` subdomain,
|
|
||||||
then delete ``www.{$DOMAIN_NAME}`` section from ``Caddyfile``.
|
|
||||||
|
|
||||||
Third-Level domains
|
|
||||||
~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
You have to disable ``www`` subdomain if
|
|
||||||
your app works on third-level domains like:
|
|
||||||
|
|
||||||
- ``kira.wemake.services``
|
|
||||||
- ``support.myapp.com``
|
|
||||||
|
|
||||||
Otherwise, ``Caddy`` will server redirects to ``www.example.yourdomain.com``.
|
|
||||||
|
|
||||||
|
|
||||||
Further reading
|
|
||||||
---------------
|
|
||||||
|
|
||||||
- Django's deployment `checklist <https://docs.djangoproject.com/en/dev/howto/deployment/checklist/#deployment-checklist>`_
|
|
@ -1,77 +0,0 @@
|
|||||||
Production
|
|
||||||
==========
|
|
||||||
|
|
||||||
We use different tools and setup for production.
|
|
||||||
We do not fully provide this part with the template. Why?
|
|
||||||
|
|
||||||
1. It requires a lot of server configuration
|
|
||||||
2. It heavily depends on your needs: performance, price, technology, etc
|
|
||||||
3. It is possible to show some vulnerable parts to possible attackers
|
|
||||||
|
|
||||||
So, you will need to deploy your application by yourself.
|
|
||||||
Here, we would like to cover some basic things that are not changed
|
|
||||||
from deployment strategy.
|
|
||||||
|
|
||||||
The easiest deployment strategy for small apps is ``docker-compose`` and
|
|
||||||
``systemd`` inside a host operating system.
|
|
||||||
|
|
||||||
|
|
||||||
Production configuration
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
You will need to specify extra configuration
|
|
||||||
to run ``docker-compose`` in production.
|
|
||||||
Since production build also uses ``caddy``,
|
|
||||||
which is not required into the development build.
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
docker-compose -f docker-compose.yml -f docker/docker-compose.prod.yml up
|
|
||||||
|
|
||||||
|
|
||||||
Pulling pre-built images
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
You will need to pull pre-built images from ``Gitlab`` to run them.
|
|
||||||
How to do that?
|
|
||||||
|
|
||||||
The first step is to create a personal access token for this service.
|
|
||||||
Then, login into your registry with:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
docker login registry.gitlab.your.domain
|
|
||||||
|
|
||||||
And now you are ready to pull your images:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
docker pull your-image:latest
|
|
||||||
|
|
||||||
See `official Gitlab docs <https://docs.gitlab.com/ee/user/project/container_registry.html>`_.
|
|
||||||
|
|
||||||
|
|
||||||
Updating already running service
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
If you need to update an already running service,
|
|
||||||
them you will have to use ``docker service update``
|
|
||||||
or ``docker stack deploy``.
|
|
||||||
|
|
||||||
Updating existing `service <https://docs.docker.com/engine/reference/commandline/service_update/>`_.
|
|
||||||
Updating existing `stack <https://docs.docker.com/engine/reference/commandline/stack_deploy/>`_.
|
|
||||||
|
|
||||||
Zero-Time Updates
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Zero-Time Updates can be tricky.
|
|
||||||
You need to create containers with the new code, update existing services,
|
|
||||||
wait for the working sessions to be completed, and to shut down old
|
|
||||||
containers.
|
|
||||||
|
|
||||||
|
|
||||||
Further reading
|
|
||||||
---------------
|
|
||||||
|
|
||||||
- Production with `docker-compose <https://docs.docker.com/compose/production>`_
|
|
||||||
- `Full tutorial <https://docs.docker.com/get-started>`_
|
|
@ -1,114 +0,0 @@
|
|||||||
Security
|
|
||||||
========
|
|
||||||
|
|
||||||
Security is our first priority.
|
|
||||||
We try to make projects as secure as possible.
|
|
||||||
We use a lot of 3rd party tools to achieve that.
|
|
||||||
|
|
||||||
|
|
||||||
Django
|
|
||||||
------
|
|
||||||
|
|
||||||
Django has a lot of `security-specific settings <https://docs.djangoproject.com/en/2.2/topics/security/>`_
|
|
||||||
that are all turned on by default in this template.
|
|
||||||
|
|
||||||
We also :ref:`enforce <going-to-production>` all the best practices
|
|
||||||
by running ``django`` checks inside CI for each commit.
|
|
||||||
|
|
||||||
We also use a set of custom ``django`` apps
|
|
||||||
to enforce even more security rules:
|
|
||||||
|
|
||||||
- `django-axes <https://github.com/jazzband/django-axes>`_ to track and ban repeating access requests
|
|
||||||
- `django-csp <https://github.com/mozilla/django-csp>`_ to enforce `Content-Security Policy <https://www.w3.org/TR/CSP/>`_ for our webpages
|
|
||||||
- `django-http-referrer-policy <https://django-referrer-policy.readthedocs.io>`_ to enforce `Referrer Policy <https://www.w3.org/TR/referrer-policy/>`_ for our webpages
|
|
||||||
|
|
||||||
And there are also some awesome extensions that are not included:
|
|
||||||
|
|
||||||
- `django-honeypot <https://github.com/jamesturk/django-honeypot>`_ - django application that provides utilities for preventing automated form spam
|
|
||||||
|
|
||||||
Passwords
|
|
||||||
~~~~~~~~~
|
|
||||||
|
|
||||||
We use strong algorithms for password hashing:
|
|
||||||
``bcrypt``, ``PBKDF2`` and ``Argon2`` which are known to be secure enough.
|
|
||||||
|
|
||||||
|
|
||||||
Dependencies
|
|
||||||
------------
|
|
||||||
|
|
||||||
We use `poetry <https://poetry.eustace.io/>`_ which ensures
|
|
||||||
that all the dependencies hashes match during the installation process.
|
|
||||||
Otherwise, the build will fail.
|
|
||||||
So, it is almost impossible to replace an already existing package
|
|
||||||
with a malicious one.
|
|
||||||
|
|
||||||
We also use `safety <https://github.com/pyupio/safety>`_
|
|
||||||
to analyze vulnerable dependencies to prevent the build
|
|
||||||
to go to the production with known unsafe dependencies.
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
safety check
|
|
||||||
|
|
||||||
We also use `Github security alerts <https://help.github.com/articles/about-security-alerts-for-vulnerable-dependencies/>`_
|
|
||||||
for our main template repository.
|
|
||||||
|
|
||||||
|
|
||||||
Static analysis
|
|
||||||
---------------
|
|
||||||
|
|
||||||
We use ``wemake-python-styleguide`` which
|
|
||||||
includes `bandit <https://pypi.org/project/bandit/>`_ security checks inside.
|
|
||||||
|
|
||||||
You can also install `pyt <https://pyt.readthedocs.io>`_
|
|
||||||
which is not included by default.
|
|
||||||
It will include even more static checks for
|
|
||||||
``sql`` injections, ``xss`` and others.
|
|
||||||
|
|
||||||
|
|
||||||
Dynamic analysis
|
|
||||||
----------------
|
|
||||||
|
|
||||||
You can monitor your running application to detect anomalous activities.
|
|
||||||
Tools to consider:
|
|
||||||
|
|
||||||
- `dagda <https://github.com/eliasgranderubio/dagda>`_ - a tool to perform static analysis of known vulnerabilities, trojans, viruses, malware & other malicious threats in docker images/containers and to monitor the docker daemon and running docker containers for detecting anomalous activities
|
|
||||||
|
|
||||||
All the tools above are not included into this template.
|
|
||||||
You have to install them by yourself.
|
|
||||||
|
|
||||||
|
|
||||||
Secrets
|
|
||||||
-------
|
|
||||||
|
|
||||||
We store secrets separately from code. So, it is harder for them to leak.
|
|
||||||
However, we encourage to use tools like
|
|
||||||
`truffleHog <https://github.com/dxa4481/truffleHog>`_ or `detect-secrets <https://github.com/Yelp/detect-secrets>`_ inside your workflow.
|
|
||||||
|
|
||||||
You can also turn on `Gitlab secrets checker <https://docs.gitlab.com/ee/push_rules/push_rules.html#prevent-pushing-secrets-to-the-repository>`_ which we highly recommend.
|
|
||||||
|
|
||||||
|
|
||||||
Audits
|
|
||||||
------
|
|
||||||
|
|
||||||
The only way to be sure that your app is secure
|
|
||||||
is to constantly audit it in production.
|
|
||||||
|
|
||||||
There are different tools to help you:
|
|
||||||
|
|
||||||
- `twa <https://github.com/trailofbits/twa>`_ - tiny web auditor that has a lot of security checks for the webpages
|
|
||||||
- `XSStrike <https://github.com/s0md3v/XSStrike>`_ - automated tool to check that your application is not vulnerable to ``xss`` errors
|
|
||||||
- `docker-bench <https://github.com/docker/docker-bench-security>`_ - a script that checks for dozens of common best-practices around deploying Docker containers in production
|
|
||||||
- `lynis <https://cisofy.com/lynis/>`_ - a battle-tested security tool for systems running Linux, macOS, or Unix-based operating system
|
|
||||||
- `trivy <https://github.com/knqyf263/trivy>`_ - a simple and comprehensive vulnerability scanner for containers
|
|
||||||
|
|
||||||
But, even after all you attempts to secure your application,
|
|
||||||
it **won't be 100% safe**. Do not fall into this false feeling of security.
|
|
||||||
|
|
||||||
|
|
||||||
Further reading
|
|
||||||
---------------
|
|
||||||
|
|
||||||
- `Open Web Application Security Project <https://www.owasp.org/images/3/33/OWASP_Application_Security_Verification_Standard_3.0.1.pdf>`_
|
|
||||||
- `Docker security <https://docs.docker.com/engine/security/security/>`_
|
|
||||||
- `AppArmor <https://docs.docker.com/engine/security/apparmor/>`_ and `bane <https://github.com/genuinetools/bane>`_
|
|
@ -1,111 +0,0 @@
|
|||||||
Testing
|
|
||||||
=======
|
|
||||||
|
|
||||||
We try to keep our quality standards high.
|
|
||||||
So, we use different tools to make this possible.
|
|
||||||
|
|
||||||
We use `mypy <http://mypy-lang.org/>`_ for optional
|
|
||||||
static typing.
|
|
||||||
We run tests with `pytest <https://pytest.org/>`_ framework.
|
|
||||||
|
|
||||||
|
|
||||||
pytest
|
|
||||||
------
|
|
||||||
|
|
||||||
``pytest`` is the main tool for test discovery, collection, and execution.
|
|
||||||
It is configured inside ``setup.cfg`` file.
|
|
||||||
|
|
||||||
We use a lot of ``pytest`` plugins that enhance our development experience.
|
|
||||||
List of these plugins is available inside ``pyproject.toml`` file.
|
|
||||||
|
|
||||||
Running:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
pytest
|
|
||||||
|
|
||||||
We also have some options that are set on each run via ``--addopts``
|
|
||||||
inside the ``setup.cfg`` file.
|
|
||||||
|
|
||||||
Plugins
|
|
||||||
~~~~~~~
|
|
||||||
|
|
||||||
We use different ``pytest`` plugins to make our testing process better.
|
|
||||||
Here's the full list of things we use:
|
|
||||||
|
|
||||||
- `pytest-django`_ - plugin that introduce a lot of ``django`` specific
|
|
||||||
helpers, fixtures, and configuration
|
|
||||||
- `django-test-migrations`_ - plugin to test Django migrations and their order
|
|
||||||
- `pytest-cov`_ - plugin to measure test coverage
|
|
||||||
- `pytest-randomly`_ - plugin to execute tests in random order and
|
|
||||||
also set predictable random seed, so you can easily debug
|
|
||||||
what went wrong for tests that rely on random behavior
|
|
||||||
- `pytest-deadfixtures`_ - plugin to find unused or duplicate fixtures
|
|
||||||
- `pytest-timeout`_ - plugin to raise errors for tests
|
|
||||||
that take too long to finish, this way you can control test execution speed
|
|
||||||
- `pytest-testmon`_ - plugin for `Test Driven Development`_ which executes
|
|
||||||
tests that are affected by your code changes
|
|
||||||
|
|
||||||
.. _pytest-django: https://github.com/pytest-dev/pytest-django
|
|
||||||
.. _django-test-migrations: https://github.com/wemake-services/django-test-migrations
|
|
||||||
.. _pytest-cov: https://github.com/pytest-dev/pytest-cov
|
|
||||||
.. _pytest-randomly: https://github.com/pytest-dev/pytest-randomly
|
|
||||||
.. _pytest-deadfixtures: https://github.com/jllorencetti/pytest-deadfixtures
|
|
||||||
.. _pytest-timeout: https://pypi.org/project/pytest-timeout
|
|
||||||
.. _pytest-testmon: https://github.com/tarpas/pytest-testmon
|
|
||||||
.. _`Test Driven Development`: https://en.wikipedia.org/wiki/Test-driven_development
|
|
||||||
|
|
||||||
Tweaking tests performance
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
There are several options you can provide or remove to make your tests faster:
|
|
||||||
|
|
||||||
- You can use ``pytest-xdist`` together with
|
|
||||||
``-n auto`` to schedule several numbers of workers,
|
|
||||||
sometimes when there are a lot of tests it may increase the testing speed.
|
|
||||||
But on a small project with a small amount of test it just
|
|
||||||
gives you an overhead, so removing it (together with `--boxed`)
|
|
||||||
will boost your testing performance
|
|
||||||
- If there are a lot of tests with database access
|
|
||||||
it may be wise to add
|
|
||||||
`--reuse-db option <https://pytest-django.readthedocs.io/en/latest/database.html#example-work-flow-with-reuse-db-and-create-db>`_,
|
|
||||||
so ``django`` won't recreate database on each test
|
|
||||||
- If there are a lot of migrations to perform you may also add
|
|
||||||
`--nomigrations option <https://pytest-django.readthedocs.io/en/latest/database.html#nomigrations-disable-django-1-7-migrations>`_,
|
|
||||||
so ``django`` won't run all the migrations
|
|
||||||
and instead will inspect and create models directly
|
|
||||||
- Removing ``coverage``. Sometimes that an option.
|
|
||||||
When running tests in TDD style why would you need such a feature?
|
|
||||||
So, coverage will be calculated when you will ask for it.
|
|
||||||
That's a huge speed up
|
|
||||||
- Removing linters. Sometimes you may want to split linting and testing phases.
|
|
||||||
This might be useful when you have a lot of tests, and you want to run
|
|
||||||
linters before, so it won't fail your complex testing pyramid with a simple
|
|
||||||
whitespace violation
|
|
||||||
|
|
||||||
|
|
||||||
mypy
|
|
||||||
----
|
|
||||||
|
|
||||||
Running ``mypy`` is required before any commit:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
mypy server tests/**/*.py
|
|
||||||
|
|
||||||
This will eliminate a lot of possible ``TypeError`` and other issues
|
|
||||||
in both `server/` and `tests/` directories.
|
|
||||||
We use `tests/**/*.py` because `tests/` is not a python package,
|
|
||||||
so it is not importable.
|
|
||||||
|
|
||||||
However, this will not make code 100% safe from errors.
|
|
||||||
So, both the testing and review process are still required.
|
|
||||||
|
|
||||||
``mypy`` is configured via ``setup.cfg``.
|
|
||||||
Read the `docs <https://mypy.readthedocs.io/en/latest/>`_
|
|
||||||
for more information.
|
|
||||||
|
|
||||||
We also use `django-stubs <https://github.com/typeddjango/django-stubs>`_
|
|
||||||
to type ``django`` internals.
|
|
||||||
This package is optional and can be removed,
|
|
||||||
if you don't want to type your ``django`` for some reason.
|
|
@ -1,47 +0,0 @@
|
|||||||
Troubleshooting
|
|
||||||
===============
|
|
||||||
|
|
||||||
This section is about some of the problems you may encounter and
|
|
||||||
how to solve these problems.
|
|
||||||
|
|
||||||
|
|
||||||
Docker
|
|
||||||
------
|
|
||||||
|
|
||||||
Pillow
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
If you want to install ``Pillow`` that you should
|
|
||||||
add this to dockerfile and rebuild image:
|
|
||||||
|
|
||||||
- ``RUN apk add jpeg-dev zlib-dev``
|
|
||||||
- ``LIBRARY_PATH=/lib:/usr/lib /bin/sh -c "poetry install ..."``
|
|
||||||
|
|
||||||
See `<https://github.com/python-pillow/Pillow/issues/1763>`_
|
|
||||||
|
|
||||||
Root owns build artifacts
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This happens on some systems.
|
|
||||||
It happens because build happens in ``docker`` as the ``root`` user.
|
|
||||||
The fix is to pass current ``UID`` to ``docker``.
|
|
||||||
See `<https://github.com/wemake-services/wemake-django-template/issues/345>`_.
|
|
||||||
|
|
||||||
MacOS performance
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
If you use the MacOS you
|
|
||||||
know that you have problems with disk performance.
|
|
||||||
Starting and restarting an application is slower than with Linux
|
|
||||||
(it's very noticeable for project with large codebase).
|
|
||||||
For particular solve this problem add ``:delegated`` to each
|
|
||||||
your volumes in ``docker-compose.yml`` file.
|
|
||||||
|
|
||||||
.. code:: yaml
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
- pgdata:/var/lib/postgresql/data:delegated
|
|
||||||
|
|
||||||
For more information, you can look at the
|
|
||||||
`docker documents <https://docs.docker.com/docker-for-mac/osxfs-caching/>`_
|
|
||||||
and a good `article <https://medium.com/@TomKeur/how-get-better-disk-performance-in-docker-for-mac-2ba1244b5b70>`_.
|
|
@ -1,51 +0,0 @@
|
|||||||
Upgrading template
|
|
||||||
==================
|
|
||||||
|
|
||||||
Upgrading your project to be up-to-date with this template is a primary goal.
|
|
||||||
This is achieved by manually applying ``diff`` to your existing code.
|
|
||||||
|
|
||||||
``diff`` can be viewed from the project's ``README.md``.
|
|
||||||
See `an example <https://github.com/wemake-services/wemake-django-template/compare/91188fc4b89bd4989a0ead3d156a4619644965b0...master>`_.
|
|
||||||
|
|
||||||
When the upgrade is applied just change the commit hash in your template
|
|
||||||
to the most recent one.
|
|
||||||
|
|
||||||
|
|
||||||
Versions
|
|
||||||
--------
|
|
||||||
|
|
||||||
Sometimes, when we break something heavily, we create a version.
|
|
||||||
That's is required for our users, so they can use old releases to create
|
|
||||||
projects as they used to be a long time ago.
|
|
||||||
|
|
||||||
However, we do not officially support older versions.
|
|
||||||
And we do not recommend to use them.
|
|
||||||
|
|
||||||
A full list of versions can be `found here <https://github.com/wemake-services/wemake-django-template/releases>`_.
|
|
||||||
|
|
||||||
|
|
||||||
Migration guides
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Each time we create a new version, we also provide a migration guide.
|
|
||||||
What is a migration guide?
|
|
||||||
It is something you have to do to your project
|
|
||||||
other than just copy-pasting diffs from new versions.
|
|
||||||
|
|
||||||
Goodbye, pipenv!
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This version requires a manual migration step.
|
|
||||||
|
|
||||||
1. You need to install ``poetry``
|
|
||||||
2. You need to create a new ``pyproject.toml`` file with ``poetry init``
|
|
||||||
3. You need to adjust name, version, description, and authors meta fields
|
|
||||||
4. You need to copy-paste dependencies from ``Pipfile`` to ``pyproject.toml``
|
|
||||||
5. You need to set correct version for each dependency in the list,
|
|
||||||
use ``"^x.y"`` `notation <https://python-poetry.org/docs/dependency-specification/#caret-requirements>`_
|
|
||||||
6. You need to adjust ``[build-system]`` tag and ``POETRY_VERSION`` variable
|
|
||||||
to fit your ``poetry`` version
|
|
||||||
7. Create ``poetry.lock`` file with ``poetry lock``
|
|
||||||
|
|
||||||
It should be fine! You may, however, experience some bugs related to different
|
|
||||||
dependency version resolution mechanisms. But, ``poetry`` does it better.
|
|
@ -1,6 +0,0 @@
|
|||||||
# This file is required since we use ReadTheDocs services.
|
|
||||||
# If it is not used, feel free to delete it.
|
|
||||||
|
|
||||||
sphinx==4.0.3
|
|
||||||
sphinx_autodoc_typehints==1.12.0
|
|
||||||
tomlkit==0.7.2
|
|
@ -1,31 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
"""
|
|
||||||
Main function.
|
|
||||||
|
|
||||||
It does several things:
|
|
||||||
1. Sets default settings module, if it is not set
|
|
||||||
2. Warns if Django is not installed
|
|
||||||
3. Executes any given command
|
|
||||||
"""
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings')
|
|
||||||
|
|
||||||
try:
|
|
||||||
from django.core import management # noqa: WPS433
|
|
||||||
except ImportError:
|
|
||||||
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?',
|
|
||||||
)
|
|
||||||
|
|
||||||
management.execute_from_command_line(sys.argv)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
2719
new-github-repos/poetry.lock
generated
2719
new-github-repos/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,73 +0,0 @@
|
|||||||
[build-system]
|
|
||||||
requires = ["poetry-core>=1.0.0"]
|
|
||||||
build-backend = "poetry.core.masonry.api"
|
|
||||||
|
|
||||||
|
|
||||||
[tool.nitpick]
|
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry]
|
|
||||||
name = "github-repos"
|
|
||||||
description = "github repos"
|
|
||||||
version = "0.1.0"
|
|
||||||
readme = "README.md"
|
|
||||||
authors = ["balsh"]
|
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
|
||||||
python = "3.8.9"
|
|
||||||
django = "^3"
|
|
||||||
django-split-settings = "^1.0"
|
|
||||||
django-axes = "^5.20"
|
|
||||||
django-csp = "^3.7"
|
|
||||||
django-health-check = "^3.16"
|
|
||||||
django-http-referrer-policy = "^1.1"
|
|
||||||
django-permissions-policy = "^4.1"
|
|
||||||
django-stubs-ext = "^0.2"
|
|
||||||
|
|
||||||
psycopg2-binary = "<2.9"
|
|
||||||
gunicorn = "^20.0"
|
|
||||||
python-decouple = "^3.4"
|
|
||||||
bcrypt = "^3.2"
|
|
||||||
structlog = "^21.1"
|
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
|
||||||
django-debug-toolbar = "^3.2"
|
|
||||||
django-querycount = "^0.7"
|
|
||||||
django-migration-linter = "^3.0"
|
|
||||||
django-extra-checks = "^0.11"
|
|
||||||
django-coverage-plugin = "^2.0"
|
|
||||||
nplusone = "^1.0"
|
|
||||||
|
|
||||||
wemake-python-styleguide = "^0.15"
|
|
||||||
flake8-pytest-style = "^1.5"
|
|
||||||
flake8-django = "^1.1"
|
|
||||||
flake8-logging-format = "^0.6"
|
|
||||||
nitpick = "^0.26"
|
|
||||||
|
|
||||||
pytest = "^6.2"
|
|
||||||
pytest-django = "^4.4"
|
|
||||||
pytest-cov = "^2.12"
|
|
||||||
pytest-randomly = "^3.8"
|
|
||||||
pytest-deadfixtures = "^2.2"
|
|
||||||
pytest-testmon = "^1.1"
|
|
||||||
pytest-timeout = "^1.4"
|
|
||||||
django-test-migrations = "^1.1"
|
|
||||||
hypothesis = "^6.14"
|
|
||||||
|
|
||||||
mypy = "^0.910"
|
|
||||||
django-stubs = "^1.8"
|
|
||||||
|
|
||||||
sphinx = "^4.0"
|
|
||||||
sphinx-autodoc-typehints = "^1.12"
|
|
||||||
tomlkit = "^0.7"
|
|
||||||
doc8 = "^0.8"
|
|
||||||
|
|
||||||
yamllint = "^1.26"
|
|
||||||
safety = "^1.10"
|
|
||||||
dotenv-linter = "^0.2"
|
|
||||||
polint = "^0.4"
|
|
||||||
dennis = "^0.9"
|
|
||||||
dump-env = "^1.3"
|
|
||||||
ipython = "^7.25"
|
|
@ -1,8 +0,0 @@
|
|||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
from server.apps.main.models import BlogPost
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(BlogPost)
|
|
||||||
class BlogPostAdmin(admin.ModelAdmin[BlogPost]):
|
|
||||||
"""Admin panel example for ``BlogPost`` model."""
|
|
@ -1,11 +0,0 @@
|
|||||||
"""
|
|
||||||
This package is a place for your business logic.
|
|
||||||
|
|
||||||
Please, do not create any other files inside your app package.
|
|
||||||
Place all files here, including: logic, forms, serializers.
|
|
||||||
|
|
||||||
Decoupling is a good thing. We need more of that.
|
|
||||||
|
|
||||||
Try using https://github.com/dry-python/
|
|
||||||
It is awesome for writing business logic!
|
|
||||||
"""
|
|
@ -1,35 +0,0 @@
|
|||||||
# Generated by Django 2.2.7 on 2019-11-24 11:01
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
"""Initial migration that creates the example BlogPost model."""
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
dependencies = []
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='BlogPost',
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
'id',
|
|
||||||
models.AutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name='ID',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
('title', models.CharField(max_length=80)),
|
|
||||||
('body', models.TextField()),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'BlogPost',
|
|
||||||
'verbose_name_plural': 'BlogPosts',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,33 +0,0 @@
|
|||||||
import textwrap
|
|
||||||
from typing import Final, final
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
#: That's how constants should be defined.
|
|
||||||
_POST_TITLE_MAX_LENGTH: Final = 80
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
class BlogPost(models.Model):
|
|
||||||
"""
|
|
||||||
This model is used just as an example.
|
|
||||||
|
|
||||||
With it we show how one can:
|
|
||||||
- Use fixtures and factories
|
|
||||||
- Use migrations testing
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
title = models.CharField(max_length=_POST_TITLE_MAX_LENGTH)
|
|
||||||
body = models.TextField()
|
|
||||||
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
|
||||||
|
|
||||||
class Meta(object):
|
|
||||||
verbose_name = 'BlogPost' # You can probably use `gettext` for this
|
|
||||||
verbose_name_plural = 'BlogPosts'
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
"""All django models should have this method."""
|
|
||||||
return textwrap.wrap(self.title, _POST_TITLE_MAX_LENGTH // 4)[0]
|
|
@ -1,29 +0,0 @@
|
|||||||
body {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wemake-services-body {
|
|
||||||
height: 95vh;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wemake-services-logo {
|
|
||||||
max-width: 250px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.github-corner img {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 5.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 8.6 KiB |
File diff suppressed because one or more lines are too long
@ -1,9 +0,0 @@
|
|||||||
from django.urls import path
|
|
||||||
|
|
||||||
from server.apps.main.views import index
|
|
||||||
|
|
||||||
app_name = 'main'
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path('hello/', index, name='hello'),
|
|
||||||
]
|
|
@ -1,12 +0,0 @@
|
|||||||
from django.http import HttpRequest, HttpResponse
|
|
||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
|
|
||||||
def index(request: HttpRequest) -> HttpResponse:
|
|
||||||
"""
|
|
||||||
Main (or index) view.
|
|
||||||
|
|
||||||
Returns rendered default page to the user.
|
|
||||||
Typed with the help of ``django-stubs`` project.
|
|
||||||
"""
|
|
||||||
return render(request, 'main/index.html')
|
|
@ -1,39 +0,0 @@
|
|||||||
"""
|
|
||||||
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',
|
|
||||||
|
|
||||||
# Select the right env:
|
|
||||||
'environments/{0}.py'.format(_ENV),
|
|
||||||
|
|
||||||
# Optionally override some settings:
|
|
||||||
optional('environments/local.py'),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Include settings:
|
|
||||||
include(*_base_settings)
|
|
@ -1,11 +0,0 @@
|
|||||||
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 = Path.cwd().parent.parent.parent.parent
|
|
||||||
|
|
||||||
# Loading `.env` files
|
|
||||||
# See docs: https://gitlab.com/mkleehammer/autoconfig
|
|
||||||
config = AutoConfig(search_path=BASE_DIR.joinpath('config'))
|
|
@ -1,16 +0,0 @@
|
|||||||
# 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'
|
|
@ -1,201 +0,0 @@
|
|||||||
"""
|
|
||||||
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/
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import Dict, List, Tuple, Union
|
|
||||||
|
|
||||||
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')
|
|
||||||
|
|
||||||
# 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 = 'en-us'
|
|
||||||
|
|
||||||
USE_I18N = True
|
|
||||||
USE_L10N = True
|
|
||||||
|
|
||||||
LANGUAGES = (
|
|
||||||
('en', _('English')),
|
|
||||||
('ru', _('Russian')),
|
|
||||||
)
|
|
||||||
|
|
||||||
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',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# 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
|
|
@ -1,15 +0,0 @@
|
|||||||
"""
|
|
||||||
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'",)
|
|
@ -1,77 +0,0 @@
|
|||||||
# 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,
|
|
||||||
)
|
|
@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
"""Overriding settings based on the environment."""
|
|
@ -1,150 +0,0 @@
|
|||||||
"""
|
|
||||||
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
|
|
@ -1 +0,0 @@
|
|||||||
"""Override any custom settings here."""
|
|
@ -1,75 +0,0 @@
|
|||||||
"""
|
|
||||||
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
|
|
@ -1,14 +0,0 @@
|
|||||||
# The humans responsible & technology colophon
|
|
||||||
# http://humanstxt.org/
|
|
||||||
|
|
||||||
|
|
||||||
## balsh
|
|
||||||
|
|
||||||
Team:
|
|
||||||
|
|
||||||
|
|
||||||
## Technologies
|
|
||||||
|
|
||||||
Language: English
|
|
||||||
Doctype: HTML5
|
|
||||||
Technologies: Python, Django
|
|
@ -1,2 +0,0 @@
|
|||||||
User-agent: *
|
|
||||||
Disallow:
|
|
@ -1,60 +0,0 @@
|
|||||||
"""
|
|
||||||
Main URL mapping configuration file.
|
|
||||||
|
|
||||||
Include other URLConfs from external apps using method `include()`.
|
|
||||||
|
|
||||||
It is also a good practice to keep a single URL to the root index page.
|
|
||||||
|
|
||||||
This examples uses Django's default media
|
|
||||||
files serving technique in development.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.contrib.admindocs import urls as admindocs_urls
|
|
||||||
from django.urls import include, path
|
|
||||||
from django.views.generic import TemplateView
|
|
||||||
from health_check import urls as health_urls
|
|
||||||
|
|
||||||
from server.apps.main import urls as main_urls
|
|
||||||
from server.apps.main.views import index
|
|
||||||
|
|
||||||
admin.autodiscover()
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
# Apps:
|
|
||||||
path('main/', include(main_urls, namespace='main')),
|
|
||||||
|
|
||||||
# Health checks:
|
|
||||||
path('health/', include(health_urls)), # noqa: DJ05
|
|
||||||
|
|
||||||
# django-admin:
|
|
||||||
path('admin/doc/', include(admindocs_urls)), # noqa: DJ05
|
|
||||||
path('admin/', admin.site.urls),
|
|
||||||
|
|
||||||
# Text and xml static files:
|
|
||||||
path('robots.txt', TemplateView.as_view(
|
|
||||||
template_name='txt/robots.txt',
|
|
||||||
content_type='text/plain',
|
|
||||||
)),
|
|
||||||
path('humans.txt', TemplateView.as_view(
|
|
||||||
template_name='txt/humans.txt',
|
|
||||||
content_type='text/plain',
|
|
||||||
)),
|
|
||||||
|
|
||||||
# It is a good practice to have explicit index view:
|
|
||||||
path('', index, name='index'),
|
|
||||||
]
|
|
||||||
|
|
||||||
if settings.DEBUG: # pragma: no cover
|
|
||||||
import debug_toolbar # noqa: WPS433
|
|
||||||
from django.conf.urls.static import static # noqa: WPS433
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
# URLs specific only to django-debug-toolbar:
|
|
||||||
path('__debug__/', include(debug_toolbar.urls)), # noqa: DJ05
|
|
||||||
] + urlpatterns + static( # type: ignore
|
|
||||||
# Serving media files in development only:
|
|
||||||
settings.MEDIA_URL,
|
|
||||||
document_root=settings.MEDIA_ROOT,
|
|
||||||
)
|
|
@ -1,15 +0,0 @@
|
|||||||
"""
|
|
||||||
WSGI config for server 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', 'server.settings')
|
|
||||||
application = get_wsgi_application()
|
|
@ -1,144 +0,0 @@
|
|||||||
# All configuration for plugins and other utils is defined here.
|
|
||||||
# Read more about `setup.cfg`:
|
|
||||||
# https://docs.python.org/3/distutils/configfile.html
|
|
||||||
|
|
||||||
|
|
||||||
[flake8]
|
|
||||||
# flake8 configuration:
|
|
||||||
# https://flake8.pycqa.org/en/latest/user/configuration.html
|
|
||||||
format = wemake
|
|
||||||
show-source = True
|
|
||||||
statistics = False
|
|
||||||
doctests = True
|
|
||||||
enable-extensions = G
|
|
||||||
|
|
||||||
# darglint configuration:
|
|
||||||
# https://github.com/terrencepreilly/darglint
|
|
||||||
strictness = long
|
|
||||||
docstring-style = numpy
|
|
||||||
|
|
||||||
# Flake plugins:
|
|
||||||
max-line-length = 80
|
|
||||||
max-complexity = 6
|
|
||||||
|
|
||||||
# Excluding some directories:
|
|
||||||
exclude = .git,__pycache__,.venv,.eggs,*.egg
|
|
||||||
|
|
||||||
# Disable some pydocstyle checks:
|
|
||||||
ignore = D100, D104, D106, D401, X100, W504, RST303, RST304, DAR103, DAR203
|
|
||||||
|
|
||||||
# Docs: https://github.com/snoack/flake8-per-file-ignores
|
|
||||||
# You can completely or partially disable our custom checks,
|
|
||||||
# to do so you have to ignore `WPS` letters for all python files:
|
|
||||||
per-file-ignores =
|
|
||||||
# Allow `__init__.py` with logic for configuration:
|
|
||||||
server/settings/*.py: WPS226, WPS407, WPS412, WPS432
|
|
||||||
# Allow to have magic numbers inside migrations and wrong module names:
|
|
||||||
server/*/migrations/*.py: WPS102, WPS114, WPS432
|
|
||||||
# Enable `assert` keyword and magic numbers for tests:
|
|
||||||
tests/*.py: S101, WPS432
|
|
||||||
|
|
||||||
|
|
||||||
[isort]
|
|
||||||
# isort configuration:
|
|
||||||
# https://github.com/timothycrosley/isort/wiki/isort-Settings
|
|
||||||
include_trailing_comma = true
|
|
||||||
use_parentheses = true
|
|
||||||
# See https://github.com/timothycrosley/isort#multi-line-output-modes
|
|
||||||
multi_line_output = 3
|
|
||||||
line_length = 80
|
|
||||||
|
|
||||||
|
|
||||||
[tool:pytest]
|
|
||||||
# pytest configuration:
|
|
||||||
# https://docs.pytest.org/en/stable/customize.html
|
|
||||||
|
|
||||||
# pytest-django configuration:
|
|
||||||
# https://pytest-django.readthedocs.io/en/latest/
|
|
||||||
DJANGO_SETTINGS_MODULE = server.settings
|
|
||||||
|
|
||||||
# Timeout for tests, so they can not take longer
|
|
||||||
# than this amount of seconds.
|
|
||||||
# You should adjust this value to be as low as possible.
|
|
||||||
# Configuration:
|
|
||||||
# https://pypi.org/project/pytest-timeout/
|
|
||||||
timeout = 5
|
|
||||||
|
|
||||||
# Directories that are not visited by pytest collector:
|
|
||||||
norecursedirs = *.egg .eggs dist build docs .tox .git __pycache__
|
|
||||||
|
|
||||||
# You will need to measure your tests speed with `-n auto` and without it,
|
|
||||||
# so you can see whether it gives you any performance gain, or just gives
|
|
||||||
# you an overhead. See `docs/template/development-process.rst`.
|
|
||||||
addopts =
|
|
||||||
--strict-markers
|
|
||||||
--strict-config
|
|
||||||
--doctest-modules
|
|
||||||
--fail-on-template-vars
|
|
||||||
--dup-fixtures
|
|
||||||
# Output:
|
|
||||||
--tb=short
|
|
||||||
# Parallelism:
|
|
||||||
# -n auto
|
|
||||||
# --boxed
|
|
||||||
# Coverage:
|
|
||||||
--cov=server
|
|
||||||
--cov=tests
|
|
||||||
--cov-branch
|
|
||||||
--cov-report=term-missing:skip-covered
|
|
||||||
--cov-report=html
|
|
||||||
--cov-fail-under=100
|
|
||||||
|
|
||||||
|
|
||||||
[coverage:run]
|
|
||||||
# Coverage configuration:
|
|
||||||
# https://coverage.readthedocs.io/en/latest/config.html
|
|
||||||
plugins =
|
|
||||||
# Docs: https://github.com/nedbat/django_coverage_plugin
|
|
||||||
django_coverage_plugin
|
|
||||||
|
|
||||||
|
|
||||||
[mypy]
|
|
||||||
# Mypy configuration:
|
|
||||||
# https://mypy.readthedocs.io/en/latest/config_file.html
|
|
||||||
allow_redefinition = False
|
|
||||||
check_untyped_defs = True
|
|
||||||
disallow_untyped_decorators = True
|
|
||||||
disallow_any_explicit = True
|
|
||||||
disallow_any_generics = True
|
|
||||||
disallow_untyped_calls = True
|
|
||||||
ignore_errors = False
|
|
||||||
ignore_missing_imports = True
|
|
||||||
implicit_reexport = False
|
|
||||||
local_partial_types = True
|
|
||||||
strict_optional = True
|
|
||||||
strict_equality = True
|
|
||||||
no_implicit_optional = True
|
|
||||||
warn_unused_ignores = True
|
|
||||||
warn_redundant_casts = True
|
|
||||||
warn_unused_configs = True
|
|
||||||
warn_unreachable = True
|
|
||||||
warn_no_return = True
|
|
||||||
|
|
||||||
plugins =
|
|
||||||
mypy_django_plugin.main
|
|
||||||
|
|
||||||
[mypy.plugins.django-stubs]
|
|
||||||
django_settings_module = server.settings
|
|
||||||
|
|
||||||
[mypy-server.apps.*.migrations.*]
|
|
||||||
# Django migrations should not produce any errors:
|
|
||||||
ignore_errors = True
|
|
||||||
|
|
||||||
[mypy-server.apps.*.models]
|
|
||||||
# FIXME: remove this line, when `django-stubs` will stop
|
|
||||||
# using `Any` inside.
|
|
||||||
disallow_any_explicit = False
|
|
||||||
|
|
||||||
|
|
||||||
[doc8]
|
|
||||||
# doc8 configuration:
|
|
||||||
# https://github.com/pycqa/doc8
|
|
||||||
ignore-path = docs/_build
|
|
||||||
max-line-length = 80
|
|
||||||
sphinx = True
|
|
@ -1,45 +0,0 @@
|
|||||||
"""
|
|
||||||
This module is used to provide configuration, fixtures, and plugins for pytest.
|
|
||||||
|
|
||||||
It may be also used for extending doctest's context:
|
|
||||||
1. https://docs.python.org/3/library/doctest.html
|
|
||||||
2. https://docs.pytest.org/en/latest/doctest.html
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
|
||||||
def _media_root(settings, tmpdir_factory) -> None:
|
|
||||||
"""Forces django to save media files into temp folder."""
|
|
||||||
settings.MEDIA_ROOT = tmpdir_factory.mktemp('media', numbered=True)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
|
||||||
def _password_hashers(settings) -> None:
|
|
||||||
"""Forces django to use fast password hashers for tests."""
|
|
||||||
settings.PASSWORD_HASHERS = [
|
|
||||||
'django.contrib.auth.hashers.MD5PasswordHasher',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
|
||||||
def _auth_backends(settings) -> None:
|
|
||||||
"""Deactivates security backend from Axes app."""
|
|
||||||
settings.AUTHENTICATION_BACKENDS = (
|
|
||||||
'django.contrib.auth.backends.ModelBackend',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
|
||||||
def _debug(settings) -> None:
|
|
||||||
"""Sets proper DEBUG and TEMPLATE debug mode for coverage."""
|
|
||||||
settings.DEBUG = False
|
|
||||||
for template in settings.TEMPLATES:
|
|
||||||
template['OPTIONS']['debug'] = True
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def main_heading() -> str:
|
|
||||||
"""An example fixture containing some html fragment."""
|
|
||||||
return '<h1>wemake-django-template</h1>'
|
|
@ -1,16 +0,0 @@
|
|||||||
from hypothesis import given
|
|
||||||
from hypothesis.extra import django
|
|
||||||
|
|
||||||
from server.apps.main.models import BlogPost
|
|
||||||
|
|
||||||
|
|
||||||
class TestBlogPost(django.TestCase):
|
|
||||||
"""This is a property-based test that ensures model correctness."""
|
|
||||||
|
|
||||||
@given(django.from_model(BlogPost))
|
|
||||||
def test_model_properties(self, instance: BlogPost) -> None:
|
|
||||||
"""Tests that instance can be saved and has correct representation."""
|
|
||||||
instance.save()
|
|
||||||
|
|
||||||
assert instance.id > 0
|
|
||||||
assert len(str(instance)) <= 20
|
|
@ -1,17 +0,0 @@
|
|||||||
import pytest
|
|
||||||
from django_test_migrations.migrator import Migrator
|
|
||||||
|
|
||||||
from server.apps.main.urls import app_name
|
|
||||||
|
|
||||||
|
|
||||||
def test_initial0001(migrator: Migrator) -> None:
|
|
||||||
"""Tests the initial migration forward application."""
|
|
||||||
old_state = migrator.apply_initial_migration((app_name, None))
|
|
||||||
with pytest.raises(LookupError):
|
|
||||||
# This model does not exist before this migration:
|
|
||||||
old_state.apps.get_model(app_name, 'BlogPost')
|
|
||||||
|
|
||||||
new_state = migrator.apply_tested_migration((app_name, '0001_initial'))
|
|
||||||
model = new_state.apps.get_model(app_name, 'BlogPost')
|
|
||||||
|
|
||||||
assert model.objects.create(title='test', body='some body')
|
|
@ -1,18 +0,0 @@
|
|||||||
from django.test import Client
|
|
||||||
from django.urls import reverse
|
|
||||||
|
|
||||||
|
|
||||||
def test_main_page(client: Client, main_heading: str) -> None:
|
|
||||||
"""This test ensures that main page works."""
|
|
||||||
response = client.get('/')
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert main_heading in str(response.content)
|
|
||||||
|
|
||||||
|
|
||||||
def test_hello_page(client: Client, main_heading: str) -> None:
|
|
||||||
"""This test ensures that hello page works."""
|
|
||||||
response = client.get(reverse('main:hello'))
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert main_heading in str(response.content)
|
|
@ -1,55 +0,0 @@
|
|||||||
import pytest
|
|
||||||
from django.test import Client
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db()
|
|
||||||
def test_health_check(client: Client) -> None:
|
|
||||||
"""This test ensures that health check is accessible."""
|
|
||||||
response = client.get('/health/')
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
|
|
||||||
def test_admin_unauthorized(client: Client) -> None:
|
|
||||||
"""This test ensures that admin panel requires auth."""
|
|
||||||
response = client.get('/admin/')
|
|
||||||
|
|
||||||
assert response.status_code == 302
|
|
||||||
|
|
||||||
|
|
||||||
def test_admin_authorized(admin_client: Client) -> None:
|
|
||||||
"""This test ensures that admin panel is accessible."""
|
|
||||||
response = admin_client.get('/admin/')
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
|
|
||||||
def test_admin_docs_unauthorized(client: Client) -> None:
|
|
||||||
"""This test ensures that admin panel docs requires auth."""
|
|
||||||
response = client.get('/admin/doc/')
|
|
||||||
|
|
||||||
assert response.status_code == 302
|
|
||||||
|
|
||||||
|
|
||||||
def test_admin_docs_authorized(admin_client: Client) -> None:
|
|
||||||
"""This test ensures that admin panel docs are accessible."""
|
|
||||||
response = admin_client.get('/admin/doc/')
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert b'docutils' not in response.content
|
|
||||||
|
|
||||||
|
|
||||||
def test_robots_txt(client: Client) -> None:
|
|
||||||
"""This test ensures that `robots.txt` is accessible."""
|
|
||||||
response = client.get('/robots.txt')
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.get('Content-Type') == 'text/plain'
|
|
||||||
|
|
||||||
|
|
||||||
def test_humans_txt(client: Client) -> None:
|
|
||||||
"""This test ensures that `humans.txt` is accessible."""
|
|
||||||
response = client.get('/humans.txt')
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.get('Content-Type') == 'text/plain'
|
|
Loading…
x
Reference in New Issue
Block a user