Rework project structure

This commit is contained in:
Gabriel Augendre 2022-06-15 19:45:35 +02:00
parent bf76cd227a
commit 4bd48ffedd
100 changed files with 423 additions and 188 deletions

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
envs/local.env envs/local.env
envs/docker-local.env
.envrc .envrc
pytest_result pytest_result

View file

@ -1,34 +1,63 @@
FROM python:3.6-alpine ## Build venv
FROM python:3.10.4-bullseye AS venv
RUN apk add --update postgresql-libs && \ RUN apt-get update && apt-get install -y --no-install-recommends \
apk add --udpate --virtual .build-deps gcc musl-dev postgresql-dev tzdata && \ gettext
cp /usr/share/zoneinfo/Europe/Paris /etc/localtime && \
echo "Europe/Paris" > /etc/timezone # https://python-poetry.org/docs/#installation
ENV POETRY_VERSION=1.1.13
RUN curl -sSL https://install.python-poetry.org | python3 -
ENV PATH /root/.local/bin:$PATH
ARG POETRY_OPTIONS
WORKDIR /app WORKDIR /app
EXPOSE 8000
VOLUME /app/staticfiles
RUN pip3 install pipenv COPY pyproject.toml poetry.lock ./
COPY Pipfile Pipfile.lock ./
RUN pipenv install --deploy
RUN apk del .build-deps RUN python -m venv --copies /app/venv \
&& . /app/venv/bin/activate \
&& poetry install $POETRY_OPTIONS
COPY . ./ ENV PATH /app/venv/bin:$PATH
COPY src ./src/
ARG SECRET_KEY="somevalue"
ARG DATABASE_URL="somevalue"
RUN python ./src/manage.py collectstatic --no-input
RUN python ./src/manage.py compilemessages -l fr -l en
CMD ["sh", "bash/run-prod.sh"] ## Get git versions
FROM alpine/git AS git
ADD . /app
WORKDIR /app
RUN git rev-parse HEAD | tee /version
HEALTHCHECK --interval=10s --timeout=10s CMD ["pipenv", "run", "python", "healthcheck.py"]
ENV DATABASE_URL postgres://postgresql:postgresql@db:5432/manuels ## Beginning of runtime image
ENV SECRET_KEY '' FROM python:3.10.4-slim-bullseye as prod
ENV MAILGUN_ACCESS_KEY '' ENV TZ "Europe/Paris"
ENV MAILGUN_SERVER_NAME '' RUN mkdir -p /app/db
ENV DJANGO_ENV ''
ENV ADMIN_EMAIL '' COPY --from=venv /app/venv /app/venv/
ENV SERVER_EMAIL '' ENV PATH /app/venv/bin:$PATH
ENV HOST ''
ENV REPLY_TO '' WORKDIR /app
ENV AUTHORIZED_EMAILS '' COPY LICENSE pyproject.toml ./
ENV LIBRARIAN_EMAILS '' COPY docker ./docker/
COPY src ./src/
COPY --from=git /version /app/.version
COPY --from=venv /app/staticfiles /app/staticfiles/
ENV SECRET_KEY "changeme"
ENV DEBUG "false"
ENV DB_BASE_DIR "/app/db"
#ENV HOSTS="host1;host2"
#ENV ADMINS='Full Name,email@example.com'
#ENV MAILGUN_API_KEY='key-yourapikey'
#ENV MAILGUN_SENDER_DOMAIN='mailgun.example.com'
#ENV BLOG_BASE_URL='https://url-of-your-blog.example.com'
HEALTHCHECK --start-period=30s CMD python -c "import requests; requests.get('http://localhost:8000', timeout=2)"
WORKDIR /app/src
CMD ["/app/docker/run.sh"]

View file

@ -4,21 +4,14 @@ Help librarian manage textbooks requests from colleagues
## Local development ## Local development
```bash ```bash
pipenv install pyenv virtualenv 3.10.4 manuels
pipenv run python manage.py migrate pyenv local manuels
DJANGO_ENV=dev pipenv run python manage.py runserver poetry install
cp envs/local.env.dist envs/local.env
echo 'export ENV_FILE=$(realpath "./envs/local.env")' > .envrc
direnv allow
inv test
``` ```
## Deploy on Heroku
```bash
heroku login
heroku git:remote --app manuels-scolaires
git push heroku master
```
You may need to upgrade Python since Heroku tends to deprecate old patch versions rather quickly.
In this case, edit `runtime.txt`.
# Reuse # Reuse
If you do reuse my work, please consider linking back to this repository 🙂 If you do reuse my work, please consider linking back to this repository 🙂

View file

@ -1,5 +0,0 @@
#!/bin/sh
yes yes | pipenv run python manage.py migrate && \
yes yes | pipenv run python manage.py createcachetable && \
pipenv run python manage.py collectstatic --noinput && \
pipenv run gunicorn manuels_collection.wsgi -b 0.0.0.0:8000 --log-file -

12
docker-compose-build.yaml Normal file
View file

@ -0,0 +1,12 @@
version: '2.4'
services:
django:
extends:
file: docker-compose.yaml
service: django
image: crocmagnon/manuels-scolaires:latest
platform: linux/amd64
volumes:
staticfiles: {}
media: {}

34
docker-compose.yaml Normal file
View file

@ -0,0 +1,34 @@
version: '2.4'
services:
django:
image: crocmagnon/manuels-scolaires:dev
platform: linux/amd64
build:
context: .
args:
POETRY_OPTIONS: "--no-dev"
env_file:
- envs/docker-local.env
volumes:
- staticfiles:/app/staticfiles
- media:/app/media
restart: on-failure
init: true
tty: true
ports:
- "8000:8000"
postgres:
image: postgres:14
environment:
POSTGRES_PASSWORD: "manuels"
POSTGRES_USER: "manuels"
POSTGRES_DB: "manuels"
volumes:
- pg_data:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
staticfiles: {}
media: {}
pg_data: {}

6
docker/run.sh Executable file
View file

@ -0,0 +1,6 @@
#!/bin/bash
set -eux
python manage.py migrate --noinput
python manage.py createcachetable
python manage.py clearcache
gunicorn manuels_collection.wsgi -b 0.0.0.0:8000 --log-file -

171
poetry.lock generated
View file

@ -141,31 +141,19 @@ postal = ["cryptography"]
[[package]] [[package]]
name = "django-bootstrap4" name = "django-bootstrap4"
version = "3.0.1" version = "22.1"
description = "Bootstrap 4 for Django" description = "Bootstrap 4 for Django"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.7"
[package.dependencies] [package.dependencies]
beautifulsoup4 = ">=4.8.0" beautifulsoup4 = ">=4.8.0"
Django = ">=2.2" Django = ">=2.2"
[[package]]
name = "django-debug-toolbar"
version = "3.4.0"
description = "A configurable set of panels that display various debug information about the current request/response."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
Django = ">=3.2"
sqlparse = ">=0.2.0"
[[package]] [[package]]
name = "django-environ" name = "django-environ"
version = "0.8.1" version = "0.9.0"
description = "A package that allows you to utilize 12factor inspired environment variables to configure your Django application." description = "A package that allows you to utilize 12factor inspired environment variables to configure your Django application."
category = "main" category = "main"
optional = false optional = false
@ -176,22 +164,6 @@ develop = ["coverage[toml] (>=5.0a4)", "pytest (>=4.6.11)", "furo (>=2021.8.17b4
docs = ["furo (>=2021.8.17b43,<2021.9.0)", "sphinx (>=3.5.0)", "sphinx-notfound-page"] docs = ["furo (>=2021.8.17b43,<2021.9.0)", "sphinx (>=3.5.0)", "sphinx-notfound-page"]
testing = ["coverage[toml] (>=5.0a4)", "pytest (>=4.6.11)"] testing = ["coverage[toml] (>=5.0a4)", "pytest (>=4.6.11)"]
[[package]]
name = "django-environ-2"
version = "2.3.0"
description = "Configure Django made easy."
category = "main"
optional = false
python-versions = ">=3.6, <4"
[package.dependencies]
django-environ = "*"
[package.extras]
develop = ["coverage[toml] (>=5.4)", "pytest (>=6.2.4)", "pylint (>=2.6.0,!=2.6.1)", "flake8 (>=3.8.4)", "flake8-import-order (>=0.18.1)", "flake8-blind-except (>=0.2.0)", "check-manifest (>=0.45)", "furo (>=2021.8.17b43,<2021.9.0)", "sphinx (>=3.5.0)", "sphinx-notfound-page"]
docs = ["furo (>=2021.8.17b43,<2021.9.0)", "sphinx (>=3.5.0)", "sphinx-notfound-page"]
testing = ["coverage[toml] (>=5.4)", "pytest (>=6.2.4)", "pylint (>=2.6.0,!=2.6.1)", "flake8 (>=3.8.4)", "flake8-import-order (>=0.18.1)", "flake8-blind-except (>=0.2.0)", "check-manifest (>=0.45)"]
[[package]] [[package]]
name = "django-import-export" name = "django-import-export"
version = "2.8.0" version = "2.8.0"
@ -266,6 +238,14 @@ category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
[[package]]
name = "invoke"
version = "1.7.1"
description = "Pythonic task execution"
category = "dev"
optional = false
python-versions = "*"
[[package]] [[package]]
name = "markuppy" name = "markuppy"
version = "1.14" version = "1.14"
@ -358,6 +338,20 @@ python-versions = ">=3.6"
dev = ["pre-commit", "tox"] dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"] testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "poetry-deps-scanner"
version = "1.0.1"
description = "Analyse poetry dependencies and comment on gitlab"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
python-gitlab = ">=2.6.0"
requests = ">=2.25.1"
semver = ">=3.0.0-dev.2"
toml = ">=0.10.2"
[[package]] [[package]]
name = "pre-commit" name = "pre-commit"
version = "2.19.0" version = "2.19.0"
@ -403,11 +397,11 @@ diagrams = ["railroad-diagrams", "jinja2"]
[[package]] [[package]]
name = "pytest" name = "pytest"
version = "6.2.5" version = "7.1.2"
description = "pytest: simple powerful testing with Python" description = "pytest: simple powerful testing with Python"
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.7"
[package.dependencies] [package.dependencies]
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
@ -417,10 +411,10 @@ iniconfig = "*"
packaging = "*" packaging = "*"
pluggy = ">=0.12,<2.0" pluggy = ">=0.12,<2.0"
py = ">=1.8.2" py = ">=1.8.2"
toml = "*" tomli = ">=1.0.0"
[package.extras] [package.extras]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
[[package]] [[package]]
name = "pytest-django" name = "pytest-django"
@ -451,14 +445,30 @@ pytest-metadata = "*"
[[package]] [[package]]
name = "pytest-metadata" name = "pytest-metadata"
version = "1.11.0" version = "2.0.0"
description = "pytest plugin for test session metadata" description = "pytest plugin for test session metadata"
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" python-versions = ">=3.7,<4.0"
[package.dependencies] [package.dependencies]
pytest = ">=2.9.0" pytest = ">=7.1.1,<8.0.0"
[[package]]
name = "python-gitlab"
version = "3.5.0"
description = "Interact with GitLab API"
category = "dev"
optional = false
python-versions = ">=3.7.0"
[package.dependencies]
requests = ">=2.25.0"
requests-toolbelt = ">=0.9.1"
[package.extras]
autocompletion = ["argcomplete (>=1.10.0,<3)"]
yaml = ["PyYaml (>=5.2)"]
[[package]] [[package]]
name = "pytz" name = "pytz"
@ -494,6 +504,17 @@ urllib3 = ">=1.21.1,<1.27"
socks = ["PySocks (>=1.5.6,!=1.5.7)"] socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
[[package]]
name = "requests-toolbelt"
version = "0.9.1"
description = "A utility belt for advanced users of python-requests"
category = "dev"
optional = false
python-versions = "*"
[package.dependencies]
requests = ">=2.0.1,<3.0.0"
[[package]] [[package]]
name = "selenium" name = "selenium"
version = "3.141.0" version = "3.141.0"
@ -505,6 +526,14 @@ python-versions = "*"
[package.dependencies] [package.dependencies]
urllib3 = "*" urllib3 = "*"
[[package]]
name = "semver"
version = "3.0.0.dev3"
description = "Python helper for Semantic Versioning (http://semver.org)"
category = "dev"
optional = false
python-versions = ">=3.6.*"
[[package]] [[package]]
name = "six" name = "six"
version = "1.16.0" version = "1.16.0"
@ -563,6 +592,14 @@ category = "dev"
optional = false optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
category = "dev"
optional = false
python-versions = ">=3.7"
[[package]] [[package]]
name = "urllib3" name = "urllib3"
version = "1.26.9" version = "1.26.9"
@ -610,11 +647,11 @@ testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)",
[[package]] [[package]]
name = "whitenoise" name = "whitenoise"
version = "5.3.0" version = "6.2.0"
description = "Radically simplified static file serving for WSGI applications" description = "Radically simplified static file serving for WSGI applications"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.5, <4" python-versions = ">=3.7"
[package.extras] [package.extras]
brotli = ["brotli"] brotli = ["brotli"]
@ -663,7 +700,7 @@ multidict = ">=4.0"
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.10" python-versions = "^3.10"
content-hash = "888ada102147bc0242781801902680c0d9c0c4c9e6d6bc52bf69c257c62d7f79" content-hash = "b4789baaa83d1e40c5f39ff581b03fb901cadf0207bb2b577439c41fffa2ae02"
[metadata.files] [metadata.files]
asgiref = [ asgiref = [
@ -719,20 +756,12 @@ django-anymail = [
{file = "django_anymail-8.6-py3-none-any.whl", hash = "sha256:49d83d7c16316ca86a624097496881d59b7d71b16bf1c5211cffa5b19ef98d0c"}, {file = "django_anymail-8.6-py3-none-any.whl", hash = "sha256:49d83d7c16316ca86a624097496881d59b7d71b16bf1c5211cffa5b19ef98d0c"},
] ]
django-bootstrap4 = [ django-bootstrap4 = [
{file = "django-bootstrap4-3.0.1.tar.gz", hash = "sha256:c5c97fb473bb56e3a91b4f4be52b74a3fc384ec3baae50dd0807fa922a55ec2b"}, {file = "django-bootstrap4-22.1.tar.gz", hash = "sha256:fc9984f7238fbcd330ec5111bf0435083caa7192b022eedd53bfa4128bee318f"},
{file = "django_bootstrap4-3.0.1-py3-none-any.whl", hash = "sha256:aa8a9cb5ab27cfae52a27d377a0401af268d0e4b91a5f8e660546464582cc010"}, {file = "django_bootstrap4-22.1-py3-none-any.whl", hash = "sha256:b6da4cb54682012ff8baa1a1e672ba30cbfae82fb3d74f4b341109074e8e239f"},
]
django-debug-toolbar = [
{file = "django-debug-toolbar-3.4.0.tar.gz", hash = "sha256:ae6bec2c1ce0e6900b0ab0443e1427eb233d8e6f57a84a0b2705eeecb8874e22"},
{file = "django_debug_toolbar-3.4.0-py3-none-any.whl", hash = "sha256:42c1c2e9dc05bb57b53d641e3a6d131fc031b92377b34ae32e604a1fe439bb83"},
] ]
django-environ = [ django-environ = [
{file = "django-environ-0.8.1.tar.gz", hash = "sha256:6f0bc902b43891656b20486938cba0861dc62892784a44919170719572a534cb"}, {file = "django-environ-0.9.0.tar.gz", hash = "sha256:bff5381533056328c9ac02f71790bd5bf1cea81b1beeb648f28b81c9e83e0a21"},
{file = "django_environ-0.8.1-py2.py3-none-any.whl", hash = "sha256:42593bee519a527602a467c7b682aee1a051c2597f98c45f4f4f44169ecdb6e5"}, {file = "django_environ-0.9.0-py2.py3-none-any.whl", hash = "sha256:f21a5ef8cc603da1870bbf9a09b7e5577ab5f6da451b843dbcc721a7bca6b3d9"},
]
django-environ-2 = [
{file = "django-environ-2-2.3.0.tar.gz", hash = "sha256:6795fe26eb01291fad80866d4bfb572bb2966db13a8fab13dee77cc353ea4011"},
{file = "django_environ_2-2.3.0-py2.py3-none-any.whl", hash = "sha256:9503cc8d6e8d9d8a87ceb178b66fb717f0d1323e2064595038eae6021ba359a1"},
] ]
django-import-export = [ django-import-export = [
{file = "django-import-export-2.8.0.tar.gz", hash = "sha256:33c37b2921ef84e2cd9aa0eb76d04a7c2b538c9d04cb1ed97ac32600876cab30"}, {file = "django-import-export-2.8.0.tar.gz", hash = "sha256:33c37b2921ef84e2cd9aa0eb76d04a7c2b538c9d04cb1ed97ac32600876cab30"},
@ -762,6 +791,10 @@ iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
] ]
invoke = [
{file = "invoke-1.7.1-py3-none-any.whl", hash = "sha256:2dc975b4f92be0c0a174ad2d063010c8a1fdb5e9389d69871001118b4fcac4fb"},
{file = "invoke-1.7.1.tar.gz", hash = "sha256:7b6deaf585eee0a848205d0b8c0014b9bf6f287a8eb798818a642dff1df14b19"},
]
markuppy = [ markuppy = [
{file = "MarkupPy-1.14.tar.gz", hash = "sha256:1adee2c0a542af378fe84548ff6f6b0168f3cb7f426b46961038a2bcfaad0d5f"}, {file = "MarkupPy-1.14.tar.gz", hash = "sha256:1adee2c0a542af378fe84548ff6f6b0168f3cb7f426b46961038a2bcfaad0d5f"},
] ]
@ -854,6 +887,10 @@ pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
] ]
poetry-deps-scanner = [
{file = "poetry-deps-scanner-1.0.1.tar.gz", hash = "sha256:a1662c4ddc27a4606f4133830994e27212ee146709efc39ef3c91e1ee3dacbc5"},
{file = "poetry_deps_scanner-1.0.1-py3-none-any.whl", hash = "sha256:932ee6ef53def05030e47abf8e1a877e4c02252e1f1e6d17214aeae1a6b1c1a9"},
]
pre-commit = [ pre-commit = [
{file = "pre_commit-2.19.0-py2.py3-none-any.whl", hash = "sha256:10c62741aa5704faea2ad69cb550ca78082efe5697d6f04e5710c3c229afdd10"}, {file = "pre_commit-2.19.0-py2.py3-none-any.whl", hash = "sha256:10c62741aa5704faea2ad69cb550ca78082efe5697d6f04e5710c3c229afdd10"},
{file = "pre_commit-2.19.0.tar.gz", hash = "sha256:4233a1e38621c87d9dda9808c6606d7e7ba0e087cd56d3fe03202a01d2919615"}, {file = "pre_commit-2.19.0.tar.gz", hash = "sha256:4233a1e38621c87d9dda9808c6606d7e7ba0e087cd56d3fe03202a01d2919615"},
@ -925,8 +962,8 @@ pyparsing = [
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
] ]
pytest = [ pytest = [
{file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
{file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"},
] ]
pytest-django = [ pytest-django = [
{file = "pytest-django-4.5.2.tar.gz", hash = "sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2"}, {file = "pytest-django-4.5.2.tar.gz", hash = "sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2"},
@ -937,8 +974,12 @@ pytest-html = [
{file = "pytest_html-3.1.1-py3-none-any.whl", hash = "sha256:b7f82f123936a3f4d2950bc993c2c1ca09ce262c9ae12f9ac763a2401380b455"}, {file = "pytest_html-3.1.1-py3-none-any.whl", hash = "sha256:b7f82f123936a3f4d2950bc993c2c1ca09ce262c9ae12f9ac763a2401380b455"},
] ]
pytest-metadata = [ pytest-metadata = [
{file = "pytest-metadata-1.11.0.tar.gz", hash = "sha256:71b506d49d34e539cc3cfdb7ce2c5f072bea5c953320002c95968e0238f8ecf1"}, {file = "pytest-metadata-2.0.0.tar.gz", hash = "sha256:08dcc2779f4393309dd6d341ea1ddc15265239b6c4d51671737e784406ec07dc"},
{file = "pytest_metadata-1.11.0-py2.py3-none-any.whl", hash = "sha256:576055b8336dd4a9006dd2a47615f76f2f8c30ab12b1b1c039d99e834583523f"}, {file = "pytest_metadata-2.0.0-py3-none-any.whl", hash = "sha256:e25f1a77ed02baf1d83911604247a70d60d7dcb970aa12be38e1ed58d4d38e65"},
]
python-gitlab = [
{file = "python-gitlab-3.5.0.tar.gz", hash = "sha256:29ae7fb9b8c9aeb2e6e19bd2fd04867e93ecd7af719978ce68fac0cf116ab30d"},
{file = "python_gitlab-3.5.0-py3-none-any.whl", hash = "sha256:73b5aa6502efa557ee1a51227cceb0243fac5529627da34f08c5f265bf50417c"},
] ]
pytz = [ pytz = [
{file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"},
@ -983,10 +1024,18 @@ requests = [
{file = "requests-2.28.0-py3-none-any.whl", hash = "sha256:bc7861137fbce630f17b03d3ad02ad0bf978c844f3536d0edda6499dafce2b6f"}, {file = "requests-2.28.0-py3-none-any.whl", hash = "sha256:bc7861137fbce630f17b03d3ad02ad0bf978c844f3536d0edda6499dafce2b6f"},
{file = "requests-2.28.0.tar.gz", hash = "sha256:d568723a7ebd25875d8d1eaf5dfa068cd2fc8194b2e483d7b1f7c81918dbec6b"}, {file = "requests-2.28.0.tar.gz", hash = "sha256:d568723a7ebd25875d8d1eaf5dfa068cd2fc8194b2e483d7b1f7c81918dbec6b"},
] ]
requests-toolbelt = [
{file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"},
{file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"},
]
selenium = [ selenium = [
{file = "selenium-3.141.0-py2.py3-none-any.whl", hash = "sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c"}, {file = "selenium-3.141.0-py2.py3-none-any.whl", hash = "sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c"},
{file = "selenium-3.141.0.tar.gz", hash = "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d"}, {file = "selenium-3.141.0.tar.gz", hash = "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d"},
] ]
semver = [
{file = "semver-3.0.0.dev3-py3-none-any.whl", hash = "sha256:10651d0dacffdce7a2f0658e58894b138f52c2a080f7d69aa999810ebfc1f97d"},
{file = "semver-3.0.0.dev3.tar.gz", hash = "sha256:7175229bdcf96a6702b077e30226f041ce112057146ae81e84b51df08a8a75cf"},
]
six = [ six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
@ -1007,6 +1056,10 @@ toml = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
] ]
tomli = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
urllib3 = [ urllib3 = [
{file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"},
{file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"},
@ -1020,8 +1073,8 @@ virtualenv = [
{file = "virtualenv-20.14.1.tar.gz", hash = "sha256:ef589a79795589aada0c1c5b319486797c03b67ac3984c48c669c0e4f50df3a5"}, {file = "virtualenv-20.14.1.tar.gz", hash = "sha256:ef589a79795589aada0c1c5b319486797c03b67ac3984c48c669c0e4f50df3a5"},
] ]
whitenoise = [ whitenoise = [
{file = "whitenoise-5.3.0-py2.py3-none-any.whl", hash = "sha256:d963ef25639d1417e8a247be36e6aedd8c7c6f0a08adcb5a89146980a96b577c"}, {file = "whitenoise-6.2.0-py3-none-any.whl", hash = "sha256:8e9c600a5c18bd17655ef668ad55b5edf6c24ce9bdca5bf607649ca4b1e8e2c2"},
{file = "whitenoise-5.3.0.tar.gz", hash = "sha256:d234b871b52271ae7ed6d9da47ffe857c76568f11dd30e28e18c5869dbd11e12"}, {file = "whitenoise-6.2.0.tar.gz", hash = "sha256:8fa943c6d4cd9e27673b70c21a07b0aa120873901e099cd46cab40f7cc96d567"},
] ]
wrapt = [ wrapt = [
{file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"},

View file

@ -8,25 +8,26 @@ license = "MIT"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.10" python = "^3.10"
Django = "^3.2.4" Django = "^3.2.4"
django-bootstrap4 = "^3.0.1" django-bootstrap4 = "^22.1"
gunicorn = "^20.1.0" gunicorn = "^20.1.0"
psycopg2-binary = "^2.9.1" psycopg2-binary = "^2.9.1"
django-anymail = "^8.4" django-anymail = "^8.4"
whitenoise = "^5.2.0" whitenoise = "^6.2.0"
django-import-export = "^2.5.0" django-import-export = "^2.5.0"
beautifulsoup4 = "^4.9.3" beautifulsoup4 = "^4.9.3"
requests = "^2.25.1" requests = "^2.25.1"
django-environ-2 = "^2.1.0" django-environ = "^0.9.0"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
django-debug-toolbar = "^3.2.1"
pre-commit = "^2.13.0" pre-commit = "^2.13.0"
pytest = "^6.2.4" pytest = "^7.1"
pytest-django = "^4.4.0" pytest-django = "^4.4.0"
pytest-html = "^3.1.1" pytest-html = "^3.1.1"
model-bakery = "^1.3.2" model-bakery = "^1.3.2"
selenium = "^3.141.0" selenium = "^3.141.0"
vcrpy = "^4.1.1" vcrpy = "^4.1.1"
invoke = "^1.7.1"
poetry-deps-scanner = "^1.0.1"
[build-system] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=1.0.0"]

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View file

Before

Width:  |  Height:  |  Size: 813 B

After

Width:  |  Height:  |  Size: 813 B

View file

Before

Width:  |  Height:  |  Size: 409 B

After

Width:  |  Height:  |  Size: 409 B

View file

Before

Width:  |  Height:  |  Size: 454 B

After

Width:  |  Height:  |  Size: 454 B

View file

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -65,7 +65,7 @@
<footer class="bg-light py-3"> <footer class="bg-light py-3">
<div class="container-fluid"> <div class="container-fluid">
<span class="text-muted">Ce service est un logiciel libre sous licence MIT réalisé par <span class="text-muted">Ce service est un logiciel libre placé dans le domaine public réalisé par
Gabriel Augendre d'après des besoins exprimés par Sandrine Augendre. Le code source est disponible Gabriel Augendre d'après des besoins exprimés par Sandrine Augendre. Le code source est disponible
<a href="https://git.augendre.info/gaugendre/manuels-scolaires">à cette adresse</a>.</span> <a href="https://git.augendre.info/gaugendre/manuels-scolaires">à cette adresse</a>.</span>
</div> </div>

View file

@ -0,0 +1,7 @@
import pytest
from django.core.management import call_command
@pytest.fixture(autouse=True, scope="session")
def _collect_static() -> None:
call_command("collectstatic", "--no-input", "--clear")

View file

@ -48,10 +48,3 @@ urlpatterns = [
path("clear", clear_teacher_view, name="clear_teacher"), path("clear", clear_teacher_view, name="clear_teacher"),
path("isbn_api/<str:isbn>", isbn_api, name="isbn_api"), path("isbn_api/<str:isbn>", isbn_api, name="isbn_api"),
] ]
if settings.DEBUG:
import debug_toolbar
urlpatterns = [
path("__debug__/", include(debug_toolbar.urls)),
] + urlpatterns

View file

@ -1,63 +1,84 @@
""" """
Django settings for manuels_collection project. Django settings for checkout project.
Generated by 'django-admin startproject' using Django 2.0.1. Generated by 'django-admin startproject' using Django 3.1.
For more information on this file, see For more information on this file, see
https://docs.djangoproject.com/en/2.0/topics/settings/ https://docs.djangoproject.com/en/3.1/topics/settings/
For the full list of settings and their values, see For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.0/ref/settings/ https://docs.djangoproject.com/en/3.1/ref/settings/
""" """
import os import os
from pathlib import Path from pathlib import Path
import environ import environ
from django.contrib.messages import constants as messages
# Build paths inside the project like this: BASE_DIR / 'subdir'.
from django.contrib import messages
BASE_DIR = Path(__file__).resolve(strict=True).parent.parent BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
env = environ.Env( env = environ.Env(
SECRET_KEY=str, DEBUG=(bool, False),
DJANGO_ENV=(str, "prod"), SECRET_KEY=(str, "s#!83!8e$3s89m)r$1ghsgxbndf8=#^qt(_*o%xbq0j2t8#db5"),
CURRENT_IP=(str, "192.168.0.200"), ADMINS=(list, []),
HOST=(list, None), MAILGUN_API_KEY=(str, ""),
ADMIN_EMAIL=str, MAILGUN_SENDER_DOMAIN=(str, ""),
SERVER_EMAIL=str, SERVER_EMAIL=(str, ""),
AUTHORIZED_EMAILS=(list, []), AUTHORIZED_EMAILS=(list, []),
LIBRARIAN_EMAILS=(list, []), LIBRARIAN_EMAILS=(list, []),
MAILGUN_ACCESS_KEY=(str, ""), HOSTS=(list, []),
MAILGUN_SERVER_NAME=(str, ""), TIME_ZONE=(str, "Europe/Paris"),
LANGUAGE_CODE=(str, "fr-fr"),
) )
env_file = os.getenv("ENV_FILE", None) env_file = os.getenv("ENV_FILE", None)
if env_file: if env_file:
environ.Env.read_env(env_file) environ.Env.read_env(env_file)
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env("SECRET_KEY") SECRET_KEY = env("SECRET_KEY")
# SECURITY WARNING: don't run with debug turned on in production! admins = env("ADMINS")
DEBUG = env("DJANGO_ENV") == "dev" if admins:
ADMINS = list(map(lambda x: tuple(x.split("|")), admins))
ALLOWED_HOSTS = ["web", "127.0.0.1"] DEFAULT_FROM_EMAIL = env("SERVER_EMAIL")
if DEBUG:
ALLOWED_HOSTS.extend(["localhost", env("CURRENT_IP")])
ALLOWED_HOSTS.extend(env("HOST"))
ADMINS = [
("Gabriel", env("ADMIN_EMAIL")),
]
SERVER_EMAIL = env("SERVER_EMAIL") SERVER_EMAIL = env("SERVER_EMAIL")
EMAIL_SUBJECT_PREFIX = "[Manuels] " EMAIL_SUBJECT_PREFIX = "[Manuels] "
EMAIL_TIMEOUT = 30
ANYMAIL = {
"MAILGUN_API_KEY": env("MAILGUN_API_KEY"),
"MAILGUN_SENDER_DOMAIN": env("MAILGUN_SENDER_DOMAIN"),
"MAILGUN_API_URL": "https://api.mailgun.net/v3",
}
EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend"
AUTHORIZED_EMAILS = env("AUTHORIZED_EMAILS") AUTHORIZED_EMAILS = env("AUTHORIZED_EMAILS")
LIBRARIAN_EMAILS = env("LIBRARIAN_EMAILS") LIBRARIAN_EMAILS = env("LIBRARIAN_EMAILS")
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env("DEBUG")
ALLOWED_HOSTS = ["localhost"] # Required for healthcheck
if DEBUG:
ALLOWED_HOSTS.append("127.0.0.1")
ALLOWED_HOSTS.extend(env("HOSTS"))
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SESSION_COOKIE_SECURE = not DEBUG
CSRF_COOKIE_SECURE = not DEBUG
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
"whitenoise.runserver_nostatic",
"django.contrib.admin", "django.contrib.admin",
"django.contrib.auth", "django.contrib.auth",
"django.contrib.contenttypes", "django.contrib.contenttypes",
@ -70,14 +91,10 @@ INSTALLED_APPS = [
"import_export", "import_export",
] ]
if DEBUG:
INSTALLED_APPS += [
"debug_toolbar",
]
MIDDLEWARE = [ MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware", "django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware",
"django.middleware.gzip.GZipMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware", "django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware", "django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware", "django.middleware.csrf.CsrfViewMiddleware",
@ -86,11 +103,6 @@ MIDDLEWARE = [
"django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",
] ]
if DEBUG:
MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware")
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
ROOT_URLCONF = "manuels_collection.urls" ROOT_URLCONF = "manuels_collection.urls"
TEMPLATES = [ TEMPLATES = [
@ -104,7 +116,6 @@ TEMPLATES = [
"django.template.context_processors.request", "django.template.context_processors.request",
"django.contrib.auth.context_processors.auth", "django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages", "django.contrib.messages.context_processors.messages",
"manuels.context_processors.authorized_mails",
], ],
}, },
}, },
@ -112,46 +123,48 @@ TEMPLATES = [
WSGI_APPLICATION = "manuels_collection.wsgi.application" WSGI_APPLICATION = "manuels_collection.wsgi.application"
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.db.DatabaseCache",
"LOCATION": "manuels_cache",
}
}
# Database # Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases # https://docs.djangoproject.com/en/3.1/ref/settings/#databases
default_db_path = BASE_DIR / "db.sqlite3" default_db_path = BASE_DIR / "db.sqlite3"
DATABASES = { DATABASES = {
"default": env.db(default=f"sqlite:///{default_db_path}"), "default": env.db(default=f"sqlite:///{default_db_path}"),
} }
INTERNAL_IPS = [
"127.0.0.1",
"localhost",
]
# Password validation # Password validation
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
}, },
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
] ]
INTERNAL_IPS = [
"127.0.0.1",
]
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/ # https://docs.djangoproject.com/en/3.1/topics/i18n/
LANGUAGE_CODE = "fr-fr" LANGUAGE_CODE = env("LANGUAGE_CODE")
TIME_ZONE = "Europe/Paris" TIME_ZONE = env("TIME_ZONE")
USE_I18N = True USE_I18N = True
USE_L10N = True
USE_TZ = True USE_TZ = True
# Logging # Logging
@ -173,29 +186,38 @@ LOGGING = {
} }
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/ # https://docs.djangoproject.com/en/3.1/howto/static-files/
STATIC_URL = "/static/" STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles" STATIC_ROOT = BASE_DIR.parent / "staticfiles"
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR.parent / "media"
LOGIN_REDIRECT_URL = "rooms-list" LOGIN_REDIRECT_URL = "rooms-list"
ANYMAIL = { LOGIN_URL = "admin:login"
"MAILGUN_API_KEY": env("MAILGUN_ACCESS_KEY"),
"MAILGUN_SENDER_DOMAIN": env("MAILGUN_SERVER_NAME"),
}
EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend" SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_HSTS_SECONDS = 63072000
MESSAGE_TAGS = { SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
messages.ERROR: "danger",
}
CACHES = { # CSP
"default": { CSP_DEFAULT_SRC = ("'none'",)
"BACKEND": "django.core.cache.backends.db.DatabaseCache", CSP_IMG_SRC = ("'self'", "data:")
"LOCATION": "manuels_cache", CSP_SCRIPT_SRC = ("'self'", "'unsafe-inline'")
} CSP_CONNECT_SRC = ("'self'",)
} CSP_STYLE_SRC = ("'self'", "'unsafe-inline'")
CSP_MANIFEST_SRC = ("'self'",)
CSP_FONT_SRC = ("'self'",)
CSP_BASE_URI = ("'none'",)
CSP_FORM_ACTION = ("'self'",)
DEFAULT_AUTO_FIELD = "django.db.models.AutoField" DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
MESSAGE_TAGS = {messages.ERROR: "danger"}

89
tasks.py Normal file
View file

@ -0,0 +1,89 @@
"""
Invoke management tasks for the project.
The current implementation with type annotations is not compatible
with invoke 1.6.0 and requires manual patching.
See https://github.com/pyinvoke/invoke/pull/458/files
"""
import time
from pathlib import Path
import requests
from invoke import Context, task
BASE_DIR = Path(__file__).parent.resolve(strict=True)
SRC_DIR = BASE_DIR / "src"
COMPOSE_BUILD_FILE = BASE_DIR / "docker-compose-build.yaml"
COMPOSE_BUILD_ENV = {"COMPOSE_FILE": COMPOSE_BUILD_FILE}
@task
def test(ctx: Context) -> None:
with ctx.cd(SRC_DIR):
ctx.run("pytest", pty=True, echo=True)
@task
def test_cov(ctx: Context) -> None:
with ctx.cd(SRC_DIR):
ctx.run(
"pytest --cov=. --cov-branch --cov-report term-missing:skip-covered",
pty=True,
echo=True,
env={"COVERAGE_FILE": BASE_DIR / ".coverage"},
)
@task
def pre_commit(ctx: Context) -> None:
with ctx.cd(BASE_DIR):
ctx.run("pre-commit run --all-files", pty=True)
@task(pre=[pre_commit, test_cov])
def check(ctx: Context) -> None:
pass
@task
def build(ctx: Context) -> None:
with ctx.cd(BASE_DIR):
ctx.run(
"docker-compose build django", pty=True, echo=True, env=COMPOSE_BUILD_ENV
)
@task
def publish(ctx: Context) -> None:
with ctx.cd(BASE_DIR):
ctx.run(
"docker-compose push django", pty=True, echo=True, env=COMPOSE_BUILD_ENV
)
@task
def deploy(ctx: Context) -> None:
ctx.run("ssh ubuntu /home/gaugendre/checkout/update", pty=True, echo=True)
@task
def check_alive(ctx: Context) -> None:
exception = None
for _ in range(5):
try:
res = requests.get("https://manuels.augendre.info")
res.raise_for_status()
print("Server is up & running")
return
except requests.exceptions.HTTPError as e:
time.sleep(2)
exception = e
raise RuntimeError("Failed to reach the server") from exception
@task(pre=[check, build, publish, deploy], post=[check_alive])
def beam(ctx: Context) -> None:
pass