From 128310262964fc4ff0147d34c6528dea6fb067ef Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sat, 10 Feb 2024 13:43:55 +0000 Subject: [PATCH 01/19] Update uvicorn from 0.27.0.post1 to 0.27.1 --- {{cookiecutter.project_slug}}/requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_slug}}/requirements/base.txt b/{{cookiecutter.project_slug}}/requirements/base.txt index 70588e23e..442357fc4 100644 --- a/{{cookiecutter.project_slug}}/requirements/base.txt +++ b/{{cookiecutter.project_slug}}/requirements/base.txt @@ -23,7 +23,7 @@ flower==2.0.1 # https://github.com/mher/flower {%- endif %} {%- endif %} {%- if cookiecutter.use_async == 'y' %} -uvicorn[standard]==0.27.0.post1 # https://github.com/encode/uvicorn +uvicorn[standard]==0.27.1 # https://github.com/encode/uvicorn {%- endif %} # Django From f12ac669f3d1a48d03363819f74e6d29ece0575c Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 13 Feb 2024 02:10:21 +0000 Subject: [PATCH 02/19] Release 2024.02.12 --- CHANGELOG.md | 13 +++++++++++++ setup.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5e20748a..1ee7a5895 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,19 @@ All enhancements and patches to Cookiecutter Django will be documented in this f +## 2024.02.12 + + +### Updated + +- Update django-model-utils to 4.4.0 ([#4850](https://github.com/cookiecutter/cookiecutter-django/pull/4850)) + +- Update pre-commit to 3.6.1 ([#4849](https://github.com/cookiecutter/cookiecutter-django/pull/4849)) + +- Update django-upgrade to 1.16.0 ([#4851](https://github.com/cookiecutter/cookiecutter-django/pull/4851)) + +- Auto-update pre-commit hooks ([#4852](https://github.com/cookiecutter/cookiecutter-django/pull/4852)) + ## 2024.02.09 diff --git a/setup.py b/setup.py index b5182fce7..7c129824d 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ except ImportError: from distutils.core import setup # We use calendar versioning -version = "2024.02.09" +version = "2024.02.12" with open("README.md") as readme_file: long_description = readme_file.read() From 2e129bd65b9d59270de6351f3478b320f5294664 Mon Sep 17 00:00:00 2001 From: "pyup.io bot" Date: Tue, 13 Feb 2024 02:25:10 -0800 Subject: [PATCH 03/19] Update black to 24.2.0 (#4853) * Update black from 24.1.1 to 24.2.0 * Update black from 24.1.1 to 24.2.0 --- requirements.txt | 2 +- {{cookiecutter.project_slug}}/requirements/local.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 18ae8f05e..a84612d0f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ binaryornot==0.4.4 # Code quality # ------------------------------------------------------------------------------ -black==24.1.1 +black==24.2.0 isort==5.13.2 flake8==7.0.0 django-upgrade==1.16.0 diff --git a/{{cookiecutter.project_slug}}/requirements/local.txt b/{{cookiecutter.project_slug}}/requirements/local.txt index ef26dcc8c..cad007f47 100644 --- a/{{cookiecutter.project_slug}}/requirements/local.txt +++ b/{{cookiecutter.project_slug}}/requirements/local.txt @@ -31,7 +31,7 @@ sphinx-autobuild==2024.2.4 # https://github.com/GaretJax/sphinx-autobuild flake8==7.0.0 # https://github.com/PyCQA/flake8 flake8-isort==6.1.1 # https://github.com/gforcada/flake8-isort coverage==7.4.1 # https://github.com/nedbat/coveragepy -black==24.1.1 # https://github.com/psf/black +black==24.2.0 # https://github.com/psf/black djlint==1.34.1 # https://github.com/Riverside-Healthcare/djLint pylint-django==2.5.5 # https://github.com/PyCQA/pylint-django {%- if cookiecutter.use_celery == 'y' %} From 07376d8a7f3f60f9dc405466dfa83646bf615d10 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 10:25:25 +0000 Subject: [PATCH 04/19] Auto-update pre-commit hooks (#4855) Co-authored-by: browniebroke <861044+browniebroke@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- {{cookiecutter.project_slug}}/.pre-commit-config.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index baafb694f..7daf6ac71 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,7 +33,7 @@ repos: exclude: hooks/ - repo: https://github.com/psf/black - rev: 24.1.1 + rev: 24.2.0 hooks: - id: black diff --git a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml index d32ac6b84..466cfeced 100644 --- a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml +++ b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml @@ -40,7 +40,7 @@ repos: args: [--py311-plus] - repo: https://github.com/psf/black - rev: 24.1.1 + rev: 24.2.0 hooks: - id: black From dd841c64780cdeb25f6ab89bdd47fa6c528f1edb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 10:28:24 +0000 Subject: [PATCH 05/19] Bump traefik to 2.11.0 (#4857) Bumps traefik from 2.10.7 to 2.11.0. --- updated-dependencies: - dependency-name: traefik dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../compose/production/traefik/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_slug}}/compose/production/traefik/Dockerfile b/{{cookiecutter.project_slug}}/compose/production/traefik/Dockerfile index c32b15873..ea918e911 100644 --- a/{{cookiecutter.project_slug}}/compose/production/traefik/Dockerfile +++ b/{{cookiecutter.project_slug}}/compose/production/traefik/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/traefik:2.10.7 +FROM docker.io/traefik:2.11.0 RUN mkdir -p /etc/traefik/acme \ && touch /etc/traefik/acme/acme.json \ && chmod 600 /etc/traefik/acme/acme.json From 6ba6104f09d4c4c9d56eb6b460e17748115ececb Mon Sep 17 00:00:00 2001 From: Jelmer Draaijer Date: Tue, 6 Feb 2024 19:56:31 +0100 Subject: [PATCH 06/19] Ruff as formatter & linter --- docs/linters.rst | 48 ++---- requirements.txt | 4 +- .../.devcontainer/devcontainer.json | 13 +- .../.pre-commit-config.yaml | 27 +-- {{cookiecutter.project_slug}}/.travis.yml | 8 +- {{cookiecutter.project_slug}}/README.md | 2 +- {{cookiecutter.project_slug}}/pyproject.toml | 162 ++++++++++++------ .../requirements/local.txt | 8 +- {{cookiecutter.project_slug}}/setup.cfg | 11 -- .../users/tests/test_admin.py | 2 +- 10 files changed, 145 insertions(+), 140 deletions(-) delete mode 100644 {{cookiecutter.project_slug}}/setup.cfg diff --git a/docs/linters.rst b/docs/linters.rst index a4f60cc8d..1fc44f30b 100644 --- a/docs/linters.rst +++ b/docs/linters.rst @@ -4,40 +4,30 @@ Linters .. index:: linters -flake8 +ruff ------ -To run flake8: :: +Ruff is a Python linter and code formatter, written in Rust. +It is a aggregation of flake8, pylint, pyupgrade and many more. - $ flake8 +Ruff comes with a linter (``ruff check``) and a formatter (``ruff format``). +The linter is a wrapper around flake8, pylint, and other linters, +and the formatter is a wrapper around black, isort, and other formatters. -The config for flake8 is located in setup.cfg. It specifies: +To run ruff without modifying your files: :: -* Set max line length to 120 chars -* Exclude ``.tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules`` + $ ruff format --diff . + $ ruff check . -pylint ------- +Ruff is capable of fixing most of the problems it encounters. +Be sure you commit first before running `ruff` so you can restore to a savepoint (and amend afterwards to prevent a double commit. : :: -To run pylint: :: + $ ruff format . + $ ruff check --fix . + # be careful with the --unsafe-fixes option, it can break your code + $ ruff check --fix --unsafe-fixes . - $ pylint - -The config for pylint is located in .pylintrc. It specifies: - -* Use the pylint_django plugin. If using Celery, also use pylint_celery. -* Set max line length to 120 chars -* Disable linting messages for missing docstring and invalid name -* max-parents=13 - -pycodestyle ------------ - -This is included in flake8's checks, but you can also run it separately to see a more detailed report: :: - - $ pycodestyle - -The config for pycodestyle is located in setup.cfg. It specifies: - -* Set max line length to 120 chars -* Exclude ``.tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules`` +The config for ruff is located in pyproject.toml. +On of the most important option is `tool.ruff.lint.select`. +`select` determines which linters are run. In example, `DJ `_ refers to flake8-django. +For a full list of available linters, see `https://docs.astral.sh/ruff/rules/ `_ diff --git a/requirements.txt b/requirements.txt index a84612d0f..3de15dc6f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,9 +4,7 @@ binaryornot==0.4.4 # Code quality # ------------------------------------------------------------------------------ -black==24.2.0 -isort==5.13.2 -flake8==7.0.0 +ruff==0.2.1 django-upgrade==1.16.0 djlint==1.34.1 pre-commit==3.6.1 diff --git a/{{cookiecutter.project_slug}}/.devcontainer/devcontainer.json b/{{cookiecutter.project_slug}}/.devcontainer/devcontainer.json index c4158dc10..7fcd62872 100644 --- a/{{cookiecutter.project_slug}}/.devcontainer/devcontainer.json +++ b/{{cookiecutter.project_slug}}/.devcontainer/devcontainer.json @@ -37,22 +37,11 @@ "editor.codeActionsOnSave": { "source.organizeImports": true }, - // Uncomment when fixed - // https://github.com/microsoft/vscode-remote-release/issues/8474 - // "editor.defaultFormatter": "ms-python.black-formatter", - "formatting.blackPath": "/usr/local/bin/black", - "formatting.provider": "black", + "editor.defaultFormatter": "charliermarsh.ruff", "languageServer": "Pylance", - // "linting.banditPath": "/usr/local/py-utils/bin/bandit", "linting.enabled": true, - "linting.flake8Enabled": true, - "linting.flake8Path": "/usr/local/bin/flake8", "linting.mypyEnabled": true, "linting.mypyPath": "/usr/local/bin/mypy", - "linting.pycodestylePath": "/usr/local/bin/pycodestyle", - // "linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", - "linting.pylintEnabled": true, - "linting.pylintPath": "/usr/local/bin/pylint" } }, // https://code.visualstudio.com/docs/remote/devcontainerjson-reference#_vs-code-specific-properties diff --git a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml index 466cfeced..7c9565e19 100644 --- a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml +++ b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml @@ -33,26 +33,15 @@ repos: - id: django-upgrade args: ['--target-version', '4.2'] - - repo: https://github.com/asottile/pyupgrade - rev: v3.15.0 + # Run the Ruff linter. + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.2.1 hooks: - - id: pyupgrade - args: [--py311-plus] - - - repo: https://github.com/psf/black - rev: 24.2.0 - hooks: - - id: black - - - repo: https://github.com/PyCQA/isort - rev: 5.13.2 - hooks: - - id: isort - - - repo: https://github.com/PyCQA/flake8 - rev: 7.0.0 - hooks: - - id: flake8 + # Linter + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + # Formatter + - id: ruff-format - repo: https://github.com/Riverside-Healthcare/djLint rev: v1.34.1 diff --git a/{{cookiecutter.project_slug}}/.travis.yml b/{{cookiecutter.project_slug}}/.travis.yml index cd703d3ad..78709191a 100644 --- a/{{cookiecutter.project_slug}}/.travis.yml +++ b/{{cookiecutter.project_slug}}/.travis.yml @@ -10,9 +10,9 @@ jobs: include: - name: "Linter" before_script: - - pip install -q flake8 + - pip install -q ruff script: - - "flake8" + - ruff check . - name: "Django Test" {%- if cookiecutter.use_docker == 'y' %} @@ -24,7 +24,7 @@ jobs: - docker compose -f local.yml run --rm django python manage.py migrate - docker compose -f local.yml up -d script: - - "docker compose -f local.yml run django pytest" + - docker compose -f local.yml run django pytest after_failure: - docker compose -f local.yml logs {%- else %} @@ -41,5 +41,5 @@ jobs: install: - pip install -r requirements/local.txt script: - - "pytest" + - pytest {%- endif %} diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_slug}}/README.md index ccf245a2f..cb7576892 100644 --- a/{{cookiecutter.project_slug}}/README.md +++ b/{{cookiecutter.project_slug}}/README.md @@ -3,7 +3,7 @@ {{ cookiecutter.description }} [![Built with Cookiecutter Django](https://img.shields.io/badge/built%20with-Cookiecutter%20Django-ff69b4.svg?logo=cookiecutter)](https://github.com/cookiecutter/cookiecutter-django/) -[![Black code style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) +[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) {%- if cookiecutter.open_source_license != "Not open source" %} diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml index 7e4c9aa9c..a056c71c3 100644 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -16,25 +16,6 @@ include = ["{{cookiecutter.project_slug}}/**"] omit = ["*/migrations/*", "*/tests/*"] plugins = ["django_coverage_plugin"] - -# ==== black ==== -[tool.black] -line-length = 119 -target-version = ['py311'] - - -# ==== isort ==== -[tool.isort] -profile = "black" -line_length = 119 -known_first_party = [ - "{{cookiecutter.project_slug}}", - "config", -] -skip = ["venv/"] -skip_glob = ["**/migrations/*.py"] - - # ==== mypy ==== [tool.mypy] python_version = "3.11" @@ -58,40 +39,6 @@ ignore_errors = true [tool.django-stubs] django_settings_module = "config.settings.test" - -# ==== PyLint ==== -[tool.pylint.MASTER] -load-plugins = [ - "pylint_django", -{%- if cookiecutter.use_celery == "y" %} - "pylint_celery", -{%- endif %} -] -django-settings-module = "config.settings.local" - -[tool.pylint.FORMAT] -max-line-length = 119 - -[tool.pylint."MESSAGES CONTROL"] -disable = [ - "missing-docstring", - "invalid-name", -] - -[tool.pylint.DESIGN] -max-parents = 13 - -[tool.pylint.TYPECHECK] -generated-members = [ - "REQUEST", - "acl_users", - "aq_parent", - "[a-zA-Z]+_set{1,2}", - "save", - "delete", -] - - # ==== djLint ==== [tool.djlint] blank_line_after_tag = "load,extends" @@ -110,3 +57,112 @@ indent_size = 2 [tool.djlint.js] indent_size = 2 + +[tool.ruff] +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "venv", + "*/migrations/*.py", + "staticfiles/*" +] +# Same as Django: https://github.com/cookiecutter/cookiecutter-django/issues/4792. +line-length = 88 +indent-width = 4 +target-version = "py311" + +[tool.ruff.lint] +select = [ + "F", + "E", + "W", + "C90", + "I", + "N", + "UP", + "YTT", + # "ANN", # flake8-annotations: we should support this in the future but 100+ errors atm + "ASYNC", + "S", + "BLE", + "FBT", + "B", + "A", + "COM", + "C4", + "DTZ", + "T10", + "DJ", + "EM", + "EXE", + "FA", + 'ISC', + "ICN", + "G", + 'INP', + 'PIE', + "T20", + 'PYI', + 'PT', + "Q", + "RSE", + "RET", + "SLF", + "SLOT", + "SIM", + "TID", + "TCH", + "INT", + # "ARG", # Unused function argument + "PTH", + "ERA", + "PD", + "PGH", + "PL", + "TRY", + "FLY", + # "NPY", + # "AIR", + "PERF", + # "FURB", + # "LOG", + "RUF" +] +ignore = [ + "S101", # Use of assert detected https://docs.astral.sh/ruff/rules/assert/ + "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` + "SIM102" # sometimes it's better to nest +] +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "auto" + +[tool.ruff.lint.isort] +force-single-line = true diff --git a/{{cookiecutter.project_slug}}/requirements/local.txt b/{{cookiecutter.project_slug}}/requirements/local.txt index cad007f47..da0e04943 100644 --- a/{{cookiecutter.project_slug}}/requirements/local.txt +++ b/{{cookiecutter.project_slug}}/requirements/local.txt @@ -28,15 +28,9 @@ sphinx-autobuild==2024.2.4 # https://github.com/GaretJax/sphinx-autobuild # Code quality # ------------------------------------------------------------------------------ -flake8==7.0.0 # https://github.com/PyCQA/flake8 -flake8-isort==6.1.1 # https://github.com/gforcada/flake8-isort +ruff==0.2.1 coverage==7.4.1 # https://github.com/nedbat/coveragepy -black==24.2.0 # https://github.com/psf/black djlint==1.34.1 # https://github.com/Riverside-Healthcare/djLint -pylint-django==2.5.5 # https://github.com/PyCQA/pylint-django -{%- if cookiecutter.use_celery == 'y' %} -pylint-celery==0.3 # https://github.com/PyCQA/pylint-celery -{%- endif %} pre-commit==3.6.1 # https://github.com/pre-commit/pre-commit # Django diff --git a/{{cookiecutter.project_slug}}/setup.cfg b/{{cookiecutter.project_slug}}/setup.cfg deleted file mode 100644 index 2412f1746..000000000 --- a/{{cookiecutter.project_slug}}/setup.cfg +++ /dev/null @@ -1,11 +0,0 @@ -# flake8 and pycodestyle don't support pyproject.toml -# https://github.com/PyCQA/flake8/issues/234 -# https://github.com/PyCQA/pycodestyle/issues/813 -[flake8] -max-line-length = 119 -exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv,.venv -extend-ignore = E203 - -[pycodestyle] -max-line-length = 119 -exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv,.venv diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py index 75917ab34..095b2cbbf 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py @@ -58,7 +58,7 @@ class TestUserAdmin: def force_allauth(self, settings): settings.DJANGO_ADMIN_FORCE_ALLAUTH = True # Reload the admin module to apply the setting change - import {{ cookiecutter.project_slug }}.users.admin as users_admin # pylint: disable=import-outside-toplevel + import {{ cookiecutter.project_slug }}.users.admin as users_admin try: reload(users_admin) From 0fc4ea6165ad98a99648e815ef615fbf85482260 Mon Sep 17 00:00:00 2001 From: Jelmer Draaijer Date: Tue, 13 Feb 2024 11:57:53 +0100 Subject: [PATCH 07/19] Apply ruff to codebase --- tests/test_cookiecutter_generation.py | 19 +++++------ .../config/api_router.py | 8 ++--- {{cookiecutter.project_slug}}/config/asgi.py | 6 ++-- .../config/settings/base.py | 11 +++--- .../config/settings/local.py | 26 +++++++++----- .../config/settings/production.py | 34 +++++++++++++------ .../config/settings/test.py | 5 +-- {{cookiecutter.project_slug}}/config/urls.py | 18 +++++++--- {{cookiecutter.project_slug}}/config/wsgi.py | 1 + {{cookiecutter.project_slug}}/docs/conf.py | 1 + {{cookiecutter.project_slug}}/manage.py | 3 +- .../merge_production_dotenvs_in_dotenv.py | 1 + .../tests/__init__.py | 0 .../{{cookiecutter.project_slug}}/__init__.py | 5 ++- .../{{cookiecutter.project_slug}}/conftest.py | 4 +-- .../contrib/sites/migrations/0001_initial.py | 5 +-- .../migrations/0002_alter_domain_unique.py | 3 +- .../0003_set_site_domain_and_name.py | 2 +- .../users/adapters.py | 16 +++++++-- .../users/admin.py | 6 ++-- .../users/api/serializers.py | 1 - .../users/api/views.py | 4 ++- .../users/apps.py | 6 ++-- .../users/managers.py | 9 +++-- .../users/migrations/0001_initial.py | 13 +++---- .../users/models.py | 13 ++++--- .../users/tests/factories.py | 5 +-- .../users/tests/test_admin.py | 24 ++++++------- .../users/tests/test_drf_urls.py | 12 +++++-- .../users/tests/test_drf_views.py | 4 +-- .../users/tests/test_forms.py | 2 +- .../users/tests/test_managers.py | 10 +++--- .../users/tests/test_swagger.py | 10 +++--- .../users/tests/test_tasks.py | 5 +-- .../users/tests/test_urls.py | 8 +++-- .../users/tests/test_views.py | 17 +++++----- .../users/urls.py | 8 ++--- .../users/views.py | 7 ++-- 38 files changed, 204 insertions(+), 128 deletions(-) create mode 100644 {{cookiecutter.project_slug}}/tests/__init__.py diff --git a/tests/test_cookiecutter_generation.py b/tests/test_cookiecutter_generation.py index 31d006bed..b744a986c 100755 --- a/tests/test_cookiecutter_generation.py +++ b/tests/test_cookiecutter_generation.py @@ -180,28 +180,25 @@ def test_project_generation(cookies, context, context_override): @pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id) -def test_flake8_passes(cookies, context_override): - """Generated project should pass flake8.""" +def test_ruff_check_passes(cookies, context_override): + """Generated project should pass ruff check.""" result = cookies.bake(extra_context=context_override) try: - sh.flake8(_cwd=str(result.project_path)) + sh.ruff("check", ".", _cwd=str(result.project_path)) except sh.ErrorReturnCode as e: pytest.fail(e.stdout.decode()) @auto_fixable @pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id) -def test_black_passes(cookies, context_override): - """Check whether generated project passes black style.""" +def test_ruff_format_passes(cookies, context_override): + """Check whether generated project passes ruff format.""" result = cookies.bake(extra_context=context_override) try: - sh.black( - "--check", - "--diff", - "--exclude", - "migrations", + sh.ruff( + "format", ".", _cwd=str(result.project_path), ) @@ -287,7 +284,7 @@ def test_travis_invokes_pytest(cookies, context, use_docker, expected_test_scrip with open(f"{result.project_path}/.travis.yml") as travis_yml: try: yml = yaml.safe_load(travis_yml)["jobs"]["include"] - assert yml[0]["script"] == ["flake8"] + assert yml[0]["script"] == ["ruff check ."] assert yml[1]["script"] == [expected_test_script] except yaml.YAMLError as e: pytest.fail(str(e)) diff --git a/{{cookiecutter.project_slug}}/config/api_router.py b/{{cookiecutter.project_slug}}/config/api_router.py index 743069b2c..d4de098fc 100644 --- a/{{cookiecutter.project_slug}}/config/api_router.py +++ b/{{cookiecutter.project_slug}}/config/api_router.py @@ -1,12 +1,10 @@ from django.conf import settings -from rest_framework.routers import DefaultRouter, SimpleRouter +from rest_framework.routers import DefaultRouter +from rest_framework.routers import SimpleRouter from {{ cookiecutter.project_slug }}.users.api.views import UserViewSet -if settings.DEBUG: - router = DefaultRouter() -else: - router = SimpleRouter() +router = DefaultRouter() if settings.DEBUG else SimpleRouter() router.register("users", UserViewSet) diff --git a/{{cookiecutter.project_slug}}/config/asgi.py b/{{cookiecutter.project_slug}}/config/asgi.py index c391bf87b..edfffbbc5 100644 --- a/{{cookiecutter.project_slug}}/config/asgi.py +++ b/{{cookiecutter.project_slug}}/config/asgi.py @@ -1,3 +1,4 @@ +# ruff: noqa """ ASGI config for {{ cookiecutter.project_name }} project. @@ -29,7 +30,7 @@ django_application = get_asgi_application() # application = HelloWorldApplication(application) # Import websocket application here, so apps from django_application are loaded first -from config.websocket import websocket_application # noqa isort:skip +from config.websocket import websocket_application async def application(scope, receive, send): @@ -38,4 +39,5 @@ async def application(scope, receive, send): elif scope["type"] == "websocket": await websocket_application(scope, receive, send) else: - raise NotImplementedError(f"Unknown scope type {scope['type']}") + msg = f"Unknown scope type {scope['type']}" + raise NotImplementedError(msg) diff --git a/{{cookiecutter.project_slug}}/config/settings/base.py b/{{cookiecutter.project_slug}}/config/settings/base.py index 3bf3a73cc..55a064e74 100644 --- a/{{cookiecutter.project_slug}}/config/settings/base.py +++ b/{{cookiecutter.project_slug}}/config/settings/base.py @@ -1,3 +1,4 @@ +# ruff: noqa: ERA001, E501 """Base settings to build other settings files upon.""" from pathlib import Path @@ -136,7 +137,9 @@ PASSWORD_HASHERS = [ ] # https://docs.djangoproject.com/en/dev/ref/settings/#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"}, @@ -209,7 +212,7 @@ TEMPLATES = [ "{{cookiecutter.project_slug}}.users.context_processors.allauth_settings", ], }, - } + }, ] # https://docs.djangoproject.com/en/dev/ref/settings/#form-renderer @@ -273,7 +276,7 @@ LOGGING = { "level": "DEBUG", "class": "logging.StreamHandler", "formatter": "verbose", - } + }, }, "root": {"level": "INFO", "handlers": ["console"]}, } @@ -379,7 +382,7 @@ WEBPACK_LOADER = { "STATS_FILE": BASE_DIR / "webpack-stats.json", "POLL_INTERVAL": 0.1, "IGNORE": [r".+\.hot-update.js", r".+\.map"], - } + }, } {%- endif %} diff --git a/{{cookiecutter.project_slug}}/config/settings/local.py b/{{cookiecutter.project_slug}}/config/settings/local.py index 0304d6cd4..f1edb514b 100644 --- a/{{cookiecutter.project_slug}}/config/settings/local.py +++ b/{{cookiecutter.project_slug}}/config/settings/local.py @@ -1,4 +1,10 @@ -from .base import * # noqa +# ruff: noqa: E501 +from .base import * # noqa: F403 +from .base import INSTALLED_APPS +from .base import MIDDLEWARE +{%- if cookiecutter.frontend_pipeline == 'Webpack' %} +from .base import WEBPACK_LOADER +{%- endif %} from .base import env # GENERAL @@ -11,7 +17,7 @@ SECRET_KEY = env( default="!!!SET DJANGO_SECRET_KEY!!!", ) # https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts -ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1"] +ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1"] # noqa: S104 # CACHES # ------------------------------------------------------------------------------ @@ -20,7 +26,7 @@ CACHES = { "default": { "BACKEND": "django.core.cache.backends.locmem.LocMemCache", "LOCATION": "", - } + }, } # EMAIL @@ -37,7 +43,9 @@ EMAIL_HOST = "localhost" EMAIL_PORT = 1025 {%- else -%} # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend -EMAIL_BACKEND = env("DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend") +EMAIL_BACKEND = env( + "DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend", +) {%- endif %} {%- if cookiecutter.use_whitenoise == 'y' %} @@ -45,15 +53,15 @@ EMAIL_BACKEND = env("DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.c # WhiteNoise # ------------------------------------------------------------------------------ # http://whitenoise.evans.io/en/latest/django.html#using-whitenoise-in-development -INSTALLED_APPS = ["whitenoise.runserver_nostatic"] + INSTALLED_APPS # noqa: F405 +INSTALLED_APPS = ["whitenoise.runserver_nostatic", *INSTALLED_APPS] {% endif %} # django-debug-toolbar # ------------------------------------------------------------------------------ # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#prerequisites -INSTALLED_APPS += ["debug_toolbar"] # noqa: F405 +INSTALLED_APPS += ["debug_toolbar"] # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#middleware -MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] # noqa: F405 +MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] # https://django-debug-toolbar.readthedocs.io/en/latest/configuration.html#debug-toolbar-config DEBUG_TOOLBAR_CONFIG = { "DISABLE_PANELS": ["debug_toolbar.panels.redirects.RedirectsPanel"], @@ -80,7 +88,7 @@ if env("USE_DOCKER") == "yes": # django-extensions # ------------------------------------------------------------------------------ # https://django-extensions.readthedocs.io/en/latest/installation_instructions.html#configuration -INSTALLED_APPS += ["django_extensions"] # noqa: F405 +INSTALLED_APPS += ["django_extensions"] {% if cookiecutter.use_celery == 'y' -%} # Celery @@ -96,7 +104,7 @@ CELERY_TASK_EAGER_PROPAGATES = True {%- if cookiecutter.frontend_pipeline == 'Webpack' %} # django-webpack-loader # ------------------------------------------------------------------------------ -WEBPACK_LOADER["DEFAULT"]["CACHE"] = not DEBUG # noqa: F405 +WEBPACK_LOADER["DEFAULT"]["CACHE"] = not DEBUG {%- endif %} # Your stuff... diff --git a/{{cookiecutter.project_slug}}/config/settings/production.py b/{{cookiecutter.project_slug}}/config/settings/production.py index 7f6293571..0cebe1d96 100644 --- a/{{cookiecutter.project_slug}}/config/settings/production.py +++ b/{{cookiecutter.project_slug}}/config/settings/production.py @@ -1,3 +1,4 @@ +# ruff: noqa: E501 {% if cookiecutter.use_sentry == 'y' -%} import logging @@ -12,7 +13,12 @@ from sentry_sdk.integrations.logging import LoggingIntegration from sentry_sdk.integrations.redis import RedisIntegration {% endif -%} -from .base import * # noqa +from .base import * # noqa: F403 +from .base import DATABASES +from .base import INSTALLED_APPS +{%- if cookiecutter.use_drf == "y" %} +from .base import SPECTACULAR_SETTINGS +{%- endif %} from .base import env # GENERAL @@ -24,7 +30,7 @@ ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["{{ cookiecutter.domai # DATABASES # ------------------------------------------------------------------------------ -DATABASES["default"]["CONN_MAX_AGE"] = env.int("CONN_MAX_AGE", default=60) # noqa: F405 +DATABASES["default"]["CONN_MAX_AGE"] = env.int("CONN_MAX_AGE", default=60) # CACHES # ------------------------------------------------------------------------------ @@ -38,7 +44,7 @@ CACHES = { # https://github.com/jazzband/django-redis#memcached-exceptions-behavior "IGNORE_EXCEPTIONS": True, }, - } + }, } # SECURITY @@ -56,17 +62,23 @@ CSRF_COOKIE_SECURE = True # TODO: set this to 60 seconds first and then to 518400 once you prove the former works SECURE_HSTS_SECONDS = 60 # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-include-subdomains -SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool("DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS", default=True) +SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool( + "DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS", + default=True, +) # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-preload SECURE_HSTS_PRELOAD = env.bool("DJANGO_SECURE_HSTS_PRELOAD", default=True) # https://docs.djangoproject.com/en/dev/ref/middleware/#x-content-type-options-nosniff -SECURE_CONTENT_TYPE_NOSNIFF = env.bool("DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", default=True) +SECURE_CONTENT_TYPE_NOSNIFF = env.bool( + "DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", + default=True, +) {% if cookiecutter.cloud_provider != 'None' -%} # STORAGES # ------------------------------------------------------------------------------ # https://django-storages.readthedocs.io/en/latest/#installation -INSTALLED_APPS += ["storages"] # noqa: F405 +INSTALLED_APPS += ["storages"] {%- endif -%} {% if cookiecutter.cloud_provider == 'AWS' %} # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings @@ -197,7 +209,7 @@ ADMIN_URL = env("DJANGO_ADMIN_URL") # Anymail # ------------------------------------------------------------------------------ # https://anymail.readthedocs.io/en/stable/installation/#installing-anymail -INSTALLED_APPS += ["anymail"] # noqa: F405 +INSTALLED_APPS += ["anymail"] # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend # https://anymail.readthedocs.io/en/stable/installation/#anymail-settings-reference {%- if cookiecutter.mail_service == 'Mailgun' %} @@ -273,7 +285,7 @@ COMPRESS_STORAGE = "compressor.storage.GzipCompressorFileStorage" COMPRESS_STORAGE = STORAGES["staticfiles"]["BACKEND"] {%- endif %} # https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_URL -COMPRESS_URL = STATIC_URL{% if cookiecutter.use_whitenoise == 'y' or cookiecutter.cloud_provider == 'None' %} # noqa: F405{% endif %} +COMPRESS_URL = STATIC_URL{% if cookiecutter.use_whitenoise == 'y' or cookiecutter.cloud_provider == 'None' %}{% endif %} {%- if cookiecutter.use_whitenoise == 'y' %} # https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_OFFLINE COMPRESS_OFFLINE = True # Offline compression is required when using Whitenoise @@ -291,7 +303,7 @@ COMPRESS_FILTERS = { # Collectfast # ------------------------------------------------------------------------------ # https://github.com/antonagestam/collectfast#installation -INSTALLED_APPS = ["collectfast"] + INSTALLED_APPS # noqa: F405 +INSTALLED_APPS = ["collectfast", *INSTALLED_APPS] {% endif %} # LOGGING # ------------------------------------------------------------------------------ @@ -351,7 +363,7 @@ LOGGING = { "level": "DEBUG", "class": "logging.StreamHandler", "formatter": "verbose", - } + }, }, "root": {"level": "INFO", "handlers": ["console"]}, "loggers": { @@ -403,7 +415,7 @@ sentry_sdk.init( # django-rest-framework # ------------------------------------------------------------------------------- # Tools that generate code samples can use SERVERS to point to the correct domain -SPECTACULAR_SETTINGS["SERVERS"] = [ # noqa: F405 +SPECTACULAR_SETTINGS["SERVERS"] = [ {"url": "https://{{ cookiecutter.domain_name }}", "description": "Production server"}, ] diff --git a/{{cookiecutter.project_slug}}/config/settings/test.py b/{{cookiecutter.project_slug}}/config/settings/test.py index 7c54e0217..696b48710 100644 --- a/{{cookiecutter.project_slug}}/config/settings/test.py +++ b/{{cookiecutter.project_slug}}/config/settings/test.py @@ -2,7 +2,8 @@ With these settings, tests run faster. """ -from .base import * # noqa +from .base import * # noqa: F403 +from .base import TEMPLATES from .base import env # GENERAL @@ -27,7 +28,7 @@ EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" # DEBUGGING FOR TEMPLATES # ------------------------------------------------------------------------------ -TEMPLATES[0]["OPTIONS"]["debug"] = True # type: ignore # noqa: F405 +TEMPLATES[0]["OPTIONS"]["debug"] = True # type: ignore[index] # MEDIA # ------------------------------------------------------------------------------ diff --git a/{{cookiecutter.project_slug}}/config/urls.py b/{{cookiecutter.project_slug}}/config/urls.py index 7c5ad1a7e..5d9301b67 100644 --- a/{{cookiecutter.project_slug}}/config/urls.py +++ b/{{cookiecutter.project_slug}}/config/urls.py @@ -1,27 +1,37 @@ +# ruff: noqa from django.conf import settings from django.conf.urls.static import static from django.contrib import admin {%- if cookiecutter.use_async == 'y' %} from django.contrib.staticfiles.urls import staticfiles_urlpatterns {%- endif %} -from django.urls import include, path +from django.urls import include +from django.urls import path from django.views import defaults as default_views from django.views.generic import TemplateView {%- if cookiecutter.use_drf == 'y' %} -from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView +from drf_spectacular.views import SpectacularAPIView +from drf_spectacular.views import SpectacularSwaggerView from rest_framework.authtoken.views import obtain_auth_token {%- endif %} urlpatterns = [ path("", TemplateView.as_view(template_name="pages/home.html"), name="home"), - path("about/", TemplateView.as_view(template_name="pages/about.html"), name="about"), + path( + "about/", + TemplateView.as_view(template_name="pages/about.html"), + name="about", + ), # Django Admin, use {% raw %}{% url 'admin:index' %}{% endraw %} path(settings.ADMIN_URL, admin.site.urls), # User management path("users/", include("{{ cookiecutter.project_slug }}.users.urls", namespace="users")), path("accounts/", include("allauth.urls")), # Your stuff: custom urls includes go here -] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + # ... + # Media files + *static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT), +] {%- if cookiecutter.use_async == 'y' %} if settings.DEBUG: # Static file serving when using Gunicorn + Uvicorn for local web socket development diff --git a/{{cookiecutter.project_slug}}/config/wsgi.py b/{{cookiecutter.project_slug}}/config/wsgi.py index 1dbd8a8d8..73a6cddcb 100644 --- a/{{cookiecutter.project_slug}}/config/wsgi.py +++ b/{{cookiecutter.project_slug}}/config/wsgi.py @@ -1,3 +1,4 @@ +# ruff: noqa """ WSGI config for {{ cookiecutter.project_name }} project. diff --git a/{{cookiecutter.project_slug}}/docs/conf.py b/{{cookiecutter.project_slug}}/docs/conf.py index c640e1c63..40d59dbbb 100644 --- a/{{cookiecutter.project_slug}}/docs/conf.py +++ b/{{cookiecutter.project_slug}}/docs/conf.py @@ -1,3 +1,4 @@ +# ruff: noqa # Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full diff --git a/{{cookiecutter.project_slug}}/manage.py b/{{cookiecutter.project_slug}}/manage.py index c44cc826d..a39871814 100755 --- a/{{cookiecutter.project_slug}}/manage.py +++ b/{{cookiecutter.project_slug}}/manage.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# ruff: noqa import os import sys from pathlib import Path @@ -13,7 +14,7 @@ if __name__ == "__main__": # issue is really that Django is missing to avoid masking other # exceptions on Python 2. try: - import django # noqa + import django except ImportError: raise ImportError( "Couldn't import Django. Are you sure it's installed and " diff --git a/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py b/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py index 35139fb2e..c83ed7166 100644 --- a/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py +++ b/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py @@ -1,3 +1,4 @@ +# ruff: noqa import os from collections.abc import Sequence from pathlib import Path diff --git a/{{cookiecutter.project_slug}}/tests/__init__.py b/{{cookiecutter.project_slug}}/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py index 150a914ee..fb6532709 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py @@ -1,2 +1,5 @@ __version__ = "{{ cookiecutter.version }}" -__version_info__ = tuple(int(num) if num.isdigit() else num for num in __version__.replace("-", ".", 1).split(".")) +__version_info__ = tuple( + int(num) if num.isdigit() else num + for num in __version__.replace("-", ".", 1).split(".") +) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/conftest.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/conftest.py index 7095a4714..98efcd75e 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/conftest.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/conftest.py @@ -5,10 +5,10 @@ from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory @pytest.fixture(autouse=True) -def media_storage(settings, tmpdir): +def _media_storage(settings, tmpdir) -> None: settings.MEDIA_ROOT = tmpdir.strpath -@pytest.fixture +@pytest.fixture() def user(db) -> User: return UserFactory() diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0001_initial.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0001_initial.py index 304cd6d7c..fd76afb25 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0001_initial.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0001_initial.py @@ -1,6 +1,7 @@ import django.contrib.sites.models from django.contrib.sites.models import _simple_domain_name_validator -from django.db import migrations, models +from django.db import migrations +from django.db import models class Migration(migrations.Migration): @@ -38,5 +39,5 @@ class Migration(migrations.Migration): }, bases=(models.Model,), managers=[("objects", django.contrib.sites.models.SiteManager())], - ) + ), ] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0002_alter_domain_unique.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0002_alter_domain_unique.py index 2c8d6dac0..4a44a6a92 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0002_alter_domain_unique.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0002_alter_domain_unique.py @@ -1,5 +1,6 @@ import django.contrib.sites.models -from django.db import migrations, models +from django.db import migrations +from django.db import models class Migration(migrations.Migration): diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0003_set_site_domain_and_name.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0003_set_site_domain_and_name.py index e1822375b..85ee2d9c1 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0003_set_site_domain_and_name.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0003_set_site_domain_and_name.py @@ -23,7 +23,7 @@ def _update_or_create_site_with_sequence(site_model, connection, domain, name): # site is created. # To avoid this, we need to manually update DB sequence and make sure it's # greater than the maximum value. - max_id = site_model.objects.order_by('-id').first().id + max_id = site_model.objects.order_by("-id").first().id with connection.cursor() as cursor: cursor.execute("SELECT last_value from django_site_id_seq") (current_id,) = cursor.fetchone() diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py index f9ae43a8e..484f686ad 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py @@ -5,10 +5,11 @@ import typing from allauth.account.adapter import DefaultAccountAdapter from allauth.socialaccount.adapter import DefaultSocialAccountAdapter from django.conf import settings -from django.http import HttpRequest if typing.TYPE_CHECKING: from allauth.socialaccount.models import SocialLogin + from django.http import HttpRequest + from {{cookiecutter.project_slug}}.users.models import User @@ -18,10 +19,19 @@ class AccountAdapter(DefaultAccountAdapter): class SocialAccountAdapter(DefaultSocialAccountAdapter): - def is_open_for_signup(self, request: HttpRequest, sociallogin: SocialLogin) -> bool: + def is_open_for_signup( + self, + request: HttpRequest, + sociallogin: SocialLogin, + ) -> bool: return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True) - def populate_user(self, request: HttpRequest, sociallogin: SocialLogin, data: dict[str, typing.Any]) -> User: + def populate_user( + self, + request: HttpRequest, + sociallogin: SocialLogin, + data: dict[str, typing.Any], + ) -> User: """ Populates user information from social provider info. diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py index 7fd49fa92..9d6c7562d 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py @@ -1,10 +1,12 @@ from django.conf import settings from django.contrib import admin from django.contrib.auth import admin as auth_admin -from django.contrib.auth import get_user_model, decorators +from django.contrib.auth import decorators +from django.contrib.auth import get_user_model from django.utils.translation import gettext_lazy as _ -from {{ cookiecutter.project_slug }}.users.forms import UserAdminChangeForm, UserAdminCreationForm +from {{ cookiecutter.project_slug }}.users.forms import UserAdminChangeForm +from {{ cookiecutter.project_slug }}.users.forms import UserAdminCreationForm User = get_user_model() diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py index 0872d06f4..ef2adb911 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py @@ -3,7 +3,6 @@ from rest_framework import serializers from {{ cookiecutter.project_slug }}.users.models import User as UserType - User = get_user_model() diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/views.py index 508431e4c..8bdf24b0b 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/views.py @@ -1,7 +1,9 @@ from django.contrib.auth import get_user_model from rest_framework import status from rest_framework.decorators import action -from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, UpdateModelMixin +from rest_framework.mixins import ListModelMixin +from rest_framework.mixins import RetrieveModelMixin +from rest_framework.mixins import UpdateModelMixin from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py index 92e7a74ec..5c3d4fe08 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py @@ -1,3 +1,5 @@ +import contextlib + from django.apps import AppConfig from django.utils.translation import gettext_lazy as _ @@ -7,7 +9,5 @@ class UsersConfig(AppConfig): verbose_name = _("Users") def ready(self): - try: + with contextlib.suppress(ImportError): import {{ cookiecutter.project_slug }}.users.signals # noqa: F401 - except ImportError: - pass diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/managers.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/managers.py index 03ac29548..c75c0e970 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/managers.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/managers.py @@ -15,7 +15,8 @@ class UserManager(DjangoUserManager["User"]): Create and save a user with the given email and password. """ if not email: - raise ValueError("The given email must be set") + msg = "The given email must be set" + raise ValueError(msg) email = self.normalize_email(email) user = self.model(email=email, **extra_fields) user.password = make_password(password) @@ -32,8 +33,10 @@ class UserManager(DjangoUserManager["User"]): extra_fields.setdefault("is_superuser", True) if extra_fields.get("is_staff") is not True: - raise ValueError("Superuser must have is_staff=True.") + msg = "Superuser must have is_staff=True." + raise ValueError(msg) if extra_fields.get("is_superuser") is not True: - raise ValueError("Superuser must have is_superuser=True.") + msg = "Superuser must have is_superuser=True." + raise ValueError(msg) return self._create_user(email, password, **extra_fields) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/0001_initial.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/0001_initial.py index 58a439c5d..cee6676bc 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/0001_initial.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/0001_initial.py @@ -1,7 +1,8 @@ import django.contrib.auth.models import django.contrib.auth.validators -from django.db import migrations, models import django.utils.timezone +from django.db import migrations +from django.db import models import {{cookiecutter.project_slug}}.users.models @@ -31,7 +32,7 @@ class Migration(migrations.Migration): ( "last_login", models.DateTimeField( - blank=True, null=True, verbose_name="last login" + blank=True, null=True, verbose_name="last login", ), ), ( @@ -61,14 +62,14 @@ class Migration(migrations.Migration): ( "email", models.EmailField( - blank=True, max_length=254, verbose_name="email address" + blank=True, max_length=254, verbose_name="email address", ), ), {%- else %} ( "email", models.EmailField( - unique=True, max_length=254, verbose_name="email address" + unique=True, max_length=254, verbose_name="email address", ), ), {%- endif %} @@ -91,13 +92,13 @@ 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", ), ), ( "name", models.CharField( - blank=True, max_length=255, verbose_name="Name of User" + blank=True, max_length=255, verbose_name="Name of User", ), ), ( diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py index ccb6b78ae..fd78c26a8 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py @@ -1,9 +1,12 @@ {%- if cookiecutter.username_type == "email" %} from typing import ClassVar -{%- endif %} +{% endif -%} from django.contrib.auth.models import AbstractUser -from django.db.models import CharField{% if cookiecutter.username_type == "email" %}, EmailField{% endif %} +from django.db.models import CharField +{%- if cookiecutter.username_type == "email" %} +from django.db.models import EmailField +{%- endif %} from django.urls import reverse from django.utils.translation import gettext_lazy as _ {%- if cookiecutter.username_type == "email" %} @@ -21,11 +24,11 @@ class User(AbstractUser): # First and last name do not cover name patterns around the globe name = CharField(_("Name of User"), blank=True, max_length=255) - first_name = None # type: ignore - last_name = None # type: ignore + first_name = None # type: ignore[assignment] + last_name = None # type: ignore[assignment] {%- if cookiecutter.username_type == "email" %} email = EmailField(_("email address"), unique=True) - username = None # type: ignore + username = None # type: ignore[assignment] USERNAME_FIELD = "email" REQUIRED_FIELDS = [] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py index bebd8adcf..f614681bb 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py @@ -2,7 +2,8 @@ from collections.abc import Sequence from typing import Any from django.contrib.auth import get_user_model -from factory import Faker, post_generation +from factory import Faker +from factory import post_generation from factory.django import DjangoModelFactory @@ -14,7 +15,7 @@ class UserFactory(DjangoModelFactory): name = Faker("name") @post_generation - def password(self, create: bool, extracted: Sequence[Any], **kwargs): + def password(self, create: bool, extracted: Sequence[Any], **kwargs): # noqa: FBT001 password = ( extracted if extracted diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py index 095b2cbbf..f802b8ba1 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py @@ -1,3 +1,5 @@ +import contextlib +from http import HTTPStatus from importlib import reload import pytest @@ -13,17 +15,17 @@ class TestUserAdmin: def test_changelist(self, admin_client): url = reverse("admin:users_user_changelist") response = admin_client.get(url) - assert response.status_code == 200 + assert response.status_code == HTTPStatus.OK def test_search(self, admin_client): url = reverse("admin:users_user_changelist") response = admin_client.get(url, data={"q": "test"}) - assert response.status_code == 200 + assert response.status_code == HTTPStatus.OK def test_add(self, admin_client): url = reverse("admin:users_user_add") response = admin_client.get(url) - assert response.status_code == 200 + assert response.status_code == HTTPStatus.OK response = admin_client.post( url, @@ -37,7 +39,7 @@ class TestUserAdmin: "password2": "My_R@ndom-P@ssw0rd", }, ) - assert response.status_code == 302 + assert response.status_code == HTTPStatus.FOUND {%- if cookiecutter.username_type == "email" %} assert User.objects.filter(email="new-admin@example.com").exists() {%- else %} @@ -52,21 +54,19 @@ class TestUserAdmin: {%- endif %} url = reverse("admin:users_user_change", kwargs={"object_id": user.pk}) response = admin_client.get(url) - assert response.status_code == 200 + assert response.status_code == HTTPStatus.OK - @pytest.fixture - def force_allauth(self, settings): + @pytest.fixture() + def _force_allauth(self, settings): settings.DJANGO_ADMIN_FORCE_ALLAUTH = True # Reload the admin module to apply the setting change import {{ cookiecutter.project_slug }}.users.admin as users_admin - try: + with contextlib.suppress(admin.sites.AlreadyRegistered): reload(users_admin) - except admin.sites.AlreadyRegistered: - pass - @pytest.mark.django_db - @pytest.mark.usefixtures("force_allauth") + @pytest.mark.django_db() + @pytest.mark.usefixtures("_force_allauth") def test_allauth_login(self, rf, settings): request = rf.get("/fake-url") request.user = AnonymousUser() diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_urls.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_urls.py index 334ab1185..b445b611d 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_urls.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_urls.py @@ -1,14 +1,20 @@ -from django.urls import resolve, reverse +from django.urls import resolve +from django.urls import reverse from {{ cookiecutter.project_slug }}.users.models import User def test_user_detail(user: User): {%- if cookiecutter.username_type == "email" %} - assert reverse("api:user-detail", kwargs={"pk": user.pk}) == f"/api/users/{user.pk}/" + assert ( + reverse("api:user-detail", kwargs={"pk": user.pk}) == f"/api/users/{user.pk}/" + ) assert resolve(f"/api/users/{user.pk}/").view_name == "api:user-detail" {%- else %} - assert reverse("api:user-detail", kwargs={"username": user.username}) == f"/api/users/{user.username}/" + assert ( + reverse("api:user-detail", kwargs={"username": user.username}) + == f"/api/users/{user.username}/" + ) assert resolve(f"/api/users/{user.username}/").view_name == "api:user-detail" {%- endif %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_views.py index 90e84dc7d..955ebe4eb 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_views.py @@ -6,7 +6,7 @@ from {{ cookiecutter.project_slug }}.users.models import User class TestUserViewSet: - @pytest.fixture + @pytest.fixture() def api_rf(self) -> APIRequestFactory: return APIRequestFactory() @@ -26,7 +26,7 @@ class TestUserViewSet: view.request = request - response = view.me(request) # type: ignore + response = view.me(request) # type: ignore[call-arg, arg-type, misc] assert response.data == { {%- if cookiecutter.username_type == "email" %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py index ca624c89a..17d0d72a1 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py @@ -30,7 +30,7 @@ class TestUserAdminCreationForm: {%- endif %} "password1": user.password, "password2": user.password, - } + }, ) assert not form.is_valid() diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_managers.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_managers.py index f25af4ee2..e5e5f5a4b 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_managers.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_managers.py @@ -6,12 +6,12 @@ from django.core.management import call_command from {{ cookiecutter.project_slug }}.users.models import User -@pytest.mark.django_db +@pytest.mark.django_db() class TestUserManager: def test_create_user(self): user = User.objects.create_user( email="john@example.com", - password="something-r@nd0m!", + password="something-r@nd0m!", # noqa: S106 ) assert user.email == "john@example.com" assert not user.is_staff @@ -22,7 +22,7 @@ class TestUserManager: def test_create_superuser(self): user = User.objects.create_superuser( email="admin@example.com", - password="something-r@nd0m!", + password="something-r@nd0m!", # noqa: S106 ) assert user.email == "admin@example.com" assert user.is_staff @@ -32,12 +32,12 @@ class TestUserManager: def test_create_superuser_username_ignored(self): user = User.objects.create_superuser( email="test@example.com", - password="something-r@nd0m!", + password="something-r@nd0m!", # noqa: S106 ) assert user.username is None -@pytest.mark.django_db +@pytest.mark.django_db() def test_createsuperuser_command(): """Ensure createsuperuser command works with our custom manager.""" out = StringIO() diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_swagger.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_swagger.py index f97658b55..3081d1f65 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_swagger.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_swagger.py @@ -1,3 +1,5 @@ +from http import HTTPStatus + import pytest from django.urls import reverse @@ -5,17 +7,17 @@ from django.urls import reverse def test_swagger_accessible_by_admin(admin_client): url = reverse("api-docs") response = admin_client.get(url) - assert response.status_code == 200 + assert response.status_code == HTTPStatus.OK -@pytest.mark.django_db +@pytest.mark.django_db() def test_swagger_ui_not_accessible_by_normal_user(client): url = reverse("api-docs") response = client.get(url) - assert response.status_code == 403 + assert response.status_code == HTTPStatus.FORBIDDEN def test_api_schema_generated_successfully(admin_client): url = reverse("api-schema") response = admin_client.get(url) - assert response.status_code == 200 + assert response.status_code == HTTPStatus.OK diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_tasks.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_tasks.py index 41d5af292..d3f610139 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_tasks.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_tasks.py @@ -9,8 +9,9 @@ pytestmark = pytest.mark.django_db def test_user_count(settings): """A basic test to execute the get_users_count Celery task.""" - UserFactory.create_batch(3) + batch_size = 3 + UserFactory.create_batch(batch_size) settings.CELERY_TASK_ALWAYS_EAGER = True task_result = get_users_count.delay() assert isinstance(task_result, EagerResult) - assert task_result.result == 3 + assert task_result.result == batch_size diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py index a0d068890..aaacb05a1 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py @@ -1,4 +1,5 @@ -from django.urls import resolve, reverse +from django.urls import resolve +from django.urls import reverse from {{ cookiecutter.project_slug }}.users.models import User @@ -8,7 +9,10 @@ def test_detail(user: User): assert reverse("users:detail", kwargs={"pk": user.pk}) == f"/users/{user.pk}/" assert resolve(f"/users/{user.pk}/").view_name == "users:detail" {%- else %} - assert reverse("users:detail", kwargs={"username": user.username}) == f"/users/{user.username}/" + assert ( + reverse("users:detail", kwargs={"username": user.username}) + == f"/users/{user.username}/" + ) assert resolve(f"/users/{user.username}/").view_name == "users:detail" {%- endif %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py index 2c1027038..136daa402 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py @@ -1,10 +1,13 @@ +from http import HTTPStatus + import pytest from django.conf import settings from django.contrib import messages from django.contrib.auth.models import AnonymousUser from django.contrib.messages.middleware import MessageMiddleware from django.contrib.sessions.middleware import SessionMiddleware -from django.http import HttpRequest, HttpResponseRedirect +from django.http import HttpRequest +from django.http import HttpResponseRedirect from django.test import RequestFactory from django.urls import reverse from django.utils.translation import gettext_lazy as _ @@ -12,11 +15,9 @@ from django.utils.translation import gettext_lazy as _ from {{ cookiecutter.project_slug }}.users.forms import UserAdminChangeForm from {{ cookiecutter.project_slug }}.users.models import User from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory -from {{ cookiecutter.project_slug }}.users.views import ( - UserRedirectView, - UserUpdateView, - user_detail_view, -) +from {{ cookiecutter.project_slug }}.users.views import UserRedirectView +from {{ cookiecutter.project_slug }}.users.views import UserUpdateView +from {{ cookiecutter.project_slug }}.users.views import user_detail_view pytestmark = pytest.mark.django_db @@ -102,7 +103,7 @@ class TestUserDetailView: response = user_detail_view(request, username=user.username) {%- endif %} - assert response.status_code == 200 + assert response.status_code == HTTPStatus.OK def test_not_authenticated(self, user: User, rf: RequestFactory): request = rf.get("/fake-url/") @@ -116,5 +117,5 @@ class TestUserDetailView: login_url = reverse(settings.LOGIN_URL) assert isinstance(response, HttpResponseRedirect) - assert response.status_code == 302 + assert response.status_code == HTTPStatus.FOUND assert response.url == f"{login_url}?next=/fake-url/" diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py index 0ffca17aa..40719ed21 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py @@ -1,10 +1,8 @@ from django.urls import path -from {{ cookiecutter.project_slug }}.users.views import ( - user_detail_view, - user_redirect_view, - user_update_view, -) +from {{ cookiecutter.project_slug }}.users.views import user_detail_view +from {{ cookiecutter.project_slug }}.users.views import user_redirect_view +from {{ cookiecutter.project_slug }}.users.views import user_update_view app_name = "users" urlpatterns = [ diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py index 82498e630..6d26e9c75 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py @@ -3,7 +3,9 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.messages.views import SuccessMessageMixin from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from django.views.generic import DetailView, RedirectView, UpdateView +from django.views.generic import DetailView +from django.views.generic import RedirectView +from django.views.generic import UpdateView User = get_user_model() @@ -28,7 +30,8 @@ class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): success_message = _("Information successfully updated") def get_success_url(self): - assert self.request.user.is_authenticated # for mypy to know that the user is authenticated + # for mypy to know that the user is authenticated + assert self.request.user.is_authenticated return self.request.user.get_absolute_url() def get_object(self): From 9a5b9c2f53e8b5f685bb04592354778c07b170e9 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 13 Feb 2024 12:05:49 +0000 Subject: [PATCH 08/19] Update sentry-sdk from 1.40.3 to 1.40.4 --- {{cookiecutter.project_slug}}/requirements/production.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_slug}}/requirements/production.txt b/{{cookiecutter.project_slug}}/requirements/production.txt index b6e793e11..90865d0b5 100644 --- a/{{cookiecutter.project_slug}}/requirements/production.txt +++ b/{{cookiecutter.project_slug}}/requirements/production.txt @@ -8,7 +8,7 @@ psycopg[c]==3.1.18 # https://github.com/psycopg/psycopg Collectfast==2.2.0 # https://github.com/antonagestam/collectfast {%- endif %} {%- if cookiecutter.use_sentry == "y" %} -sentry-sdk==1.40.3 # https://github.com/getsentry/sentry-python +sentry-sdk==1.40.4 # https://github.com/getsentry/sentry-python {%- endif %} {%- if cookiecutter.use_docker == "n" and cookiecutter.windows == "y" %} hiredis==2.3.2 # https://github.com/redis/hiredis-py From bf9a861ddc34d1b746c8e4a3850389c80cccde64 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 14 Feb 2024 02:10:20 +0000 Subject: [PATCH 09/19] Release 2024.02.13 --- CHANGELOG.md | 19 +++++++++++++++++++ setup.py | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ee7a5895..bf6cbe02d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,25 @@ All enhancements and patches to Cookiecutter Django will be documented in this f +## 2024.02.13 + + +### Changed + +- Ruff linting & formatting ([#4834](https://github.com/cookiecutter/cookiecutter-django/pull/4834)) + +### Updated + +- Update uvicorn to 0.27.1 ([#4848](https://github.com/cookiecutter/cookiecutter-django/pull/4848)) + +- Update sentry-sdk to 1.40.4 ([#4858](https://github.com/cookiecutter/cookiecutter-django/pull/4858)) + +- Bump traefik to 2.11.0 ([#4857](https://github.com/cookiecutter/cookiecutter-django/pull/4857)) + +- Auto-update pre-commit hooks ([#4855](https://github.com/cookiecutter/cookiecutter-django/pull/4855)) + +- Update black to 24.2.0 ([#4853](https://github.com/cookiecutter/cookiecutter-django/pull/4853)) + ## 2024.02.12 diff --git a/setup.py b/setup.py index 7c129824d..bb08c3b44 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ except ImportError: from distutils.core import setup # We use calendar versioning -version = "2024.02.12" +version = "2024.02.13" with open("README.md") as readme_file: long_description = readme_file.read() From fd959eedd93b95e6200c51e226edaf7773182857 Mon Sep 17 00:00:00 2001 From: mpsantos Date: Fri, 16 Feb 2024 06:52:55 -0300 Subject: [PATCH 10/19] Added link to the ruff repository (#4866) --- {{cookiecutter.project_slug}}/requirements/local.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_slug}}/requirements/local.txt b/{{cookiecutter.project_slug}}/requirements/local.txt index da0e04943..fb0eda72c 100644 --- a/{{cookiecutter.project_slug}}/requirements/local.txt +++ b/{{cookiecutter.project_slug}}/requirements/local.txt @@ -28,7 +28,7 @@ sphinx-autobuild==2024.2.4 # https://github.com/GaretJax/sphinx-autobuild # Code quality # ------------------------------------------------------------------------------ -ruff==0.2.1 +ruff==0.2.1 # https://github.com/astral-sh/ruff coverage==7.4.1 # https://github.com/nedbat/coveragepy djlint==1.34.1 # https://github.com/Riverside-Healthcare/djLint pre-commit==3.6.1 # https://github.com/pre-commit/pre-commit From 6237573637d09406bdd93d564e6999d4ec3d3f89 Mon Sep 17 00:00:00 2001 From: Paul Wulff <121822604+mtmpaulwulff@users.noreply.github.com> Date: Fri, 16 Feb 2024 10:55:24 +0100 Subject: [PATCH 11/19] Speed up CI pytest by 30%: Change ci.yml to only build images relevant for running pytest (#4863) In order to run pytest we can constrain the build command to only build the images related to the django service. This mainly prevents the docs image from being built unnecessarily. This cuts run time of the Github Actions workflow from approx. 4min 30s to below 3min. --- {{cookiecutter.project_slug}}/.github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml index e39933fe1..414ee1e60 100644 --- a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +++ b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: - name: Run pre-commit uses: pre-commit/action@v3.0.0 - # With no caching at all the entire ci process takes 4m 30s to complete! + # With no caching at all the entire ci process takes 3m to complete! pytest: runs-on: ubuntu-latest {%- if cookiecutter.use_docker == 'n' %} @@ -69,7 +69,7 @@ jobs: {%- if cookiecutter.use_docker == 'y' %} - name: Build the Stack - run: docker compose -f local.yml build + run: docker compose -f local.yml build django - name: Run DB Migrations run: docker compose -f local.yml run --rm django python manage.py migrate From 80147bd3404d0938983b62c7031a89e6a0765f6e Mon Sep 17 00:00:00 2001 From: browniebroke Date: Fri, 16 Feb 2024 09:55:59 +0000 Subject: [PATCH 12/19] Update Contributors --- .github/contributors.json | 5 +++++ CONTRIBUTORS.md | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/.github/contributors.json b/.github/contributors.json index c95797b18..596f2af83 100644 --- a/.github/contributors.json +++ b/.github/contributors.json @@ -1518,5 +1518,10 @@ "name": "henningbra", "github_login": "henningbra", "twitter_username": "" + }, + { + "name": "Paul Wulff", + "github_login": "mtmpaulwulff", + "twitter_username": "" } ] \ No newline at end of file diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 7b778f439..9a59b6572 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1650,6 +1650,13 @@ Listed in alphabetical order. + + Paul Wulff + + mtmpaulwulff + + + Pawan Chaurasia From f77233d7c0940208eda61fc680d642e745175076 Mon Sep 17 00:00:00 2001 From: "pyup.io bot" Date: Fri, 16 Feb 2024 02:14:57 -0800 Subject: [PATCH 13/19] Update gitpython to 3.1.42 (#4864) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3de15dc6f..c0385c69b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,6 +21,6 @@ pyyaml==6.0.1 # Scripting # ------------------------------------------------------------------------------ PyGithub==2.2.0 -gitpython==3.1.41 +gitpython==3.1.42 jinja2==3.1.3 requests==2.31.0 From cb0e06dcbdc72f2e5e6c5a39e16601e2721b806a Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 17 Feb 2024 02:09:39 +0000 Subject: [PATCH 14/19] Release 2024.02.16 --- CHANGELOG.md | 11 +++++++++++ setup.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf6cbe02d..f0ef07835 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,17 @@ All enhancements and patches to Cookiecutter Django will be documented in this f +## 2024.02.16 + + +### Changed + +- Speed up GitHub CI for Docker setup ([#4863](https://github.com/cookiecutter/cookiecutter-django/pull/4863)) + +### Documentation + +- Add link to the ruff repository in requirements ([#4866](https://github.com/cookiecutter/cookiecutter-django/pull/4866)) + ## 2024.02.13 diff --git a/setup.py b/setup.py index bb08c3b44..b5b999779 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ except ImportError: from distutils.core import setup # We use calendar versioning -version = "2024.02.13" +version = "2024.02.16" with open("README.md") as readme_file: long_description = readme_file.read() From dea7316a629c18ac235e3ce26178fe48eb74cd4e Mon Sep 17 00:00:00 2001 From: "pyup.io bot" Date: Sat, 17 Feb 2024 02:25:34 -0800 Subject: [PATCH 15/19] Update tox to 4.13.0 (#4868) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c0385c69b..17a01d599 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ pre-commit==3.6.1 # Testing # ------------------------------------------------------------------------------ -tox==4.12.1 +tox==4.13.0 pytest==8.0.0 pytest-xdist==3.5.0 pytest-cookies==0.7.0 From 7db193d9fac5f862f546c13444b1e676a25c3783 Mon Sep 17 00:00:00 2001 From: "pyup.io bot" Date: Sat, 17 Feb 2024 02:39:05 -0800 Subject: [PATCH 16/19] Update pytest to 8.0.1 (#4870) * Update pytest from 8.0.0 to 8.0.1 * Update pytest from 8.0.0 to 8.0.1 --- requirements.txt | 2 +- {{cookiecutter.project_slug}}/requirements/local.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 17a01d599..077ce3f75 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ pre-commit==3.6.1 # Testing # ------------------------------------------------------------------------------ tox==4.13.0 -pytest==8.0.0 +pytest==8.0.1 pytest-xdist==3.5.0 pytest-cookies==0.7.0 pytest-instafail==0.5.0 diff --git a/{{cookiecutter.project_slug}}/requirements/local.txt b/{{cookiecutter.project_slug}}/requirements/local.txt index fb0eda72c..4c142ff66 100644 --- a/{{cookiecutter.project_slug}}/requirements/local.txt +++ b/{{cookiecutter.project_slug}}/requirements/local.txt @@ -15,7 +15,7 @@ watchfiles==0.21.0 # https://github.com/samuelcolvin/watchfiles # ------------------------------------------------------------------------------ mypy==1.7.1 # https://github.com/python/mypy django-stubs[compatible-mypy]==4.2.7 # https://github.com/typeddjango/django-stubs -pytest==8.0.0 # https://github.com/pytest-dev/pytest +pytest==8.0.1 # https://github.com/pytest-dev/pytest pytest-sugar==1.0.0 # https://github.com/Frozenball/pytest-sugar {%- if cookiecutter.use_drf == "y" %} djangorestframework-stubs[compatible-mypy]==3.14.5 # https://github.com/typeddjango/djangorestframework-stubs From 2ac513e0c1bc6c9e66fee619fe5db8a304daf307 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 18 Feb 2024 02:11:43 +0000 Subject: [PATCH 17/19] Release 2024.02.17 --- CHANGELOG.md | 7 +++++++ setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0ef07835..8d7e382e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ All enhancements and patches to Cookiecutter Django will be documented in this f +## 2024.02.17 + + +### Updated + +- Update pytest to 8.0.1 ([#4870](https://github.com/cookiecutter/cookiecutter-django/pull/4870)) + ## 2024.02.16 diff --git a/setup.py b/setup.py index b5b999779..6637e9e7b 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ except ImportError: from distutils.core import setup # We use calendar versioning -version = "2024.02.16" +version = "2024.02.17" with open("README.md") as readme_file: long_description = readme_file.read() From adebac5b88c337e1558ba7e90764db729b111edf Mon Sep 17 00:00:00 2001 From: "pyup.io bot" Date: Mon, 19 Feb 2024 08:30:11 -0800 Subject: [PATCH 18/19] Update sentry-sdk from 1.40.4 to 1.40.5 (#4876) --- {{cookiecutter.project_slug}}/requirements/production.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_slug}}/requirements/production.txt b/{{cookiecutter.project_slug}}/requirements/production.txt index 90865d0b5..4d96e86e1 100644 --- a/{{cookiecutter.project_slug}}/requirements/production.txt +++ b/{{cookiecutter.project_slug}}/requirements/production.txt @@ -8,7 +8,7 @@ psycopg[c]==3.1.18 # https://github.com/psycopg/psycopg Collectfast==2.2.0 # https://github.com/antonagestam/collectfast {%- endif %} {%- if cookiecutter.use_sentry == "y" %} -sentry-sdk==1.40.4 # https://github.com/getsentry/sentry-python +sentry-sdk==1.40.5 # https://github.com/getsentry/sentry-python {%- endif %} {%- if cookiecutter.use_docker == "n" and cookiecutter.windows == "y" %} hiredis==2.3.2 # https://github.com/redis/hiredis-py From 8c60674654fd6ce4a217e3adb86d915eee3fbc5e Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 20 Feb 2024 02:09:27 +0000 Subject: [PATCH 19/19] Release 2024.02.19 --- CHANGELOG.md | 7 +++++++ setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d7e382e3..1f9b07857 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ All enhancements and patches to Cookiecutter Django will be documented in this f +## 2024.02.19 + + +### Updated + +- Update sentry-sdk to 1.40.5 ([#4876](https://github.com/cookiecutter/cookiecutter-django/pull/4876)) + ## 2024.02.17 diff --git a/setup.py b/setup.py index 6637e9e7b..b29fc417a 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ except ImportError: from distutils.core import setup # We use calendar versioning -version = "2024.02.17" +version = "2024.02.19" with open("README.md") as readme_file: long_description = readme_file.read()