mirror of
https://github.com/Crocmagnon/checkout.git
synced 2024-11-24 00:58:04 +01:00
Initial commit
This commit is contained in:
commit
1288a30308
32 changed files with 2249 additions and 0 deletions
9
.dockerignore
Normal file
9
.dockerignore
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
db/
|
||||||
|
media/
|
||||||
|
src/media/
|
||||||
|
staticfiles/
|
||||||
|
src/staticfiles/
|
||||||
|
__pycache__/
|
||||||
|
**/__pycache__/
|
||||||
|
.pytest_cache/
|
||||||
|
.idea/
|
51
.eslintrc
Normal file
51
.eslintrc
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es6": true,
|
||||||
|
"jquery": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended"
|
||||||
|
],
|
||||||
|
"ignorePatterns": ["dist/", "node_modules/"],
|
||||||
|
"rules": {
|
||||||
|
"block-scoped-var": "error",
|
||||||
|
"consistent-return": "error",
|
||||||
|
"curly": "error",
|
||||||
|
"default-case": "error",
|
||||||
|
"default-param-last": ["error"],
|
||||||
|
"dot-notation": "error",
|
||||||
|
"eqeqeq": "error",
|
||||||
|
"guard-for-in": "error",
|
||||||
|
"max-classes-per-file": "error",
|
||||||
|
"no-alert": "error",
|
||||||
|
"no-caller": "error",
|
||||||
|
"no-else-return": "error",
|
||||||
|
"no-empty-function": "error",
|
||||||
|
"no-floating-decimal": "error",
|
||||||
|
"no-implicit-coercion": "error",
|
||||||
|
"no-multi-spaces": "error",
|
||||||
|
"no-multi-str": "error",
|
||||||
|
"no-param-reassign": "error",
|
||||||
|
"no-return-assign": "error",
|
||||||
|
"no-return-await": "error",
|
||||||
|
"no-self-compare": "error",
|
||||||
|
"no-throw-literal": "error",
|
||||||
|
"no-useless-concat": "error",
|
||||||
|
"radix": ["error", "as-needed"],
|
||||||
|
"require-await": "error",
|
||||||
|
"yoda": "error",
|
||||||
|
"no-shadow": "off",
|
||||||
|
"prefer-destructuring": ["error", { "array": false, "object": true }],
|
||||||
|
"padding-line-between-statements": [
|
||||||
|
"error",
|
||||||
|
{ "blankLine": "always", "prev": "import", "next": "export" },
|
||||||
|
{ "blankLine": "always", "prev": "export", "next": "export" },
|
||||||
|
{ "blankLine": "always", "prev": "*", "next": "return" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 6,
|
||||||
|
"sourceType": "module"
|
||||||
|
}
|
||||||
|
}
|
278
.gitignore
vendored
Normal file
278
.gitignore
vendored
Normal file
|
@ -0,0 +1,278 @@
|
||||||
|
|
||||||
|
# Created by https://www.toptal.com/developers/gitignore/api/osx,pycharm,python
|
||||||
|
# Edit at https://www.toptal.com/developers/gitignore?templates=osx,pycharm,python
|
||||||
|
|
||||||
|
### OSX ###
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
### PyCharm ###
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
### PyCharm Patch ###
|
||||||
|
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
||||||
|
|
||||||
|
# *.iml
|
||||||
|
# modules.xml
|
||||||
|
# .idea/misc.xml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# Sonarlint plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/7973-sonarlint
|
||||||
|
.idea/**/sonarlint/
|
||||||
|
|
||||||
|
# SonarQube Plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
|
||||||
|
.idea/**/sonarIssues.xml
|
||||||
|
|
||||||
|
# Markdown Navigator plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
|
||||||
|
.idea/**/markdown-navigator.xml
|
||||||
|
.idea/**/markdown-navigator-enh.xml
|
||||||
|
.idea/**/markdown-navigator/
|
||||||
|
|
||||||
|
# Cache file creation bug
|
||||||
|
# See https://youtrack.jetbrains.com/issue/JBR-2257
|
||||||
|
.idea/$CACHE_FILE$
|
||||||
|
|
||||||
|
# CodeStream plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/12206-codestream
|
||||||
|
.idea/codestream.xml
|
||||||
|
|
||||||
|
### Python ###
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
pip-wheel-metadata/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
pytestdebug.log
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db/
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
doc/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.envrc
|
||||||
|
*.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/osx,pycharm,python
|
||||||
|
|
||||||
|
.idea
|
||||||
|
staticfiles/
|
||||||
|
media/
|
||||||
|
db.sqlite3
|
71
.pre-commit-config.yaml
Normal file
71
.pre-commit-config.yaml
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
exclude: (\.min\.(js|css)(\.map)?$|/vendor/)
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.2.0
|
||||||
|
hooks:
|
||||||
|
- id: check-ast
|
||||||
|
- id: check-json
|
||||||
|
- id: check-toml
|
||||||
|
- id: check-xml
|
||||||
|
- id: check-yaml
|
||||||
|
args: [--allow-multiple-documents]
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: check-merge-conflict
|
||||||
|
- id: debug-statements
|
||||||
|
- id: detect-private-key
|
||||||
|
- id: pretty-format-json
|
||||||
|
args:
|
||||||
|
- --autofix
|
||||||
|
- --no-sort-keys
|
||||||
|
- id: trailing-whitespace
|
||||||
|
args:
|
||||||
|
- --markdown-linebreak-ext=md
|
||||||
|
- repo: https://github.com/timothycrosley/isort
|
||||||
|
rev: 5.10.1
|
||||||
|
hooks:
|
||||||
|
- id: isort
|
||||||
|
- repo: https://github.com/psf/black
|
||||||
|
rev: 22.3.0
|
||||||
|
hooks:
|
||||||
|
- id: black
|
||||||
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
|
rev: v2.32.0
|
||||||
|
hooks:
|
||||||
|
- id: pyupgrade
|
||||||
|
args:
|
||||||
|
- --py310-plus
|
||||||
|
- repo: https://github.com/adamchainz/django-upgrade
|
||||||
|
rev: 1.5.0
|
||||||
|
hooks:
|
||||||
|
- id: django-upgrade
|
||||||
|
args: [--target-version, "4.0"]
|
||||||
|
- repo: https://github.com/rtts/djhtml
|
||||||
|
rev: v1.5.0
|
||||||
|
hooks:
|
||||||
|
- id: djhtml
|
||||||
|
- repo: https://github.com/flakeheaven/flakeheaven
|
||||||
|
rev: 0.11.0
|
||||||
|
hooks:
|
||||||
|
- id: flakeheaven
|
||||||
|
additional_dependencies:
|
||||||
|
- flake8-annotations-complexity
|
||||||
|
- flake8-builtins
|
||||||
|
- flake8-bugbear
|
||||||
|
- flake8-comprehensions
|
||||||
|
- flake8-eradicate
|
||||||
|
- flake8-noqa
|
||||||
|
- flake8-pytest-style
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||||
|
rev: v2.6.2
|
||||||
|
hooks:
|
||||||
|
- id: prettier
|
||||||
|
types_or: [javascript, css]
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-eslint
|
||||||
|
rev: v8.14.0
|
||||||
|
hooks:
|
||||||
|
- id: eslint
|
||||||
|
args: [--fix]
|
||||||
|
types_or: [javascript, css]
|
||||||
|
additional_dependencies:
|
||||||
|
- eslint@^7.29.0
|
||||||
|
- eslint-config-prettier@^8.3.0
|
5
.prettierrc
Normal file
5
.prettierrc
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"tabWidth": 4,
|
||||||
|
"printWidth": 120,
|
||||||
|
"endOfLine": "auto"
|
||||||
|
}
|
57
Dockerfile
Normal file
57
Dockerfile
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
## Build venv
|
||||||
|
FROM python:3.10.4-bullseye AS venv
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
COPY pyproject.toml poetry.lock ./
|
||||||
|
|
||||||
|
RUN python -m venv --copies /app/venv \
|
||||||
|
&& . /app/venv/bin/activate \
|
||||||
|
&& poetry install $POETRY_OPTIONS
|
||||||
|
|
||||||
|
ENV PATH /app/venv/bin:$PATH
|
||||||
|
COPY src ./src/
|
||||||
|
RUN python ./src/manage.py collectstatic --no-input
|
||||||
|
|
||||||
|
## Get git versions
|
||||||
|
FROM alpine/git AS git
|
||||||
|
ADD . /app
|
||||||
|
WORKDIR /app
|
||||||
|
RUN git rev-parse HEAD | tee /version
|
||||||
|
|
||||||
|
|
||||||
|
## Beginning of runtime image
|
||||||
|
FROM python:3.10.4-slim-bullseye as prod
|
||||||
|
ENV TZ "Europe/Paris"
|
||||||
|
RUN mkdir -p /app/db
|
||||||
|
|
||||||
|
COPY --from=venv /app/venv /app/venv/
|
||||||
|
ENV PATH /app/venv/bin:$PATH
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY LICENSE pyproject.toml ./
|
||||||
|
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"]
|
24
LICENSE
Normal file
24
LICENSE
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more information, please refer to <https://unlicense.org>
|
16
README.md
Normal file
16
README.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Blog
|
||||||
|
|
||||||
|
Simple blog management system.
|
||||||
|
|
||||||
|
The authoritative source for this repo is at https://git.augendre.info/gaugendre/blog
|
||||||
|
|
||||||
|
Hosted at https://gabnotes.org
|
||||||
|
|
||||||
|
## Development
|
||||||
|
```shell
|
||||||
|
inv test-cov
|
||||||
|
inv beam
|
||||||
|
```
|
||||||
|
|
||||||
|
# Reuse
|
||||||
|
If you do reuse my work, please consider linking back to this repository 🙂
|
12
docker-compose-build.yaml
Normal file
12
docker-compose-build.yaml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
version: '2.4'
|
||||||
|
services:
|
||||||
|
django:
|
||||||
|
extends:
|
||||||
|
file: docker-compose.yaml
|
||||||
|
service: django
|
||||||
|
image: crocmagnon/cheese-factory:latest
|
||||||
|
platform: linux/amd64
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
staticfiles: {}
|
||||||
|
media: {}
|
23
docker-compose.yaml
Normal file
23
docker-compose.yaml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
version: '2.4'
|
||||||
|
services:
|
||||||
|
django:
|
||||||
|
image: crocmagnon/cheese-factory:dev
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
args:
|
||||||
|
POETRY_OPTIONS: "--no-dev"
|
||||||
|
env_file:
|
||||||
|
- envs/docker-local.env
|
||||||
|
volumes:
|
||||||
|
- ./db:/app/db
|
||||||
|
- staticfiles:/app/staticfiles
|
||||||
|
- media:/app/media
|
||||||
|
restart: on-failure
|
||||||
|
init: true
|
||||||
|
tty: true
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
staticfiles: {}
|
||||||
|
media: {}
|
4
docker/run.sh
Executable file
4
docker/run.sh
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -eux
|
||||||
|
python manage.py migrate --noinput
|
||||||
|
gunicorn checkout.wsgi -b 0.0.0.0:8000 --log-file -
|
6
envs/local.env.dist
Normal file
6
envs/local.env.dist
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
PYTHONUNBUFFERED=1
|
||||||
|
DJANGO_SETTINGS_MODULE=checkout.settings
|
||||||
|
ADMINS="Gabriel Augendre|gabriel@augendre.info"
|
||||||
|
MAILGUN_API_KEY=API_KEY_HERE
|
||||||
|
MAILGUN_SENDER_DOMAIN=mg.gabnotes.org
|
||||||
|
HOSTS=192.168.0.1,192.168.0.2
|
1065
poetry.lock
generated
Normal file
1065
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
67
pyproject.toml
Normal file
67
pyproject.toml
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
[tool.poetry]
|
||||||
|
name = "checkout"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = ""
|
||||||
|
authors = ["Gabriel Augendre <gabriel@augendre.info>"]
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.10"
|
||||||
|
django = "^4.0"
|
||||||
|
django-anymail = {version = "^8.4", extras = ["mailgun"]}
|
||||||
|
django-cleanup = "^5.0"
|
||||||
|
whitenoise = {extras = ["brotli"], version = "^6.0"}
|
||||||
|
django-csp = "^3.7"
|
||||||
|
django-environ = "^0.8.1"
|
||||||
|
requests = "^2.27.1"
|
||||||
|
django-extensions = "^3.1.5"
|
||||||
|
bpython = "^0.22.1"
|
||||||
|
gunicorn = "^20.1.0"
|
||||||
|
|
||||||
|
[tool.poetry.dev-dependencies]
|
||||||
|
pre-commit = "^2.7"
|
||||||
|
pytest = "^6.0"
|
||||||
|
pytest-django = "^4.5"
|
||||||
|
model-bakery = "^1.1"
|
||||||
|
pytest-cov = "^3.0"
|
||||||
|
poetry-deps-scanner = "^1.0.1"
|
||||||
|
invoke = "^1.7.0"
|
||||||
|
|
||||||
|
[tool.black]
|
||||||
|
target-version = ['py310']
|
||||||
|
|
||||||
|
[tool.isort]
|
||||||
|
profile = "black"
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
addopts = "--color=yes"
|
||||||
|
minversion = "6.0"
|
||||||
|
DJANGO_SETTINGS_MODULE = "checkout.settings"
|
||||||
|
testpaths = [
|
||||||
|
"src",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.flakeheaven]
|
||||||
|
max_complexity = 10
|
||||||
|
format = "grouped"
|
||||||
|
|
||||||
|
[tool.flakeheaven.plugins]
|
||||||
|
"flake8-*" = [
|
||||||
|
"+*",
|
||||||
|
# long lines
|
||||||
|
"-E501",
|
||||||
|
# conflict with black on PEP8 interpretation
|
||||||
|
"-E203",
|
||||||
|
# deprecated rule: https://www.flake8rules.com/rules/W503.html
|
||||||
|
"-W503",
|
||||||
|
]
|
||||||
|
flake8-quotes = ["+*", "-Q000"] # found double quotes, conflict with black
|
||||||
|
flake8-commas = ["+*", "-C812"] # missing trailing comma, conflict with black
|
||||||
|
flake8-docstrings = ["+*", "-D1??"] # missing docstring
|
||||||
|
flake8-rst-docstrings = ["-*"]
|
||||||
|
|
||||||
|
[tool.flakeheaven.exceptions."**/tests/*"]
|
||||||
|
flake8-bandit = ["+*", "-S101"] # Use of assert detected.
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core>=1.0.0"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
0
src/checkout/__init__.py
Normal file
0
src/checkout/__init__.py
Normal file
16
src/checkout/asgi.py
Normal file
16
src/checkout/asgi.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
"""
|
||||||
|
ASGI config for checkout project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "checkout.settings")
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
205
src/checkout/settings.py
Normal file
205
src/checkout/settings.py
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
"""
|
||||||
|
Django settings for checkout project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 3.1.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/ref/settings/
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import environ
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
|
||||||
|
|
||||||
|
env = environ.Env(
|
||||||
|
DEBUG=(bool, False),
|
||||||
|
SECRET_KEY=(str, "s#!83!8e$3s89m)r$1ghsgxbndf8=#^qt(_*o%xbq0j2t8#db5"),
|
||||||
|
ADMINS=(list, []),
|
||||||
|
MAILGUN_API_KEY=(str, ""),
|
||||||
|
MAILGUN_SENDER_DOMAIN=(str, ""),
|
||||||
|
HOSTS=(list, []),
|
||||||
|
DB_BASE_DIR=(Path, BASE_DIR),
|
||||||
|
)
|
||||||
|
|
||||||
|
env_file = os.getenv("ENV_FILE", None)
|
||||||
|
|
||||||
|
if 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!
|
||||||
|
SECRET_KEY = env("SECRET_KEY")
|
||||||
|
|
||||||
|
admins = env("ADMINS")
|
||||||
|
if admins:
|
||||||
|
ADMINS = list(map(lambda x: tuple(x.split("|")), admins))
|
||||||
|
|
||||||
|
DEFAULT_FROM_EMAIL = "Gab's Notes <checkout@mg.gabnotes.org>"
|
||||||
|
SERVER_EMAIL = "Gab's Notes <checkout@mg.gabnotes.org>"
|
||||||
|
EMAIL_SUBJECT_PREFIX = "[Cheese checkout] "
|
||||||
|
EMAIL_TIMEOUT = 30
|
||||||
|
|
||||||
|
ANYMAIL = {
|
||||||
|
"MAILGUN_API_KEY": env("MAILGUN_API_KEY"),
|
||||||
|
"MAILGUN_SENDER_DOMAIN": env("MAILGUN_SENDER_DOMAIN"),
|
||||||
|
"MAILGUN_API_URL": "https://api.eu.mailgun.net/v3",
|
||||||
|
}
|
||||||
|
EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend"
|
||||||
|
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"whitenoise.runserver_nostatic",
|
||||||
|
"django.contrib.admin",
|
||||||
|
"django.contrib.auth",
|
||||||
|
"django.contrib.contenttypes",
|
||||||
|
"django.contrib.sessions",
|
||||||
|
"django.contrib.messages",
|
||||||
|
"django.contrib.staticfiles",
|
||||||
|
"anymail",
|
||||||
|
"django_cleanup.apps.CleanupConfig",
|
||||||
|
"common",
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
"django.middleware.security.SecurityMiddleware",
|
||||||
|
"whitenoise.middleware.WhiteNoiseMiddleware",
|
||||||
|
"django.middleware.gzip.GZipMiddleware",
|
||||||
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"django.middleware.common.CommonMiddleware",
|
||||||
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
|
"csp.middleware.CSPMiddleware",
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = "checkout.urls"
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
|
"DIRS": ["checkout/templates"],
|
||||||
|
"APP_DIRS": True,
|
||||||
|
"OPTIONS": {
|
||||||
|
"context_processors": [
|
||||||
|
"django.template.context_processors.debug",
|
||||||
|
"django.template.context_processors.request",
|
||||||
|
"django.contrib.auth.context_processors.auth",
|
||||||
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = "checkout.wsgi.application"
|
||||||
|
|
||||||
|
CACHES = {
|
||||||
|
"default": {
|
||||||
|
"BACKEND": "django.core.cache.backends.db.DatabaseCache",
|
||||||
|
"LOCATION": "cache",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
|
||||||
|
|
||||||
|
DB_BASE_DIR = env("DB_BASE_DIR")
|
||||||
|
if not DB_BASE_DIR:
|
||||||
|
# Protect against empty strings
|
||||||
|
DB_BASE_DIR = BASE_DIR
|
||||||
|
else:
|
||||||
|
DB_BASE_DIR = DB_BASE_DIR.resolve(strict=True)
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
|
"NAME": DB_BASE_DIR / "db.sqlite3",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
INTERNAL_IPS = [
|
||||||
|
"127.0.0.1",
|
||||||
|
"localhost",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"
|
||||||
|
},
|
||||||
|
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
|
||||||
|
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
|
||||||
|
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/3.1/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = "en-us"
|
||||||
|
|
||||||
|
TIME_ZONE = "Europe/Paris"
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/3.1/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = "/static/"
|
||||||
|
STATIC_ROOT = BASE_DIR.parent / "staticfiles"
|
||||||
|
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
||||||
|
|
||||||
|
MEDIA_URL = "/media/"
|
||||||
|
MEDIA_ROOT = BASE_DIR.parent / "media"
|
||||||
|
|
||||||
|
AUTH_USER_MODEL = "common.User"
|
||||||
|
|
||||||
|
LOGIN_URL = "admin:login"
|
||||||
|
|
||||||
|
SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"
|
||||||
|
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
||||||
|
SECURE_HSTS_PRELOAD = True
|
||||||
|
SECURE_HSTS_SECONDS = 63072000
|
||||||
|
|
||||||
|
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||||
|
|
||||||
|
# CSP
|
||||||
|
CSP_DEFAULT_SRC = ("'none'",)
|
||||||
|
CSP_IMG_SRC = ("'self'",)
|
||||||
|
CSP_SCRIPT_SRC = ("'self'",)
|
||||||
|
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"
|
0
src/checkout/tests/__init__.py
Normal file
0
src/checkout/tests/__init__.py
Normal file
9
src/checkout/tests/test_robots.py
Normal file
9
src/checkout/tests/test_robots.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
from django.test.client import Client
|
||||||
|
|
||||||
|
|
||||||
|
def test_robots_txt(client: Client) -> None:
|
||||||
|
res = client.get("/robots.txt")
|
||||||
|
assert res.status_code == 200
|
||||||
|
assert res["Content-Type"] == "text/plain"
|
||||||
|
content = res.content.decode("utf-8")
|
||||||
|
assert "User-Agent" in content
|
34
src/checkout/urls.py
Normal file
34
src/checkout/urls.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
"""checkout URL Configuration
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/3.1/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('checkout/', include('checkout.urls'))
|
||||||
|
"""
|
||||||
|
from django.conf.urls.static import static
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
from checkout import settings
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path(
|
||||||
|
"robots.txt",
|
||||||
|
TemplateView.as_view(
|
||||||
|
template_name="common/robots.txt", content_type="text/plain"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
path("admin/", admin.site.urls),
|
||||||
|
]
|
||||||
|
|
||||||
|
if settings.DEBUG:
|
||||||
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
16
src/checkout/wsgi.py
Normal file
16
src/checkout/wsgi.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
"""
|
||||||
|
WSGI config for checkout 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/3.1/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "checkout.settings")
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
0
src/common/__init__.py
Normal file
0
src/common/__init__.py
Normal file
3
src/common/admin.py
Normal file
3
src/common/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
6
src/common/apps.py
Normal file
6
src/common/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class CommonConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "common"
|
132
src/common/migrations/0001_initial.py
Normal file
132
src/common/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
# Generated by Django 4.0.4 on 2022-04-24 13:35
|
||||||
|
|
||||||
|
import django.contrib.auth.models
|
||||||
|
import django.contrib.auth.validators
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("auth", "0012_alter_user_first_name_max_length"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="User",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("password", models.CharField(max_length=128, verbose_name="password")),
|
||||||
|
(
|
||||||
|
"last_login",
|
||||||
|
models.DateTimeField(
|
||||||
|
blank=True, null=True, verbose_name="last login"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"is_superuser",
|
||||||
|
models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="Designates that this user has all permissions without explicitly assigning them.",
|
||||||
|
verbose_name="superuser status",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"username",
|
||||||
|
models.CharField(
|
||||||
|
error_messages={
|
||||||
|
"unique": "A user with that username already exists."
|
||||||
|
},
|
||||||
|
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
|
||||||
|
max_length=150,
|
||||||
|
unique=True,
|
||||||
|
validators=[
|
||||||
|
django.contrib.auth.validators.UnicodeUsernameValidator()
|
||||||
|
],
|
||||||
|
verbose_name="username",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"first_name",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=150, verbose_name="first name"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"last_name",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=150, verbose_name="last name"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"email",
|
||||||
|
models.EmailField(
|
||||||
|
blank=True, max_length=254, verbose_name="email address"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"is_staff",
|
||||||
|
models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="Designates whether the user can log into this admin site.",
|
||||||
|
verbose_name="staff status",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"is_active",
|
||||||
|
models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
|
||||||
|
verbose_name="active",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"date_joined",
|
||||||
|
models.DateTimeField(
|
||||||
|
default=django.utils.timezone.now, verbose_name="date joined"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"groups",
|
||||||
|
models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
|
||||||
|
related_name="user_set",
|
||||||
|
related_query_name="user",
|
||||||
|
to="auth.group",
|
||||||
|
verbose_name="groups",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user_permissions",
|
||||||
|
models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Specific permissions for this user.",
|
||||||
|
related_name="user_set",
|
||||||
|
related_query_name="user",
|
||||||
|
to="auth.permission",
|
||||||
|
verbose_name="user permissions",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "user",
|
||||||
|
"verbose_name_plural": "users",
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
managers=[
|
||||||
|
("objects", django.contrib.auth.models.UserManager()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
0
src/common/migrations/__init__.py
Normal file
0
src/common/migrations/__init__.py
Normal file
5
src/common/models.py
Normal file
5
src/common/models.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from django.contrib.auth.models import AbstractUser
|
||||||
|
|
||||||
|
|
||||||
|
class User(AbstractUser):
|
||||||
|
pass
|
2
src/common/templates/common/robots.txt
Normal file
2
src/common/templates/common/robots.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
User-Agent: *
|
||||||
|
Disallow: /admin/
|
3
src/common/tests.py
Normal file
3
src/common/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
3
src/common/views.py
Normal file
3
src/common/views.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
22
src/manage.py
Executable file
22
src/manage.py
Executable file
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""Run administrative tasks.""" # noqa: DAR401
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "checkout.settings")
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
105
tasks.py
Normal file
105
tasks.py
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
def mypy(ctx: Context) -> None:
|
||||||
|
with ctx.cd(BASE_DIR):
|
||||||
|
ctx.run("pre-commit run --all-files mypy", 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://gabnotes.org")
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
@task
|
||||||
|
def download_db(ctx: Context) -> None:
|
||||||
|
with ctx.cd(BASE_DIR):
|
||||||
|
ctx.run("scp ubuntu:/home/gaugendre/checkout/db/db.sqlite3 ./db/db.sqlite3")
|
||||||
|
ctx.run("rm -rf src/media/")
|
||||||
|
ctx.run("scp -r ubuntu:/home/gaugendre/checkout/media/ ./src/media")
|
||||||
|
with ctx.cd(SRC_DIR):
|
||||||
|
ctx.run("./manage.py changepassword gaugendre", pty=True)
|
Loading…
Reference in a new issue