mirror of
https://github.com/Crocmagnon/checkout.git
synced 2024-11-23 08:38:02 +01:00
Modernize app
This commit is contained in:
parent
37129d9405
commit
114ae437bc
40 changed files with 613 additions and 247 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -276,3 +276,4 @@ dmypy.json
|
|||
staticfiles/
|
||||
media/
|
||||
db.sqlite3
|
||||
.direnv
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
exclude: (\.min\.(js|css)(\.map)?$|/vendor/)
|
||||
exclude: \.min\.(js|css)(\.map)?$|^\.idea/|/vendor/
|
||||
ci:
|
||||
skip: [pip-compile]
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.3.0
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: check-ast
|
||||
- id: check-json
|
||||
|
@ -20,52 +23,57 @@ repos:
|
|||
- 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.8.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.38.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args:
|
||||
- --py310-plus
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-shebang-scripts-are-executable
|
||||
- repo: https://github.com/adamchainz/django-upgrade
|
||||
rev: 1.10.0
|
||||
rev: 1.13.0
|
||||
hooks:
|
||||
- id: django-upgrade
|
||||
args: [--target-version, "4.1"]
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
args: [--target-version, py311]
|
||||
- repo: https://github.com/rtts/djhtml
|
||||
rev: v1.5.2
|
||||
rev: 3.0.6
|
||||
hooks:
|
||||
- id: djhtml
|
||||
- repo: https://github.com/flakeheaven/flakeheaven
|
||||
rev: 3.0.0
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.259
|
||||
hooks:
|
||||
- id: flakeheaven
|
||||
additional_dependencies:
|
||||
- flake8-annotations-complexity
|
||||
- flake8-builtins
|
||||
- flake8-bugbear
|
||||
- flake8-comprehensions
|
||||
- flake8-eradicate
|
||||
- flake8-noqa
|
||||
- flake8-pytest-style
|
||||
- id: ruff
|
||||
args: [--fix]
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v3.0.0-alpha.0
|
||||
rev: v3.0.0-alpha.6
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or: [javascript, css]
|
||||
- repo: https://github.com/pre-commit/mirrors-eslint
|
||||
rev: v8.24.0
|
||||
rev: v8.36.0
|
||||
hooks:
|
||||
- id: eslint
|
||||
args: [--fix]
|
||||
types_or: [javascript, css]
|
||||
additional_dependencies:
|
||||
- eslint@^7.29.0
|
||||
- eslint-config-prettier@^8.3.0
|
||||
- eslint@8.36.0
|
||||
- eslint-config-prettier@8.5.0
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: 0.9.2
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
- repo: https://github.com/jazzband/pip-tools
|
||||
rev: 6.12.3
|
||||
hooks:
|
||||
- id: pip-compile
|
||||
name: pip-compile requirements.txt
|
||||
args: [-q, --allow-unsafe, --resolver=backtracking, requirements.in]
|
||||
files: ^requirements\.(in|txt)$
|
||||
- id: pip-compile
|
||||
name: pip-compile constraints.txt
|
||||
args: [-q, --allow-unsafe, --resolver=backtracking, --strip-extras, --output-file=constraints.txt, requirements.in]
|
||||
files: ^requirements\.in|constraints\.txt$
|
||||
- id: pip-compile
|
||||
name: pip-compile requirements-dev.txt
|
||||
args: [-q, --allow-unsafe, --resolver=backtracking, requirements-dev.in]
|
||||
files: ^requirements-dev\.(in|txt)$
|
||||
|
|
1
.tool-versions
Normal file
1
.tool-versions
Normal file
|
@ -0,0 +1 @@
|
|||
python 3.11.2
|
90
constraints.txt
Normal file
90
constraints.txt
Normal file
|
@ -0,0 +1,90 @@
|
|||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.11
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --allow-unsafe --output-file=constraints.txt --resolver=backtracking --strip-extras requirements.in
|
||||
#
|
||||
asgiref==3.6.0
|
||||
# via django
|
||||
certifi==2022.12.7
|
||||
# via requests
|
||||
charset-normalizer==3.1.0
|
||||
# via requests
|
||||
contourpy==1.0.7
|
||||
# via matplotlib
|
||||
crispy-bootstrap5==0.7
|
||||
# via -r requirements.in
|
||||
cycler==0.11.0
|
||||
# via matplotlib
|
||||
django==4.1.7
|
||||
# via
|
||||
# -r requirements.in
|
||||
# crispy-bootstrap5
|
||||
# django-anymail
|
||||
# django-crispy-forms
|
||||
# django-csp
|
||||
# django-extensions
|
||||
# django-htmx
|
||||
# django-solo
|
||||
django-anymail==9.1
|
||||
# via -r requirements.in
|
||||
django-cleanup==7.0.0
|
||||
# via -r requirements.in
|
||||
django-crispy-forms==2.0
|
||||
# via
|
||||
# -r requirements.in
|
||||
# crispy-bootstrap5
|
||||
django-csp==3.7
|
||||
# via -r requirements.in
|
||||
django-environ==0.10.0
|
||||
# via -r requirements.in
|
||||
django-extensions==3.2.1
|
||||
# via -r requirements.in
|
||||
django-htmx==1.14.0
|
||||
# via -r requirements.in
|
||||
django-solo==2.0.0
|
||||
# via -r requirements.in
|
||||
fonttools==4.39.2
|
||||
# via matplotlib
|
||||
freezegun==1.2.2
|
||||
# via -r requirements.in
|
||||
gunicorn==20.1.0
|
||||
# via -r requirements.in
|
||||
idna==3.4
|
||||
# via requests
|
||||
kiwisolver==1.4.4
|
||||
# via matplotlib
|
||||
matplotlib==3.7.1
|
||||
# via -r requirements.in
|
||||
numpy==1.24.2
|
||||
# via
|
||||
# contourpy
|
||||
# matplotlib
|
||||
packaging==23.0
|
||||
# via matplotlib
|
||||
pillow==9.4.0
|
||||
# via
|
||||
# -r requirements.in
|
||||
# matplotlib
|
||||
pyparsing==3.0.9
|
||||
# via matplotlib
|
||||
python-dateutil==2.8.2
|
||||
# via
|
||||
# freezegun
|
||||
# matplotlib
|
||||
requests==2.28.2
|
||||
# via
|
||||
# -r requirements.in
|
||||
# django-anymail
|
||||
six==1.16.0
|
||||
# via python-dateutil
|
||||
sqlparse==0.4.3
|
||||
# via django
|
||||
urllib3==1.26.15
|
||||
# via requests
|
||||
whitenoise==6.4.0
|
||||
# via -r requirements.in
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
setuptools==67.6.0
|
||||
# via gunicorn
|
108
pyproject.toml
108
pyproject.toml
|
@ -1,48 +1,6 @@
|
|||
[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 = "^6.0"
|
||||
whitenoise = {extras = ["brotli"], version = "^6.0"}
|
||||
django-csp = "^3.7"
|
||||
django-environ = "^0.9"
|
||||
requests = "^2.27.1"
|
||||
django-extensions = "^3.1.5"
|
||||
bpython = "^0.23"
|
||||
gunicorn = "^20.1.0"
|
||||
Pillow = "^9.1.0"
|
||||
django-crispy-forms = "^1.14.0"
|
||||
crispy-bootstrap5 = "^0.6"
|
||||
matplotlib = "^3.5.1"
|
||||
freezegun = "^1.2.1"
|
||||
django-htmx = "^1.12.2"
|
||||
django-solo = "^2.0.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 = "^2.0"
|
||||
invoke = "^1.7.0"
|
||||
factory-boy = "^3.2.1"
|
||||
pytest-selenium = "^4.0.0"
|
||||
selenium = "^4.4.3"
|
||||
kolo = "^2.0.3"
|
||||
|
||||
[tool.black]
|
||||
target-version = ['py310']
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
|
||||
###############################################################################
|
||||
# pytest
|
||||
###############################################################################
|
||||
[tool.pytest.ini_options]
|
||||
addopts = "--color=yes --driver Firefox"
|
||||
minversion = "6.0"
|
||||
|
@ -51,28 +9,46 @@ testpaths = [
|
|||
"src",
|
||||
]
|
||||
|
||||
[tool.flakeheaven]
|
||||
max_complexity = 10
|
||||
format = "grouped"
|
||||
###############################################################################
|
||||
# ruff
|
||||
###############################################################################
|
||||
|
||||
[tool.flakeheaven.plugins]
|
||||
"*" = [
|
||||
"+*",
|
||||
# long lines
|
||||
"-E501",
|
||||
# conflict with black on PEP8 interpretation
|
||||
"-E203",
|
||||
# deprecated rule: https://www.flake8rules.com/rules/W503.html
|
||||
"-W503",
|
||||
[tool.ruff]
|
||||
src = ["src"]
|
||||
target-version = "py311"
|
||||
select = ["ALL"]
|
||||
unfixable = ["T20", "RUF001", "RUF002", "RUF003"]
|
||||
|
||||
ignore = [
|
||||
"ANN", # flake8-annotations
|
||||
"BLE", # flake8-blind-except
|
||||
"TCH", # flake8-type-checking / TODO: revisit later ?
|
||||
|
||||
"E501", # long lines
|
||||
"D1", # missing docstring
|
||||
"TRY003", # Avoid specifying long messages outside the exception class
|
||||
]
|
||||
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.
|
||||
[tool.ruff.per-file-ignores]
|
||||
"**/tests/*" = [
|
||||
"S101", # Use of assert detected.
|
||||
"S105", # Possible hardcoded password.
|
||||
"B011", # Do not call assert False since python -O removes these calls.
|
||||
"ARG001", # Unused function argument (mostly fixtures)
|
||||
"PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable
|
||||
"S311", # Standard pseudo-random generators are not suitable for cryptographic purposes.
|
||||
]
|
||||
# File {name} is part of an implicit namespace package. Add an `__init__.py`.
|
||||
"tasks.py" = ["INP001"]
|
||||
"src/conftest.py" = ["INP001"]
|
||||
"src/manage.py" = ["INP001"]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
"src/purchase/management/commands/generate_dummy_baskets.py" = [
|
||||
"S311", # Standard pseudo-random generators are not suitable for cryptographic purposes.
|
||||
]
|
||||
|
||||
[tool.ruff.pydocstyle]
|
||||
convention = "pep257"
|
||||
|
||||
[tool.ruff.mccabe]
|
||||
max-complexity = 10
|
||||
|
|
14
requirements-dev.in
Normal file
14
requirements-dev.in
Normal file
|
@ -0,0 +1,14 @@
|
|||
-c constraints.txt
|
||||
pre-commit>=2.7
|
||||
pytest>=6.0
|
||||
pytest-cov>=3.0.0
|
||||
pytest-django>=4.5.0
|
||||
pytest-selenium>=4.0.0
|
||||
pre-commit>=2.7
|
||||
model-bakery>=1.1
|
||||
invoke>=2.0.0
|
||||
factory-boy>=3.2.1
|
||||
selenium>=4.4.3
|
||||
black>=22.12.0
|
||||
pip-tools>=6.0
|
||||
ruff>=0.0.237
|
183
requirements-dev.txt
Normal file
183
requirements-dev.txt
Normal file
|
@ -0,0 +1,183 @@
|
|||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.11
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --allow-unsafe --resolver=backtracking requirements-dev.in
|
||||
#
|
||||
asgiref==3.6.0
|
||||
# via
|
||||
# -c constraints.txt
|
||||
# django
|
||||
async-generator==1.10
|
||||
# via trio
|
||||
attrs==22.2.0
|
||||
# via
|
||||
# outcome
|
||||
# pytest
|
||||
# trio
|
||||
black==23.1.0
|
||||
# via -r requirements-dev.in
|
||||
build==0.10.0
|
||||
# via pip-tools
|
||||
certifi==2022.12.7
|
||||
# via
|
||||
# -c constraints.txt
|
||||
# requests
|
||||
# selenium
|
||||
cfgv==3.3.1
|
||||
# via pre-commit
|
||||
charset-normalizer==3.1.0
|
||||
# via
|
||||
# -c constraints.txt
|
||||
# requests
|
||||
click==8.1.3
|
||||
# via
|
||||
# black
|
||||
# pip-tools
|
||||
coverage[toml]==7.2.2
|
||||
# via pytest-cov
|
||||
distlib==0.3.6
|
||||
# via virtualenv
|
||||
django==4.1.7
|
||||
# via
|
||||
# -c constraints.txt
|
||||
# model-bakery
|
||||
exceptiongroup==1.1.1
|
||||
# via trio-websocket
|
||||
factory-boy==3.2.1
|
||||
# via -r requirements-dev.in
|
||||
faker==18.3.1
|
||||
# via factory-boy
|
||||
filelock==3.10.4
|
||||
# via virtualenv
|
||||
h11==0.14.0
|
||||
# via wsproto
|
||||
identify==2.5.22
|
||||
# via pre-commit
|
||||
idna==3.4
|
||||
# via
|
||||
# -c constraints.txt
|
||||
# requests
|
||||
# trio
|
||||
iniconfig==2.0.0
|
||||
# via pytest
|
||||
invoke==2.0.0
|
||||
# via -r requirements-dev.in
|
||||
model-bakery==1.10.1
|
||||
# via -r requirements-dev.in
|
||||
mypy-extensions==1.0.0
|
||||
# via black
|
||||
nodeenv==1.7.0
|
||||
# via pre-commit
|
||||
outcome==1.2.0
|
||||
# via trio
|
||||
packaging==23.0
|
||||
# via
|
||||
# -c constraints.txt
|
||||
# black
|
||||
# build
|
||||
# pytest
|
||||
pathspec==0.11.1
|
||||
# via black
|
||||
pip-tools==6.12.3
|
||||
# via -r requirements-dev.in
|
||||
platformdirs==3.1.1
|
||||
# via
|
||||
# black
|
||||
# virtualenv
|
||||
pluggy==1.0.0
|
||||
# via pytest
|
||||
pre-commit==3.2.1
|
||||
# via -r requirements-dev.in
|
||||
py==1.11.0
|
||||
# via
|
||||
# pytest
|
||||
# pytest-html
|
||||
pyproject-hooks==1.0.0
|
||||
# via build
|
||||
pysocks==1.7.1
|
||||
# via urllib3
|
||||
pytest==6.2.5
|
||||
# via
|
||||
# -r requirements-dev.in
|
||||
# pytest-base-url
|
||||
# pytest-cov
|
||||
# pytest-django
|
||||
# pytest-html
|
||||
# pytest-metadata
|
||||
# pytest-selenium
|
||||
# pytest-variables
|
||||
pytest-base-url==2.0.0
|
||||
# via pytest-selenium
|
||||
pytest-cov==4.0.0
|
||||
# via -r requirements-dev.in
|
||||
pytest-django==4.5.2
|
||||
# via -r requirements-dev.in
|
||||
pytest-html==3.2.0
|
||||
# via pytest-selenium
|
||||
pytest-metadata==2.0.4
|
||||
# via pytest-html
|
||||
pytest-selenium==4.0.0
|
||||
# via -r requirements-dev.in
|
||||
pytest-variables==2.0.0
|
||||
# via pytest-selenium
|
||||
python-dateutil==2.8.2
|
||||
# via
|
||||
# -c constraints.txt
|
||||
# faker
|
||||
pyyaml==6.0
|
||||
# via pre-commit
|
||||
requests==2.28.2
|
||||
# via
|
||||
# -c constraints.txt
|
||||
# pytest-base-url
|
||||
# pytest-selenium
|
||||
ruff==0.0.259
|
||||
# via -r requirements-dev.in
|
||||
selenium==4.8.3
|
||||
# via
|
||||
# -r requirements-dev.in
|
||||
# pytest-selenium
|
||||
six==1.16.0
|
||||
# via
|
||||
# -c constraints.txt
|
||||
# python-dateutil
|
||||
# tenacity
|
||||
sniffio==1.3.0
|
||||
# via trio
|
||||
sortedcontainers==2.4.0
|
||||
# via trio
|
||||
sqlparse==0.4.3
|
||||
# via
|
||||
# -c constraints.txt
|
||||
# django
|
||||
tenacity==6.3.1
|
||||
# via pytest-selenium
|
||||
toml==0.10.2
|
||||
# via pytest
|
||||
trio==0.22.0
|
||||
# via
|
||||
# selenium
|
||||
# trio-websocket
|
||||
trio-websocket==0.10.2
|
||||
# via selenium
|
||||
urllib3[socks]==1.26.15
|
||||
# via
|
||||
# -c constraints.txt
|
||||
# requests
|
||||
# selenium
|
||||
virtualenv==20.21.0
|
||||
# via pre-commit
|
||||
wheel==0.40.0
|
||||
# via pip-tools
|
||||
wsproto==1.2.0
|
||||
# via trio-websocket
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
pip==23.0.1
|
||||
# via pip-tools
|
||||
setuptools==67.6.0
|
||||
# via
|
||||
# -c constraints.txt
|
||||
# nodeenv
|
||||
# pip-tools
|
16
requirements.in
Normal file
16
requirements.in
Normal file
|
@ -0,0 +1,16 @@
|
|||
django>=4.1,<5.0
|
||||
django-anymail[mailgun]>=8.6
|
||||
django-cleanup>=6.0
|
||||
whitenoise>=6.2
|
||||
django-csp>=3.7
|
||||
django-environ>=0.9.0
|
||||
requests>=2.28.1
|
||||
django-extensions>=3.1.5
|
||||
gunicorn>=20.1.0
|
||||
Pillow>=9.3.0
|
||||
django-crispy-forms>=1.14.0
|
||||
crispy-bootstrap5>=0.6
|
||||
matplotlib>=3.5.1
|
||||
freezegun>=1.2.1
|
||||
django-htmx>=1.12.2
|
||||
django-solo>=2.0.0
|
90
requirements.txt
Normal file
90
requirements.txt
Normal file
|
@ -0,0 +1,90 @@
|
|||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.11
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --allow-unsafe --resolver=backtracking requirements.in
|
||||
#
|
||||
asgiref==3.6.0
|
||||
# via django
|
||||
certifi==2022.12.7
|
||||
# via requests
|
||||
charset-normalizer==3.1.0
|
||||
# via requests
|
||||
contourpy==1.0.7
|
||||
# via matplotlib
|
||||
crispy-bootstrap5==0.7
|
||||
# via -r requirements.in
|
||||
cycler==0.11.0
|
||||
# via matplotlib
|
||||
django==4.1.7
|
||||
# via
|
||||
# -r requirements.in
|
||||
# crispy-bootstrap5
|
||||
# django-anymail
|
||||
# django-crispy-forms
|
||||
# django-csp
|
||||
# django-extensions
|
||||
# django-htmx
|
||||
# django-solo
|
||||
django-anymail[mailgun]==9.1
|
||||
# via -r requirements.in
|
||||
django-cleanup==7.0.0
|
||||
# via -r requirements.in
|
||||
django-crispy-forms==2.0
|
||||
# via
|
||||
# -r requirements.in
|
||||
# crispy-bootstrap5
|
||||
django-csp==3.7
|
||||
# via -r requirements.in
|
||||
django-environ==0.10.0
|
||||
# via -r requirements.in
|
||||
django-extensions==3.2.1
|
||||
# via -r requirements.in
|
||||
django-htmx==1.14.0
|
||||
# via -r requirements.in
|
||||
django-solo==2.0.0
|
||||
# via -r requirements.in
|
||||
fonttools==4.39.2
|
||||
# via matplotlib
|
||||
freezegun==1.2.2
|
||||
# via -r requirements.in
|
||||
gunicorn==20.1.0
|
||||
# via -r requirements.in
|
||||
idna==3.4
|
||||
# via requests
|
||||
kiwisolver==1.4.4
|
||||
# via matplotlib
|
||||
matplotlib==3.7.1
|
||||
# via -r requirements.in
|
||||
numpy==1.24.2
|
||||
# via
|
||||
# contourpy
|
||||
# matplotlib
|
||||
packaging==23.0
|
||||
# via matplotlib
|
||||
pillow==9.4.0
|
||||
# via
|
||||
# -r requirements.in
|
||||
# matplotlib
|
||||
pyparsing==3.0.9
|
||||
# via matplotlib
|
||||
python-dateutil==2.8.2
|
||||
# via
|
||||
# freezegun
|
||||
# matplotlib
|
||||
requests==2.28.2
|
||||
# via
|
||||
# -r requirements.in
|
||||
# django-anymail
|
||||
six==1.16.0
|
||||
# via python-dateutil
|
||||
sqlparse==0.4.3
|
||||
# via django
|
||||
urllib3==1.26.15
|
||||
# via requests
|
||||
whitenoise==6.4.0
|
||||
# via -r requirements.in
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
setuptools==67.6.0
|
||||
# via gunicorn
|
|
@ -110,7 +110,7 @@ MIDDLEWARE = [
|
|||
try:
|
||||
import kolo # noqa: F401
|
||||
|
||||
MIDDLEWARE = ["kolo.middleware.KoloMiddleware"] + MIDDLEWARE
|
||||
MIDDLEWARE = ["kolo.middleware.KoloMiddleware", *MIDDLEWARE]
|
||||
except ImportError:
|
||||
# Don't add kolo if unavailable
|
||||
pass
|
||||
|
@ -139,14 +139,14 @@ 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:
|
||||
if not DB_BASE_DIR: # noqa: SIM108
|
||||
# Protect against empty strings
|
||||
DB_BASE_DIR = BASE_DIR
|
||||
else:
|
||||
|
@ -156,7 +156,7 @@ DATABASES = {
|
|||
"default": {
|
||||
"ENGINE": "django.db.backends.sqlite3",
|
||||
"NAME": DB_BASE_DIR / "db.sqlite3",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
INTERNAL_IPS = [
|
||||
|
@ -169,7 +169,7 @@ INTERNAL_IPS = [
|
|||
|
||||
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"},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""checkout URL Configuration
|
||||
"""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/
|
||||
|
@ -24,7 +24,8 @@ urlpatterns = [
|
|||
path(
|
||||
"robots.txt",
|
||||
TemplateView.as_view(
|
||||
template_name="common/robots.txt", content_type="text/plain"
|
||||
template_name="common/robots.txt",
|
||||
content_type="text/plain",
|
||||
),
|
||||
),
|
||||
path("", include("common.urls")),
|
||||
|
|
|
@ -7,7 +7,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
|
@ -31,7 +30,9 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"last_login",
|
||||
models.DateTimeField(
|
||||
blank=True, null=True, verbose_name="last login"
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name="last login",
|
||||
),
|
||||
),
|
||||
(
|
||||
|
@ -46,13 +47,13 @@ class Migration(migrations.Migration):
|
|||
"username",
|
||||
models.CharField(
|
||||
error_messages={
|
||||
"unique": "A user with that username already exists."
|
||||
"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()
|
||||
django.contrib.auth.validators.UnicodeUsernameValidator(),
|
||||
],
|
||||
verbose_name="username",
|
||||
),
|
||||
|
@ -60,19 +61,25 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"first_name",
|
||||
models.CharField(
|
||||
blank=True, max_length=150, verbose_name="first name"
|
||||
blank=True,
|
||||
max_length=150,
|
||||
verbose_name="first name",
|
||||
),
|
||||
),
|
||||
(
|
||||
"last_name",
|
||||
models.CharField(
|
||||
blank=True, max_length=150, verbose_name="last name"
|
||||
blank=True,
|
||||
max_length=150,
|
||||
verbose_name="last name",
|
||||
),
|
||||
),
|
||||
(
|
||||
"email",
|
||||
models.EmailField(
|
||||
blank=True, max_length=254, verbose_name="email address"
|
||||
blank=True,
|
||||
max_length=254,
|
||||
verbose_name="email address",
|
||||
),
|
||||
),
|
||||
(
|
||||
|
@ -94,7 +101,8 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"date_joined",
|
||||
models.DateTimeField(
|
||||
default=django.utils.timezone.now, verbose_name="date joined"
|
||||
default=django.utils.timezone.now,
|
||||
verbose_name="date joined",
|
||||
),
|
||||
),
|
||||
(
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Checkout</title>
|
||||
<link href="{% static "vendor/bootstrap-5.1.3-dist/css/bootstrap.min.css" %}"
|
||||
rel="stylesheet">
|
||||
rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
margin-bottom: 2em;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from django.shortcuts import redirect
|
||||
|
||||
|
||||
def home(request):
|
||||
def home(_request):
|
||||
return redirect("purchase:new")
|
||||
|
|
|
@ -5,16 +5,17 @@ import sys
|
|||
|
||||
|
||||
def main() -> None:
|
||||
"""Run administrative tasks.""" # noqa: DAR401
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "checkout.settings")
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
msg = (
|
||||
"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
|
||||
)
|
||||
raise ImportError(msg) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
|
|
|
@ -39,8 +39,8 @@ class BasketForm(forms.ModelForm):
|
|||
label=product.name,
|
||||
min_value=0,
|
||||
initial=products.get(product, 0),
|
||||
)
|
||||
}
|
||||
),
|
||||
},
|
||||
)
|
||||
fields.append(BasketItemField(field_name, product=product))
|
||||
self.helper.layout = Layout(
|
||||
|
@ -51,7 +51,7 @@ class BasketForm(forms.ModelForm):
|
|||
InlineRadios("payment_method"),
|
||||
)
|
||||
|
||||
def save(self, commit=True):
|
||||
def save(self):
|
||||
instance: Basket = super().save(commit=True)
|
||||
name: str
|
||||
products = {product.id: product for product in Product.objects.all()}
|
||||
|
|
|
@ -6,7 +6,7 @@ from purchase.models import Basket, BasketItem, PaymentMethod, Product
|
|||
class Command(BaseCommand):
|
||||
help = "Clear all data" # noqa: A003
|
||||
|
||||
def handle(self, *args, **options):
|
||||
def handle(self, *args, **options): # noqa: ARG002
|
||||
self.delete(BasketItem)
|
||||
self.delete(Basket)
|
||||
self.delete(Product)
|
||||
|
|
|
@ -13,7 +13,7 @@ from purchase.models import Basket, BasketItem, PaymentMethod, Product
|
|||
class Command(BaseCommand):
|
||||
help = "Generates dummy baskets" # noqa: A003
|
||||
|
||||
def handle(self, *args, **options):
|
||||
def handle(self, *args, **options): # noqa: ARG002
|
||||
call_command("loaddata", ["payment_methods", "products"])
|
||||
products = list(Product.objects.all())
|
||||
payment_methods = list(PaymentMethod.objects.all())
|
||||
|
@ -37,7 +37,7 @@ class Command(BaseCommand):
|
|||
products_weights = [1 / product.display_order for product in products]
|
||||
for _ in range(count):
|
||||
method = None
|
||||
if random.random() < 0.99:
|
||||
if random.random() < 0.99: # noqa: PLR2004
|
||||
method = random.choices(payment_methods, weights=methods_weights)[0]
|
||||
basket = Basket.objects.create(payment_method=method)
|
||||
items_in_basket = int(random.normalvariate(3, 2))
|
||||
|
@ -45,7 +45,7 @@ class Command(BaseCommand):
|
|||
items_in_basket = len(products)
|
||||
if items_in_basket < 1:
|
||||
items_in_basket = 1
|
||||
selected_products = np.random.choice(
|
||||
selected_products = np.random.Generator(
|
||||
products,
|
||||
size=items_in_basket,
|
||||
replace=False,
|
||||
|
@ -59,7 +59,7 @@ class Command(BaseCommand):
|
|||
basket=basket,
|
||||
quantity=random.randint(1, 3),
|
||||
unit_price_cents=product.unit_price_cents,
|
||||
)
|
||||
),
|
||||
)
|
||||
BasketItem.objects.bulk_create(items)
|
||||
return count
|
||||
|
|
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("purchase", "0001_initial"),
|
||||
]
|
||||
|
|
|
@ -6,7 +6,6 @@ import purchase.models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("purchase", "0002_alter_product_image"),
|
||||
]
|
||||
|
@ -16,7 +15,7 @@ class Migration(migrations.Migration):
|
|||
model_name="product",
|
||||
name="display_order",
|
||||
field=models.PositiveIntegerField(
|
||||
default=purchase.models.default_product_display_order
|
||||
default=purchase.models.default_product_display_order,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -4,7 +4,6 @@ from django.db import migrations
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("purchase", "0003_alter_product_display_order"),
|
||||
]
|
||||
|
|
|
@ -7,7 +7,6 @@ import purchase.models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("purchase", "0004_remove_basket_status"),
|
||||
]
|
||||
|
@ -128,7 +127,10 @@ class Migration(migrations.Migration):
|
|||
model_name="product",
|
||||
name="image",
|
||||
field=models.ImageField(
|
||||
blank=True, null=True, upload_to="", verbose_name="image"
|
||||
blank=True,
|
||||
null=True,
|
||||
upload_to="",
|
||||
verbose_name="image",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
|
@ -140,7 +142,8 @@ class Migration(migrations.Migration):
|
|||
model_name="product",
|
||||
name="unit_price_cents",
|
||||
field=models.PositiveIntegerField(
|
||||
help_text="unit price in cents", verbose_name="unit price (cents)"
|
||||
help_text="unit price in cents",
|
||||
verbose_name="unit price (cents)",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
|
|
|
@ -4,7 +4,7 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
def forwards(apps, schema_editor):
|
||||
BasketItem = apps.get_model("purchase", "BasketItem")
|
||||
BasketItem = apps.get_model("purchase", "BasketItem") # noqa: N806
|
||||
items = (
|
||||
BasketItem.objects.using(schema_editor.connection.alias)
|
||||
.all()
|
||||
|
@ -16,7 +16,6 @@ def forwards(apps, schema_editor):
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("purchase", "0005_alter_basket_options_alter_basketitem_options_and_more"),
|
||||
]
|
||||
|
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("purchase", "0006_basketitem_unit_price_cents"),
|
||||
]
|
||||
|
|
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("purchase", "0007_alter_basketitem_unit_price_cents"),
|
||||
]
|
||||
|
|
|
@ -6,7 +6,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("purchase", "0008_basketitem_unique_product_per_basket"),
|
||||
]
|
||||
|
|
|
@ -4,7 +4,6 @@ from django.db import migrations
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("purchase", "0009_basketitemetag"),
|
||||
]
|
||||
|
|
|
@ -4,7 +4,6 @@ from django.db import migrations
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("purchase", "0010_rename_basketitemetag_cacheetag"),
|
||||
]
|
||||
|
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("purchase", "0011_rename_cacheetag_cache"),
|
||||
]
|
||||
|
|
|
@ -27,10 +27,10 @@ class PaymentMethodQuerySet(models.QuerySet):
|
|||
turnover=Coalesce(
|
||||
Sum(
|
||||
F("baskets__items__quantity")
|
||||
* F("baskets__items__unit_price_cents")
|
||||
* F("baskets__items__unit_price_cents"),
|
||||
),
|
||||
0,
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
def with_sold(self):
|
||||
|
@ -71,7 +71,7 @@ class ProductQuerySet(models.QuerySet):
|
|||
turnover=Coalesce(
|
||||
Sum(F("basket_items__quantity") * F("basket_items__unit_price_cents")),
|
||||
0,
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
def with_sold(self):
|
||||
|
@ -87,10 +87,12 @@ class Product(Model):
|
|||
name = models.CharField(max_length=250, unique=True, verbose_name=_("name"))
|
||||
image = models.ImageField(null=True, blank=True, verbose_name=_("image"))
|
||||
unit_price_cents = models.PositiveIntegerField(
|
||||
verbose_name=_("unit price (cents)"), help_text=_("unit price in cents")
|
||||
verbose_name=_("unit price (cents)"),
|
||||
help_text=_("unit price in cents"),
|
||||
)
|
||||
display_order = models.PositiveIntegerField(
|
||||
default=default_product_display_order, verbose_name=_("display order")
|
||||
default=default_product_display_order,
|
||||
verbose_name=_("display order"),
|
||||
)
|
||||
|
||||
objects = ProductManager.from_queryset(ProductQuerySet)()
|
||||
|
@ -109,19 +111,21 @@ class Product(Model):
|
|||
@property
|
||||
def color_hue(self):
|
||||
return int(
|
||||
hashlib.sha256(bytes(self.name, encoding="utf-8")).hexdigest()[:2], base=16
|
||||
hashlib.sha256(bytes(self.name, encoding="utf-8")).hexdigest()[:2],
|
||||
base=16,
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save()
|
||||
super().save(*args, **kwargs)
|
||||
if not self.image:
|
||||
return
|
||||
with Image.open(self.image.path) as img:
|
||||
img = ImageOps.exif_transpose(img)
|
||||
with Image.open(self.image.path) as img_file:
|
||||
img = ImageOps.exif_transpose(img_file)
|
||||
|
||||
width, height = img.size # Get dimensions
|
||||
|
||||
if width > 300 and height > 300:
|
||||
image_max_size = 300
|
||||
if width > image_max_size and height > image_max_size:
|
||||
# keep ratio but shrink down
|
||||
img.thumbnail((width, height))
|
||||
|
||||
|
@ -142,8 +146,8 @@ class Product(Model):
|
|||
bottom = width
|
||||
img = img.crop((left, top, right, bottom))
|
||||
|
||||
if width > 300 and height > 300:
|
||||
img.thumbnail((300, 300))
|
||||
if width > image_max_size and height > image_max_size:
|
||||
img.thumbnail((image_max_size, image_max_size))
|
||||
|
||||
img.save(self.image.path)
|
||||
|
||||
|
@ -151,7 +155,7 @@ class Product(Model):
|
|||
class BasketQuerySet(models.QuerySet):
|
||||
def priced(self) -> BasketQuerySet:
|
||||
return self.annotate(
|
||||
price=Coalesce(Sum(F("items__quantity") * F("items__unit_price_cents")), 0)
|
||||
price=Coalesce(Sum(F("items__quantity") * F("items__unit_price_cents")), 0),
|
||||
)
|
||||
|
||||
def average_basket(self) -> float:
|
||||
|
@ -220,7 +224,7 @@ class BasketItem(Model):
|
|||
verbose_name = _("basket item")
|
||||
verbose_name_plural = _("basket items")
|
||||
constraints = [
|
||||
UniqueConstraint("product", "basket", name="unique_product_per_basket")
|
||||
UniqueConstraint("product", "basket", name="unique_product_per_basket"),
|
||||
]
|
||||
|
||||
|
||||
|
@ -236,9 +240,9 @@ class Cache(SingletonModel):
|
|||
self.save()
|
||||
|
||||
|
||||
def reports_etag(request):
|
||||
def reports_etag(_request):
|
||||
return str(Cache.get_solo().etag)
|
||||
|
||||
|
||||
def reports_last_modified(request):
|
||||
def reports_last_modified(_request):
|
||||
return Cache.get_solo().last_modified
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
def basket_item_on_save(sender, **kwargs):
|
||||
def basket_item_on_save(sender, **kwargs): # noqa: ARG001
|
||||
from purchase.models import Cache
|
||||
|
||||
Cache.get_solo().refresh()
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<img src="{{ product.image.url }}" class="card-img">
|
||||
{% else %}
|
||||
<div class="card-img product-img-placeholder"
|
||||
style="background-color: hsl({{ product.color_hue }}, 60%, 80%)">
|
||||
style="background-color: hsl({{ product.color_hue }}, 60%, 80%)">
|
||||
<span>
|
||||
{{ product.name|slice:"1" }}
|
||||
</span>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% load static %}
|
||||
<div hx-get="{% url url %}"
|
||||
hx-trigger="load"
|
||||
hx-swap="outerHTML"
|
||||
hx-trigger="load"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<img class="htmx-indicator" src="{% static 'purchase/spinner.gif' %}" alt="Spinner">
|
||||
</div>
|
||||
|
|
|
@ -5,6 +5,6 @@ register = template.Library()
|
|||
|
||||
@register.filter
|
||||
def currency(value):
|
||||
if isinstance(value, int) or isinstance(value, float):
|
||||
if isinstance(value, int | float):
|
||||
return f"{value/100:.2f}€"
|
||||
return value
|
||||
|
|
|
@ -21,7 +21,7 @@ class CashierFactory(factory.django.DjangoModelFactory):
|
|||
is_staff = True
|
||||
|
||||
@factory.post_generation
|
||||
def groups(self, create, extracted, **kwargs):
|
||||
def groups(self, create, _extracted, **_kwargs):
|
||||
if create:
|
||||
self.groups.add(CashierGroupFactory())
|
||||
|
||||
|
@ -48,7 +48,7 @@ class BasketWithItemsFactory(factory.django.DjangoModelFactory):
|
|||
payment_method = factory.Iterator(PaymentMethod.objects.all())
|
||||
|
||||
@factory.post_generation
|
||||
def items(self, create, extracted, **kwargs):
|
||||
def items(self, create, _extracted, **_kwargs):
|
||||
if create:
|
||||
products = Product.objects.order_by("?")
|
||||
quantity = random.randint(1, len(products))
|
||||
|
@ -68,7 +68,7 @@ class CashierGroupFactory(factory.django.DjangoModelFactory):
|
|||
name = "Caissier"
|
||||
|
||||
@factory.post_generation
|
||||
def permissions(self, create, extracted, **kwargs):
|
||||
def permissions(self, create, _extracted, **_kwargs):
|
||||
if create:
|
||||
self.permissions.add(
|
||||
Permission.objects.get(codename="add_basket"),
|
||||
|
|
|
@ -20,7 +20,10 @@ from purchase.tests.factories import (
|
|||
|
||||
|
||||
@freezegun.freeze_time("2022-09-24 19:01:00+0200")
|
||||
def test_cashier_create_and_update_basket(live_server: LiveServer, selenium: WebDriver):
|
||||
def test_cashier_create_and_update_basket( # noqa: PLR0915
|
||||
live_server: LiveServer,
|
||||
selenium: WebDriver,
|
||||
):
|
||||
wait = WebDriverWait(selenium, 10)
|
||||
assert Basket.objects.count() == 0
|
||||
|
||||
|
@ -44,7 +47,7 @@ def test_cashier_create_and_update_basket(live_server: LiveServer, selenium: Web
|
|||
wait.until(lambda driver: driver.current_url == redirect_url)
|
||||
displayed_products = selenium.find_elements(By.CSS_SELECTOR, ".card.h-100")
|
||||
assert len(displayed_products) == len(products)
|
||||
for product, displayed_product in zip(products, displayed_products):
|
||||
for product, displayed_product in zip(products, displayed_products, strict=True):
|
||||
assert (
|
||||
product.name
|
||||
== displayed_product.find_element(By.CLASS_NAME, "card-title").text
|
||||
|
@ -185,7 +188,10 @@ def test_cashier_create_and_update_basket(live_server: LiveServer, selenium: Web
|
|||
|
||||
|
||||
def login(
|
||||
live_server: LiveServer, selenium: WebDriver, cashier: User, url: str = "/"
|
||||
live_server: LiveServer,
|
||||
selenium: WebDriver,
|
||||
cashier: User,
|
||||
url: str = "/",
|
||||
) -> None:
|
||||
# Go to page
|
||||
url = live_url(live_server, url)
|
||||
|
@ -215,12 +221,12 @@ def test_baskets_list(live_server: LiveServer, selenium: WebDriver):
|
|||
with freezegun.freeze_time("2022-09-24 19:01:00+0200"):
|
||||
basket_with_payment_method = BasketWithItemsFactory()
|
||||
basket_with_payment_method = Basket.objects.priced().get(
|
||||
pk=basket_with_payment_method.pk
|
||||
pk=basket_with_payment_method.pk,
|
||||
)
|
||||
with freezegun.freeze_time("2022-09-24 19:02:00+0200"):
|
||||
basket_no_payment_method = BasketWithItemsFactory(payment_method=None)
|
||||
basket_no_payment_method = Basket.objects.priced().get(
|
||||
pk=basket_no_payment_method.pk
|
||||
pk=basket_no_payment_method.pk,
|
||||
)
|
||||
|
||||
# Login
|
||||
|
@ -268,7 +274,7 @@ def test_baskets_list(live_server: LiveServer, selenium: WebDriver):
|
|||
|
||||
# Assert redirected to list view
|
||||
wait.until(
|
||||
lambda driver: driver.current_url == live_reverse(live_server, "purchase:list")
|
||||
lambda driver: driver.current_url == live_reverse(live_server, "purchase:list"),
|
||||
)
|
||||
|
||||
# Click on edit on remaining basket
|
||||
|
@ -277,7 +283,9 @@ def test_baskets_list(live_server: LiveServer, selenium: WebDriver):
|
|||
|
||||
# Assert redirected to edit view
|
||||
redirect_url = live_reverse(
|
||||
live_server, "purchase:update", pk=basket_no_payment_method.pk
|
||||
live_server,
|
||||
"purchase:update",
|
||||
pk=basket_no_payment_method.pk,
|
||||
)
|
||||
wait.until(lambda driver: driver.current_url == redirect_url)
|
||||
|
||||
|
|
|
@ -44,7 +44,9 @@ def update_basket(request: HttpRequest, pk: int) -> HttpResponse:
|
|||
form = BasketForm(instance=basket)
|
||||
|
||||
return TemplateResponse(
|
||||
request, "purchase/basket_form.html", {"form": form, "basket": basket}
|
||||
request,
|
||||
"purchase/basket_form.html",
|
||||
{"form": form, "basket": basket},
|
||||
)
|
||||
|
||||
|
||||
|
@ -62,7 +64,6 @@ def delete_basket(request: HttpRequest, pk: int) -> HttpResponse:
|
|||
if request.method == "GET":
|
||||
context = {"basket": basket}
|
||||
return TemplateResponse(request, "purchase/basket_confirm_delete.html", context)
|
||||
else:
|
||||
basket.delete()
|
||||
messages.success(request, _("Basket successfully deleted."))
|
||||
return redirect("purchase:list")
|
||||
basket.delete()
|
||||
messages.success(request, _("Basket successfully deleted."))
|
||||
return redirect("purchase:list")
|
||||
|
|
|
@ -2,7 +2,7 @@ import datetime
|
|||
from io import StringIO
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import matplotlib
|
||||
import matplotlib as mpl
|
||||
import numpy as np
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
|
@ -27,7 +27,7 @@ from purchase.models import (
|
|||
reports_last_modified,
|
||||
)
|
||||
|
||||
matplotlib.use("SVG")
|
||||
mpl.use("SVG")
|
||||
|
||||
|
||||
@permission_required("purchase.view_basket")
|
||||
|
@ -204,5 +204,4 @@ def get_image_from_fig(fig):
|
|||
image_data = StringIO()
|
||||
fig.savefig(image_data, format="svg")
|
||||
image_data.seek(0)
|
||||
img1 = image_data.getvalue()
|
||||
return img1
|
||||
return image_data.getvalue()
|
||||
|
|
101
tasks.py
101
tasks.py
|
@ -1,23 +1,48 @@
|
|||
"""
|
||||
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}
|
||||
TEST_ENV = {"ENV_FILE": BASE_DIR / "envs" / "test-envs.env"}
|
||||
|
||||
|
||||
@task
|
||||
def update_dependencies(ctx: Context, *, sync: bool = True):
|
||||
return compile_dependencies(ctx, update=True, sync=sync)
|
||||
|
||||
|
||||
@task
|
||||
def compile_dependencies(ctx: Context, *, update: bool = False, sync: bool = False):
|
||||
common_args = "-q --allow-unsafe --resolver=backtracking"
|
||||
if update:
|
||||
common_args += " --upgrade"
|
||||
with ctx.cd(BASE_DIR):
|
||||
ctx.run(
|
||||
f"pip-compile {common_args} requirements.in",
|
||||
pty=True,
|
||||
echo=True,
|
||||
)
|
||||
ctx.run(
|
||||
f"pip-compile {common_args} --strip-extras -o constraints.txt requirements.in",
|
||||
pty=True,
|
||||
echo=True,
|
||||
)
|
||||
ctx.run(
|
||||
f"pip-compile {common_args} requirements-dev.in",
|
||||
pty=True,
|
||||
echo=True,
|
||||
)
|
||||
if sync:
|
||||
sync_dependencies(ctx)
|
||||
|
||||
|
||||
@task
|
||||
def sync_dependencies(ctx: Context):
|
||||
with ctx.cd(BASE_DIR):
|
||||
ctx.run("pip-sync requirements.txt requirements-dev.txt", pty=True, echo=True)
|
||||
|
||||
|
||||
@task
|
||||
|
@ -49,58 +74,6 @@ def test_cov(ctx: Context) -> None:
|
|||
)
|
||||
|
||||
|
||||
@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 /mnt/data/checkout/update", pty=True, echo=True)
|
||||
|
||||
|
||||
@task
|
||||
def check_alive(ctx: Context) -> None:
|
||||
exception = None
|
||||
for _ in range(5):
|
||||
try:
|
||||
res = requests.get("https://checkout.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
|
||||
|
||||
|
||||
@task
|
||||
def download_db(ctx: Context) -> None:
|
||||
with ctx.cd(BASE_DIR):
|
||||
|
|
Loading…
Reference in a new issue