diff --git a/.github/contributors.json b/.github/contributors.json
index 190b81d38..e84337123 100644
--- a/.github/contributors.json
+++ b/.github/contributors.json
@@ -1302,5 +1302,85 @@
"name": "Abe Hanoka",
"github_login": "abe-101",
"twitter_username": "abe__101"
+ },
+ {
+ "name": "Adin Hodovic",
+ "github_login": "adinhodovic",
+ "twitter_username": ""
+ },
+ {
+ "name": "Leifur Halldor Asgeirsson",
+ "github_login": "leifurhauks",
+ "twitter_username": ""
+ },
+ {
+ "name": "David",
+ "github_login": "buckldav",
+ "twitter_username": ""
+ },
+ {
+ "name": "rguptar",
+ "github_login": "rguptar",
+ "twitter_username": ""
+ },
+ {
+ "name": "Omer-5",
+ "github_login": "Omer-5",
+ "twitter_username": ""
+ },
+ {
+ "name": "TAKAHASHI Shuuji",
+ "github_login": "shuuji3",
+ "twitter_username": ""
+ },
+ {
+ "name": "Thomas Booij",
+ "github_login": "ThomasBooij95",
+ "twitter_username": ""
+ },
+ {
+ "name": "Pamela Fox",
+ "github_login": "pamelafox",
+ "twitter_username": "pamelafox"
+ },
+ {
+ "name": "Robin",
+ "github_login": "Kaffeetasse",
+ "twitter_username": ""
+ },
+ {
+ "name": "Patrick Tran",
+ "github_login": "theptrk",
+ "twitter_username": ""
+ },
+ {
+ "name": "tildebox",
+ "github_login": "tildebox",
+ "twitter_username": ""
+ },
+ {
+ "name": "duffn",
+ "github_login": "duffn",
+ "twitter_username": ""
+ },
+ {
+ "name": "Delphine LEMIRE",
+ "github_login": "DelphineLemire",
+ "twitter_username": ""
+ },
+ {
+ "name": "Hoai-Thu Vuong",
+ "github_login": "thuvh",
+ "twitter_username": ""
+ },
+ {
+ "name": "Arkadiusz Michał Ryś",
+ "github_login": "arrys",
+ "twitter_username": ""
+ },
+ {
+ "name": "mpsantos",
+ "github_login": "mpsantos",
+ "twitter_username": ""
}
]
\ No newline at end of file
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 8922cd450..d95192f33 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -18,3 +18,56 @@ updates:
interval: "daily"
labels:
- "update"
+
+ # Enable version updates for Docker
+ # We need to specify each Dockerfile in a separate entry because Dependabot doesn't
+ # support wildcards or recursively checking subdirectories. Check this issue for updates:
+ # https://github.com/dependabot/dependabot-core/issues/2178
+ - package-ecosystem: "docker"
+ directory: "{{cookiecutter.project_slug}}/compose/local/django/"
+ schedule:
+ interval: "daily"
+ labels:
+ - "update"
+
+ - package-ecosystem: "docker"
+ directory: "{{cookiecutter.project_slug}}/compose/local/docs/"
+ schedule:
+ interval: "daily"
+ labels:
+ - "update"
+
+ - package-ecosystem: "docker"
+ directory: "{{cookiecutter.project_slug}}/compose/local/node/"
+ schedule:
+ interval: "daily"
+ labels:
+ - "update"
+
+ - package-ecosystem: "docker"
+ directory: "{{cookiecutter.project_slug}}/compose/production/aws/"
+ schedule:
+ interval: "daily"
+ labels:
+ - "update"
+
+ - package-ecosystem: "docker"
+ directory: "{{cookiecutter.project_slug}}/compose/production/django/"
+ schedule:
+ interval: "daily"
+ labels:
+ - "update"
+
+ - package-ecosystem: "docker"
+ directory: "{{cookiecutter.project_slug}}/compose/production/postgres/"
+ schedule:
+ interval: "daily"
+ labels:
+ - "update"
+
+ - package-ecosystem: "docker"
+ directory: "{{cookiecutter.project_slug}}/compose/production/traefik/"
+ schedule:
+ interval: "daily"
+ labels:
+ - "update"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d466f4032..c31be1b7a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,6 +2,7 @@ name: CI
on:
push:
+ branches: [ "master", "main" ]
pull_request:
concurrency:
@@ -9,17 +10,6 @@ concurrency:
cancel-in-progress: true
jobs:
- lint:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-python@v4
- with:
- python-version: "3.10"
- cache: pip
- - name: Run pre-commit
- uses: pre-commit/action@v3.0.0
-
tests:
strategy:
fail-fast: false
@@ -29,7 +19,7 @@ jobs:
- windows-latest
- macOS-latest
- name: "Run tests"
+ name: "pytest ${{ matrix.os }}"
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
@@ -49,10 +39,14 @@ jobs:
script:
- name: Basic
args: ""
- - name: Extended
- args: "use_celery=y use_drf=y frontend_pipeline=Gulp"
+ - name: Celery & DRF
+ args: "use_celery=y use_drf=y"
+ - name: Gulp
+ args: "frontend_pipeline=Gulp"
+ - name: Webpack
+ args: "frontend_pipeline=Webpack"
- name: "${{ matrix.script.name }} Docker"
+ name: "Docker ${{ matrix.script.name }}"
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: 1
@@ -74,12 +68,14 @@ jobs:
fail-fast: false
matrix:
script:
- - name: With Celery
+ - name: Celery
args: "use_celery=y frontend_pipeline='Django Compressor'"
- - name: With Gulp
- args: "frontend_pipeline='Gulp'"
+ - name: Gulp
+ args: "frontend_pipeline=Gulp"
+ - name: Webpack
+ args: "frontend_pipeline=Webpack"
- name: "${{ matrix.script.name }} Bare metal"
+ name: "Bare metal ${{ matrix.script.name }}"
runs-on: ubuntu-latest
services:
redis:
diff --git a/.github/workflows/update-contributors.yml b/.github/workflows/update-contributors.yml
index ee0c547e1..83c651a46 100644
--- a/.github/workflows/update-contributors.yml
+++ b/.github/workflows/update-contributors.yml
@@ -29,9 +29,11 @@ jobs:
pip install -r requirements.txt
- name: Update list
run: python scripts/update_contributors.py
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Commit changes
- uses: stefanzweifel/git-auto-commit-action@v4.15.3
+ uses: stefanzweifel/git-auto-commit-action@v4.16.0
with:
commit_message: Update Contributors
file_pattern: CONTRIBUTORS.md .github/contributors.json
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e31055bb1..438de52b4 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,30 +3,30 @@ default_stages: [commit]
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.3.0
+ rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: check-yaml
- repo: https://github.com/asottile/pyupgrade
- rev: v3.2.2
+ rev: v3.3.1
hooks:
- id: pyupgrade
args: [--py310-plus]
exclude: hooks/
- repo: https://github.com/psf/black
- rev: 22.10.0
+ rev: 23.1.0
hooks:
- id: black
- repo: https://github.com/PyCQA/isort
- rev: 5.10.1
+ rev: 5.12.0
hooks:
- id: isort
- repo: https://github.com/PyCQA/flake8
- rev: 5.0.4
+ rev: 6.0.0
hooks:
- id: flake8
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 88f2ab45b..00daff8e2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,412 @@ All enhancements and patches to Cookiecutter Django will be documented in this f
+## 2023.03.09
+
+### Fixed
+- Fix the omit configuration for coverage ([#4201](https://github.com/cookiecutter/cookiecutter-django/pull/4201))
+### Updated
+- Update ipdb to 0.13.13 ([#4202](https://github.com/cookiecutter/cookiecutter-django/pull/4202))
+
+## 2023.03.07
+
+### Updated
+- Update mypy to 1.1.1 ([#4196](https://github.com/cookiecutter/cookiecutter-django/pull/4196))
+- Update django-environ to 0.10.0 ([#4195](https://github.com/cookiecutter/cookiecutter-django/pull/4195))
+
+## 2023.03.04
+
+### Changed
+- Add option to serve media files locally using nginx ([#2457](https://github.com/cookiecutter/cookiecutter-django/pull/2457))
+### Documentation
+- Include contributing page to the docs ([#4144](https://github.com/cookiecutter/cookiecutter-django/pull/4144))
+### Updated
+- Update myst-parser to 0.19.1 ([#4193](https://github.com/cookiecutter/cookiecutter-django/pull/4193))
+- Update pytest to 7.2.2 ([#4191](https://github.com/cookiecutter/cookiecutter-django/pull/4191))
+- Update drf-spectacular to 0.26.0 ([#4192](https://github.com/cookiecutter/cookiecutter-django/pull/4192))
+
+## 2023.02.28
+
+### Updated
+- Update pre-commit to 3.1.1 ([#4188](https://github.com/cookiecutter/cookiecutter-django/pull/4188))
+
+## 2023.02.27
+
+### Updated
+- Update sentry-sdk to 1.16.0 ([#4187](https://github.com/cookiecutter/cookiecutter-django/pull/4187))
+
+## 2023.02.26
+
+### Changed
+- Fix readthedocs config file for generated project ([#4172](https://github.com/cookiecutter/cookiecutter-django/pull/4172))
+### Updated
+- Bump traefik from v2.2.11 to 2.9.8 ([#4164](https://github.com/cookiecutter/cookiecutter-django/pull/4164))
+- Update coverage to 7.2.1 ([#4186](https://github.com/cookiecutter/cookiecutter-django/pull/4186))
+
+## 2023.02.25
+
+### Changed
+- Run linting with pre-commit on GitLab ([#4150](https://github.com/cookiecutter/cookiecutter-django/pull/4150))
+### Fixed
+- Disable caching for linter job on GitHub actions ([#4166](https://github.com/cookiecutter/cookiecutter-django/pull/4166))
+### Documentation
+- Add instuction to run celery beat ([#4162](https://github.com/cookiecutter/cookiecutter-django/pull/4162))
+### Updated
+- Bump garland/aws-cli-docker from 1.15.47 to 1.16.140 ([#4136](https://github.com/cookiecutter/cookiecutter-django/pull/4136))
+- Update djangorestframework-stubs to 1.9.1 ([#4184](https://github.com/cookiecutter/cookiecutter-django/pull/4184))
+- Update whitenoise to 6.4.0 ([#4180](https://github.com/cookiecutter/cookiecutter-django/pull/4180))
+- Update django-stubs to 1.15.0 ([#4183](https://github.com/cookiecutter/cookiecutter-django/pull/4183))
+- Update django-crispy-forms to 2.0 ([#4158](https://github.com/cookiecutter/cookiecutter-django/pull/4158))
+- Update django-cors-headers to 3.14.0 ([#4181](https://github.com/cookiecutter/cookiecutter-django/pull/4181))
+- Update python-slugify to 8.0.1 ([#4178](https://github.com/cookiecutter/cookiecutter-django/pull/4178))
+- Update pre-commit to 3.1.0 ([#4176](https://github.com/cookiecutter/cookiecutter-django/pull/4176))
+- Update mypy to 1.0.1 ([#4168](https://github.com/cookiecutter/cookiecutter-django/pull/4168))
+- Update werkzeug to 2.2.3 ([#4160](https://github.com/cookiecutter/cookiecutter-django/pull/4160))
+- Update coverage to 7.2.0 ([#4177](https://github.com/cookiecutter/cookiecutter-django/pull/4177))
+- Update django to 4.0.10 ([#4159](https://github.com/cookiecutter/cookiecutter-django/pull/4159))
+- Update hiredis to 2.2.2 ([#4156](https://github.com/cookiecutter/cookiecutter-django/pull/4156))
+
+## 2023.02.17
+
+### Changed
+- Update version of github actions on the template project ([#4167](https://github.com/cookiecutter/cookiecutter-django/pull/4167))
+
+## 2023.02.09
+
+### Changed
+- Remove unused pip cache paths in GHA & add a note for pre-commit.ci ([#4151](https://github.com/cookiecutter/cookiecutter-django/pull/4151))
+### Updated
+- Update mypy to 0.991 ([#4106](https://github.com/cookiecutter/cookiecutter-django/pull/4106))
+
+## 2023.02.08
+
+### Updated
+- Update sphinx to 6.1.3 ([#4148](https://github.com/cookiecutter/cookiecutter-django/pull/4148))
+- Update redis to 4.5.1 ([#4147](https://github.com/cookiecutter/cookiecutter-django/pull/4147))
+
+## 2023.02.07
+
+### Updated
+- Bump postcss-preset-env from 7.8.3 to 8.0.1 ([#4115](https://github.com/cookiecutter/cookiecutter-django/pull/4115))
+- Bump sass-loader from 12.6.0 to 13.2.0 ([#4116](https://github.com/cookiecutter/cookiecutter-django/pull/4116))
+- Bump babel-loader from 8.3.0 to 9.1.2 ([#4117](https://github.com/cookiecutter/cookiecutter-django/pull/4117))
+- Bump postcss-loader from 6.2.1 to 7.0.2 ([#4114](https://github.com/cookiecutter/cookiecutter-django/pull/4114))
+- Bump webpack-cli from 4.10.0 to 5.0.1 ([#4118](https://github.com/cookiecutter/cookiecutter-django/pull/4118))
+- Update redis to 4.5.0 ([#4142](https://github.com/cookiecutter/cookiecutter-django/pull/4142))
+- Update sentry-sdk to 1.15.0 ([#4141](https://github.com/cookiecutter/cookiecutter-django/pull/4141))
+
+## 2023.02.06
+
+### Changed
+- Change `RequestFactory` to `APIRequestFactory` in tests for API views ([#4110](https://github.com/cookiecutter/cookiecutter-django/pull/4110))
+### Fixed
+- Fix django-webpack-loader setup when running tests ([#4128](https://github.com/cookiecutter/cookiecutter-django/pull/4128))
+### Documentation
+- Added AWS ECS Full Deployment Article to README ([#2630](https://github.com/cookiecutter/cookiecutter-django/pull/2630))
+### Updated
+- Update hiredis to 2.2.1 ([#4123](https://github.com/cookiecutter/cookiecutter-django/pull/4123))
+- Update tox to 4.4.4 ([#4133](https://github.com/cookiecutter/cookiecutter-django/pull/4133))
+- Update django to 4.0.9 ([#4134](https://github.com/cookiecutter/cookiecutter-django/pull/4134))
+- Update django-webpack-loader to 1.8.1 ([#4132](https://github.com/cookiecutter/cookiecutter-django/pull/4132))
+
+## 2023.02.05
+
+### Documentation
+- Add note about which service to request when running locally with Docker & Webpack or Gulp ([#4130](https://github.com/cookiecutter/cookiecutter-django/pull/4130))
+
+## 2023.02.03
+
+### Updated
+- Update pre-commit to 3.0.4 ([#4127](https://github.com/cookiecutter/cookiecutter-django/pull/4127))
+
+## 2023.02.02
+
+### Updated
+- Update python-slugify to 8.0.0 ([#4111](https://github.com/cookiecutter/cookiecutter-django/pull/4111))
+- Update pre-commit to 3.0.3 ([#4121](https://github.com/cookiecutter/cookiecutter-django/pull/4121))
+- Update black to 23.1.0 ([#4120](https://github.com/cookiecutter/cookiecutter-django/pull/4120))
+- Update black pre-commit hook ([#4122](https://github.com/cookiecutter/cookiecutter-django/pull/4122))
+
+## 2023.01.29
+
+### Changed
+- Add Webpack support ([#3623](https://github.com/cookiecutter/cookiecutter-django/pull/3623))
+- Remove `BrokenLinkEmailsMiddleware` ([#4112](https://github.com/cookiecutter/cookiecutter-django/pull/4112))
+
+## 2023.01.28
+
+### Changed
+- Refactor `merge_production_dotenvs_in_dotenv.py` ([#4105](https://github.com/cookiecutter/cookiecutter-django/pull/4105))
+### Updated
+- Update isort to 5.12.0 ([#4109](https://github.com/cookiecutter/cookiecutter-django/pull/4109))
+- Auto-update pre-commit hooks ([#4108](https://github.com/cookiecutter/cookiecutter-django/pull/4108))
+
+## 2023.01.27
+
+### Updated
+- Update django-stubs to 1.14.0 ([#4103](https://github.com/cookiecutter/cookiecutter-django/pull/4103))
+
+## 2023.01.26
+
+### Changed
+- Rename BASE_DIR_PATH to BASE_DIR ([#4102](https://github.com/cookiecutter/cookiecutter-django/pull/4102))
+### Updated
+- Update pre-commit to 3.0.1 ([#4104](https://github.com/cookiecutter/cookiecutter-django/pull/4104))
+- Update tox to 4.4.2 ([#4101](https://github.com/cookiecutter/cookiecutter-django/pull/4101))
+
+## 2023.01.25
+
+### Changed
+- Rename ROOT_DIR to BASE_DIR ([#4086](https://github.com/cookiecutter/cookiecutter-django/pull/4086))
+- Update postgres and redis to point to mini tiers ([#4099](https://github.com/cookiecutter/cookiecutter-django/pull/4099))
+### Updated
+- Update coverage to 7.1.0 ([#4100](https://github.com/cookiecutter/cookiecutter-django/pull/4100))
+
+## 2023.01.24
+
+### Updated
+- Update pre-commit to 3.0.0 ([#4098](https://github.com/cookiecutter/cookiecutter-django/pull/4098))
+
+## 2023.01.23
+
+### Updated
+- Update sentry-sdk to 1.14.0 ([#4096](https://github.com/cookiecutter/cookiecutter-django/pull/4096))
+
+## 2023.01.22
+
+### Updated
+- Update django-compressor to 4.3.1 ([#4094](https://github.com/cookiecutter/cookiecutter-django/pull/4094))
+
+## 2023.01.21
+
+### Updated
+- Update django-stubs to 1.13.2 ([#4093](https://github.com/cookiecutter/cookiecutter-django/pull/4093))
+
+## 2023.01.19
+
+### Fixed
+- Add sourcemaps support to Gulp ([#4089](https://github.com/cookiecutter/cookiecutter-django/pull/4089))
+### Updated
+- Update coverage to 7.0.5 ([#4092](https://github.com/cookiecutter/cookiecutter-django/pull/4092))
+- Update redis to 4.4.2 ([#4091](https://github.com/cookiecutter/cookiecutter-django/pull/4091))
+- Update requests to 2.28.2 ([#4090](https://github.com/cookiecutter/cookiecutter-django/pull/4090))
+- Update tox to 4.3.5 ([#4087](https://github.com/cookiecutter/cookiecutter-django/pull/4087))
+
+## 2023.01.17
+
+### Updated
+- Update tox to 4.3.3 ([#4081](https://github.com/cookiecutter/cookiecutter-django/pull/4081))
+
+## 2023.01.15
+
+### Updated
+- Update pytest to 7.2.1 ([#4077](https://github.com/cookiecutter/cookiecutter-django/pull/4077))
+- Update pytz to 2022.7.1 ([#4078](https://github.com/cookiecutter/cookiecutter-django/pull/4078))
+
+## 2023.01.12
+
+### Updated
+- Update sentry-sdk to 1.13.0 ([#4074](https://github.com/cookiecutter/cookiecutter-django/pull/4074))
+
+## 2023.01.11
+
+### Changed
+- Update Celery instructions in the documentation ([#4061](https://github.com/cookiecutter/cookiecutter-django/pull/4061))
+### Updated
+- Update tox to 4.2.7 ([#4073](https://github.com/cookiecutter/cookiecutter-django/pull/4073))
+
+## 2023.01.10
+
+### Changed
+- Add dump.rdb to gitignore ([#4062](https://github.com/cookiecutter/cookiecutter-django/pull/4062))
+### Fixed
+- Exclude `.venv` from code style checks ([#4069](https://github.com/cookiecutter/cookiecutter-django/pull/4069))
+### Updated
+- Update hiredis to 2.1.1 ([#4070](https://github.com/cookiecutter/cookiecutter-django/pull/4070))
+
+## 2023.01.08
+
+### Updated
+- Update redis to 4.4.1 ([#4068](https://github.com/cookiecutter/cookiecutter-django/pull/4068))
+- Update coverage to 7.0.4 ([#4067](https://github.com/cookiecutter/cookiecutter-django/pull/4067))
+
+## 2023.01.07
+
+### Updated
+- Update tox to 4.2.6 ([#4064](https://github.com/cookiecutter/cookiecutter-django/pull/4064))
+- Update django-storages to 1.13.2 ([#4057](https://github.com/cookiecutter/cookiecutter-django/pull/4057))
+- Update isort to 5.11.4 ([#4058](https://github.com/cookiecutter/cookiecutter-django/pull/4058))
+- Update rcssmin to 1.1.1 ([#4060](https://github.com/cookiecutter/cookiecutter-django/pull/4060))
+- Update django-compressor to 4.3 ([#4063](https://github.com/cookiecutter/cookiecutter-django/pull/4063))
+
+## 2023.01.06
+
+### Changed
+- Add `.git` to `.dockerignore` ([#4054](https://github.com/cookiecutter/cookiecutter-django/pull/4054))
+- Fix link and add non-Docker commands to testing page in the docs ([#4036](https://github.com/cookiecutter/cookiecutter-django/pull/4036))
+### Updated
+- Update tox to 4.2.3 ([#4051](https://github.com/cookiecutter/cookiecutter-django/pull/4051))
+
+## 2023.01.04
+
+### Changed
+- Fix typo on test settings ([#4049](https://github.com/cookiecutter/cookiecutter-django/pull/4049))
+### Updated
+- Update tox to 4.2.2 ([#4050](https://github.com/cookiecutter/cookiecutter-django/pull/4050))
+- Update tox to 4.2.1 ([#4046](https://github.com/cookiecutter/cookiecutter-django/pull/4046))
+- Update coverage to 7.0.3 ([#4047](https://github.com/cookiecutter/cookiecutter-django/pull/4047))
+
+## 2023.01.03
+
+### Updated
+- Update flake8-isort to 6.0.0 ([#4022](https://github.com/cookiecutter/cookiecutter-django/pull/4022))
+- Update tox to 4.1.3 ([#4041](https://github.com/cookiecutter/cookiecutter-django/pull/4041))
+- Update pillow to 9.4.0 ([#4040](https://github.com/cookiecutter/cookiecutter-django/pull/4040))
+- Update gitpython to 3.1.30 ([#4032](https://github.com/cookiecutter/cookiecutter-django/pull/4032))
+- Update coverage to 7.0.2 ([#4042](https://github.com/cookiecutter/cookiecutter-django/pull/4042))
+- Update whitenoise to 6.3.0 ([#4044](https://github.com/cookiecutter/cookiecutter-django/pull/4044))
+
+## 2022.12.29
+
+### Updated
+- Update tox to 4.1.0 ([#4035](https://github.com/cookiecutter/cookiecutter-django/pull/4035))
+- Update tox to 4.0.19 ([#4030](https://github.com/cookiecutter/cookiecutter-django/pull/4030))
+- Update django-allauth to 0.52.0 ([#4033](https://github.com/cookiecutter/cookiecutter-django/pull/4033))
+
+## 2022.12.26
+
+### Updated
+- Update tox to 4.0.17 ([#4027](https://github.com/cookiecutter/cookiecutter-django/pull/4027))
+- Update pre-commit to 2.21.0 ([#4026](https://github.com/cookiecutter/cookiecutter-django/pull/4026))
+
+## 2022.12.25
+
+### Updated
+- Auto-update pre-commit hooks ([#4021](https://github.com/cookiecutter/cookiecutter-django/pull/4021))
+
+## 2022.12.24
+
+### Updated
+- Update coverage to 7.0.1 ([#4024](https://github.com/cookiecutter/cookiecutter-django/pull/4024))
+
+## 2022.12.21
+
+### Changed
+- Retry when trying to store a Celery result in backend ([#3996](https://github.com/cookiecutter/cookiecutter-django/pull/3996))
+- Update image URL for build status shield badge ([#4018](https://github.com/cookiecutter/cookiecutter-django/pull/4018))
+### Updated
+- Update pytz to 2022.7 ([#4020](https://github.com/cookiecutter/cookiecutter-django/pull/4020))
+- Update ipdb to 0.13.11 ([#4019](https://github.com/cookiecutter/cookiecutter-django/pull/4019))
+- Update tox to 4.0.16 ([#4017](https://github.com/cookiecutter/cookiecutter-django/pull/4017))
+- Update sentry-sdk to 1.12.1 ([#4014](https://github.com/cookiecutter/cookiecutter-django/pull/4014))
+- Update coverage to 7.0.0 ([#4013](https://github.com/cookiecutter/cookiecutter-django/pull/4013))
+- Update django-anymail to 9.0 ([#4012](https://github.com/cookiecutter/cookiecutter-django/pull/4012))
+- Auto-update pre-commit hooks ([#4005](https://github.com/cookiecutter/cookiecutter-django/pull/4005))
+- Update isort to 5.11.3 ([#4010](https://github.com/cookiecutter/cookiecutter-django/pull/4010))
+- Update drf-spectacular to 0.25.1 ([#4009](https://github.com/cookiecutter/cookiecutter-django/pull/4009))
+- Update hiredis to 2.1.0 ([#4006](https://github.com/cookiecutter/cookiecutter-django/pull/4006))
+
+## 2022.12.13
+
+### Changed
+- Improve documentation for Getting started with Docker ([#4003](https://github.com/cookiecutter/cookiecutter-django/pull/4003))
+### Updated
+- Update isort to 5.11.1 ([#3999](https://github.com/cookiecutter/cookiecutter-django/pull/3999))
+- Auto-update pre-commit hooks ([#3998](https://github.com/cookiecutter/cookiecutter-django/pull/3998))
+- Update isort to 5.11.0 ([#3997](https://github.com/cookiecutter/cookiecutter-django/pull/3997))
+
+## 2022.12.10
+
+### Updated
+- Update tox to 4.0.5 ([#3993](https://github.com/cookiecutter/cookiecutter-django/pull/3993))
+- Auto-update pre-commit hooks ([#3991](https://github.com/cookiecutter/cookiecutter-django/pull/3991))
+
+## 2022.12.09
+
+### Changed
+- Remove bind option mounts for docker compose volumes ([#3981](https://github.com/cookiecutter/cookiecutter-django/pull/3981))
+### Updated
+- Update djangorestframework-stubs to 1.8.0 ([#3990](https://github.com/cookiecutter/cookiecutter-django/pull/3990))
+- Update black to 22.12.0 ([#3988](https://github.com/cookiecutter/cookiecutter-django/pull/3988))
+
+## 2022.12.08
+
+### Updated
+- Update tox to 4.0.3 ([#3987](https://github.com/cookiecutter/cookiecutter-django/pull/3987))
+- Update tox to 4.0.2 ([#3985](https://github.com/cookiecutter/cookiecutter-django/pull/3985))
+- Update django-stubs to 1.13.1 ([#3986](https://github.com/cookiecutter/cookiecutter-django/pull/3986))
+
+## 2022.12.07
+
+### Updated
+- Auto-update pre-commit hooks ([#3983](https://github.com/cookiecutter/cookiecutter-django/pull/3983))
+
+## 2022.12.06
+
+### Changed
+- Simplify production `DATABASES` setting to extend base definition ([#3969](https://github.com/cookiecutter/cookiecutter-django/pull/3969))
+### Fixed
+- Only set `SERVERS` for `drf-spectacular` in production ([#3609](https://github.com/cookiecutter/cookiecutter-django/pull/3609))
+### Updated
+- Update django-coverage-plugin to 3.0.0 ([#3979](https://github.com/cookiecutter/cookiecutter-django/pull/3979))
+- Bump stefanzweifel/git-auto-commit-action from 4.15.4 to 4.16.0 ([#3978](https://github.com/cookiecutter/cookiecutter-django/pull/3978))
+
+## 2022.12.04
+
+### Updated
+- Update redis to 4.4.0 ([#3977](https://github.com/cookiecutter/cookiecutter-django/pull/3977))
+- Update django-debug-toolbar to 3.8.1 ([#3976](https://github.com/cookiecutter/cookiecutter-django/pull/3976))
+
+## 2022.12.03
+
+### Updated
+- Auto-update pre-commit hooks ([#3975](https://github.com/cookiecutter/cookiecutter-django/pull/3975))
+
+## 2022.12.02
+
+### Updated
+- Update flake8 to 6.0.0 ([#3974](https://github.com/cookiecutter/cookiecutter-django/pull/3974))
+
+## 2022.11.30
+
+### Changed
+- Add Azure Storage as an option to serve static and media files ([#3967](https://github.com/cookiecutter/cookiecutter-django/pull/3967))
+### Updated
+- Auto-update pre-commit hooks ([#3970](https://github.com/cookiecutter/cookiecutter-django/pull/3970))
+
+## 2022.11.26
+
+### Changed
+- Fix typo in flower start for watching celery ([#3966](https://github.com/cookiecutter/cookiecutter-django/pull/3966))
+
+## 2022.11.24
+
+### Updated
+- Auto-update pre-commit hooks ([#3963](https://github.com/cookiecutter/cookiecutter-django/pull/3963))
+
+## 2022.11.23
+
+### Changed
+- Fix graceful shutdown of local dev containers and use watchfiles for beat + flower ([#3925](https://github.com/cookiecutter/cookiecutter-django/pull/3925))
+- feat(celery): Enable sending the sent task event by default ([#3961](https://github.com/cookiecutter/cookiecutter-django/pull/3961))
+### Updated
+- Bump stefanzweifel/git-auto-commit-action from 4.15.3 to 4.15.4 ([#3940](https://github.com/cookiecutter/cookiecutter-django/pull/3940))
+- Update django-model-utils to 4.3.1 ([#3948](https://github.com/cookiecutter/cookiecutter-django/pull/3948))
+- Update flake8-isort to 5.0.3 ([#3952](https://github.com/cookiecutter/cookiecutter-django/pull/3952))
+
+## 2022.11.22
+
+### Changed
+- Remove USE_L10N due to deprecation ([#3960](https://github.com/cookiecutter/cookiecutter-django/pull/3960))
+- Remove platform from compose file ([#3957](https://github.com/cookiecutter/cookiecutter-django/pull/3957))
+- feat(celery): Send task events for Celery by default ([#3959](https://github.com/cookiecutter/cookiecutter-django/pull/3959))
+### Updated
+- Update python-slugify to 7.0.0 ([#3950](https://github.com/cookiecutter/cookiecutter-django/pull/3950))
+- Update redis to 4.3.5 ([#3954](https://github.com/cookiecutter/cookiecutter-django/pull/3954))
+- Update sentry-sdk to 1.11.1 ([#3955](https://github.com/cookiecutter/cookiecutter-django/pull/3955))
+- Update uvicorn to 0.20.0 ([#3953](https://github.com/cookiecutter/cookiecutter-django/pull/3953))
+- Update tox to 3.27.1 ([#3945](https://github.com/cookiecutter/cookiecutter-django/pull/3945))
+
## 2022.11.11
### Updated
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 86ee9251c..f78278e34 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -166,6 +166,13 @@ Listed in alphabetical order.
Tim Claessens |
diff --git a/README.md b/README.md
index 650730849..28a9db8cb 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,13 @@
# Cookiecutter Django
-[](https://github.com/cookiecutter/cookiecutter-django/actions?query=workflow%3ACI)
+[](https://github.com/cookiecutter/cookiecutter-django/actions/workflows/ci.yml?query=branch%3Amaster)
[](https://cookiecutter-django.readthedocs.io/en/latest/?badge=latest)
+[](https://results.pre-commit.ci/latest/github/cookiecutter/cookiecutter-django/master)
+[](https://github.com/ambv/black)
+
[](https://pyup.io/repos/github/cookiecutter/cookiecutter-django/)
[](https://discord.gg/uFXweDQc5a)
[](https://www.codetriage.com/cookiecutter/cookiecutter-django)
-[](https://github.com/ambv/black)
Powered by [Cookiecutter](https://github.com/cookiecutter/cookiecutter), Cookiecutter Django is a framework for jumpstarting
production-ready Django projects quickly.
@@ -27,9 +29,9 @@ production-ready Django projects quickly.
- Registration via [django-allauth](https://github.com/pennersr/django-allauth)
- Comes with custom user model ready to go
- Optional basic ASGI setup for Websockets
-- Optional custom static build using Gulp and livereload
+- Optional custom static build using Gulp or Webpack
- Send emails via [Anymail](https://github.com/anymail/django-anymail) (using [Mailgun](http://www.mailgun.com/) by default or Amazon SES if AWS is selected cloud provider, but switchable)
-- Media storage using Amazon S3 or Google Cloud Storage
+- Media storage using Amazon S3, Google Cloud Storage, Azure Storage or nginx
- Docker support using [docker-compose](https://github.com/docker/compose) for development and production (using [Traefik](https://traefik.io/) with [LetsEncrypt](https://letsencrypt.org/) support)
- [Procfile](https://devcenter.heroku.com/articles/procfile) for deploying to Heroku
- Instructions for deploying to [PythonAnywhere](https://www.pythonanywhere.com/)
@@ -41,7 +43,7 @@ production-ready Django projects quickly.
*These features can be enabled during initial project setup.*
-- Serve static files from Amazon S3, Google Cloud Storage or [Whitenoise](https://whitenoise.readthedocs.io/)
+- Serve static files from Amazon S3, Google Cloud Storage, Azure Storage or [Whitenoise](https://whitenoise.readthedocs.io/)
- Configuration for [Celery](https://docs.celeryq.dev) and [Flower](https://github.com/mher/flower) (the latter in Docker setup only)
- Integration with [MailHog](https://github.com/mailhog/MailHog) for local email testing
- Integration with [Sentry](https://sentry.io/welcome/) for error logging
@@ -153,6 +155,7 @@ Answer the prompts with your own desired [options](http://cookiecutter-django.re
1 - None
2 - Django Compressor
3 - Gulp
+ 4 - Webpack
Choose from 1, 2, 3, 4 [1]: 1
use_celery [n]: y
use_mailhog [n]: n
@@ -237,6 +240,7 @@ experience better.
## Articles
- [Cookiecutter Django With Amazon RDS](https://haseeburrehman.com/posts/cookiecutter-django-with-amazon-rds/) - Apr, 2, 2021
+- [Complete Walkthrough: Blue/Green Deployment to AWS ECS using GitHub actions](https://github.com/Andrew-Chen-Wang/cookiecutter-django-ecs-github) - June 10, 2020
- [Using cookiecutter-django with Google Cloud Storage](https://ahhda.github.io/cloud/gce/django/2019/03/12/using-django-cookiecutter-cloud-storage.html) - Mar. 12, 2019
- [cookiecutter-django with Nginx, Route 53 and ELB](https://msaizar.com/blog/cookiecutter-django-nginx-route-53-and-elb/) - Feb. 12, 2018
- [cookiecutter-django and Amazon RDS](https://msaizar.com/blog/cookiecutter-django-and-amazon-rds/) - Feb. 7, 2018
diff --git a/cookiecutter.json b/cookiecutter.json
index ccdd0f174..0318db44e 100644
--- a/cookiecutter.json
+++ b/cookiecutter.json
@@ -28,6 +28,7 @@
"cloud_provider": [
"AWS",
"GCP",
+ "Azure",
"None"
],
"mail_service": [
@@ -46,7 +47,8 @@
"frontend_pipeline": [
"None",
"Django Compressor",
- "Gulp"
+ "Gulp",
+ "Webpack"
],
"use_celery": "n",
"use_mailhog": "n",
diff --git a/docs/conf.py b/docs/conf.py
index b53e6a7e7..b1a97750a 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -23,13 +23,13 @@ now = datetime.now()
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = []
+extensions = ["myst_parser"]
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# The suffix of source filenames.
-source_suffix = ".rst"
+source_suffix = [".rst", ".md"]
# The encoding of source files.
# source_encoding = 'utf-8-sig'
diff --git a/docs/contributing.md b/docs/contributing.md
new file mode 100644
index 000000000..66c1f98d3
--- /dev/null
+++ b/docs/contributing.md
@@ -0,0 +1,3 @@
+```{include} ../CONTRIBUTING.md
+
+```
diff --git a/docs/deployment-on-heroku.rst b/docs/deployment-on-heroku.rst
index e239b6593..71c6e11b2 100644
--- a/docs/deployment-on-heroku.rst
+++ b/docs/deployment-on-heroku.rst
@@ -12,13 +12,13 @@ Run these commands to deploy the project to Heroku:
heroku create --buildpack heroku/python
- heroku addons:create heroku-postgresql:hobby-dev
+ heroku addons:create heroku-postgresql:mini
# On Windows use double quotes for the time zone, e.g.
# heroku pg:backups schedule --at "02:00 America/Los_Angeles" DATABASE_URL
heroku pg:backups schedule --at '02:00 America/Los_Angeles' DATABASE_URL
heroku pg:promote DATABASE_URL
- heroku addons:create heroku-redis:hobby-dev
+ heroku addons:create heroku-redis:mini
# Assuming you chose Mailgun as mail service (see below for others)
heroku addons:create mailgun:starter
@@ -109,10 +109,10 @@ Or add the DSN for your account, if you already have one:
.. _Sentry add-on: https://elements.heroku.com/addons/sentry
-Gulp & Bootstrap compilation
-++++++++++++++++++++++++++++
+Gulp or Webpack
++++++++++++++++
-If you've opted for Gulp, you'll most likely need to setup
+If you've opted for Gulp or Webpack as frontend pipeline, you'll most likely need to setup
your app to use `multiple buildpacks`_: one for Python & one for Node.js:
.. code-block:: bash
@@ -121,8 +121,8 @@ your app to use `multiple buildpacks`_: one for Python & one for Node.js:
At time of writing, this should do the trick: during deployment,
the Heroku should run ``npm install`` and then ``npm build``,
-which runs Gulp in cookiecutter-django.
+which run the SASS compilation & JS bundling.
If things don't work, please refer to the Heroku docs.
-.. _multiple buildpacks: https://devcenter.heroku.com/articles/using-multiple-buildpacks-for-an-app
\ No newline at end of file
+.. _multiple buildpacks: https://devcenter.heroku.com/articles/using-multiple-buildpacks-for-an-app
diff --git a/docs/deployment-with-docker.rst b/docs/deployment-with-docker.rst
index fcce7e6f5..c1b8c6d7b 100644
--- a/docs/deployment-with-docker.rst
+++ b/docs/deployment-with-docker.rst
@@ -84,6 +84,32 @@ You can read more about this feature and how to configure it, at `Automatic HTTP
.. _Automatic HTTPS: https://docs.traefik.io/https/acme/
+.. _webpack-whitenoise-limitation:
+
+Webpack without Whitenoise limitation
+-------------------------------------
+
+If you opt for Webpack without Whitenoise, Webpack needs to know the static URL at build time, when running ``docker-compose build`` (See ``webpack/prod.config.js``). Depending on your setup, this URL may come from the following environment variables:
+
+- ``AWS_STORAGE_BUCKET_NAME``
+- ``DJANGO_AWS_S3_CUSTOM_DOMAIN``
+- ``DJANGO_GCP_STORAGE_BUCKET_NAME``
+- ``DJANGO_AZURE_CONTAINER_NAME``
+
+The Django settings are getting these values at runtime via the ``.envs/.production/.django`` file , but Docker does not read this file at build time, it only look for a ``.env`` in the root of the project. Failing to pass the values correctly will result in a page without CSS styles nor javascript.
+
+To solve this, you can either:
+
+1. merge all the env files into ``.env`` by running::
+
+ merge_production_dotenvs_in_dotenv.py
+
+2. create a ``.env`` file in the root of the project with just variables you need. You'll need to also define them in ``.envs/.production/.django`` (hence duplicating them).
+3. set these variables when running the build command::
+
+ DJANGO_AWS_S3_CUSTOM_DOMAIN=example.com docker-compose -f production.yml build``.
+
+None of these options are ideal, we're open to suggestions on how to improve this. If you think you have one, please open an issue or a pull request.
(Optional) Postgres Data Volume Modifications
---------------------------------------------
@@ -161,3 +187,7 @@ For status check, run::
supervisorctl status
+Media files without cloud provider
+----------------------------------
+
+If you chose no cloud provider and Docker, the media files will be served by an nginx service, from a ``production_django_media`` volume. Make sure to keep this around to avoid losing any media files.
diff --git a/docs/developing-locally-docker.rst b/docs/developing-locally-docker.rst
index 4bb5d9032..935e86020 100644
--- a/docs/developing-locally-docker.rst
+++ b/docs/developing-locally-docker.rst
@@ -3,9 +3,6 @@ Getting Up and Running Locally With Docker
.. index:: Docker
-The steps below will get you up and running with a local development environment.
-All of these commands assume you are in the root of your generated project.
-
.. note::
If you're new to Docker, please be aware that some resources are cached system-wide
@@ -19,10 +16,16 @@ Prerequisites
* Docker; if you don't have it yet, follow the `installation instructions`_;
* Docker Compose; refer to the official documentation for the `installation guide`_.
* Pre-commit; refer to the official documentation for the `pre-commit`_.
+* Cookiecutter; refer to the official GitHub repository of `Cookiecutter`_
.. _`installation instructions`: https://docs.docker.com/install/#supported-platforms
.. _`installation guide`: https://docs.docker.com/compose/install/
.. _`pre-commit`: https://pre-commit.com/#install
+.. _`Cookiecutter`: https://github.com/cookiecutter/cookiecutter
+
+Before Getting Started
+----------------------
+.. include:: generate-project-block.rst
Build the Stack
---------------
@@ -210,6 +213,11 @@ By default, it's enabled both in local and production environments (``local.yml`
.. _`Flower`: https://github.com/mher/flower
+Using Webpack or Gulp
+~~~~~~~~~~~~~~~~~~~~~
+
+When using Webpack or Gulp as the ``frontend_pipeline`` option, you should access your application at the address of the ``node`` service in order to see your correct styles. This is http://localhost:3000 by default. When using any of the other ``frontend_pipeline`` options, you should use the address of the ``django`` service, http://localhost:8000.
+
Developing locally with HTTPS
-----------------------------
diff --git a/docs/developing-locally.rst b/docs/developing-locally.rst
index c9d28ff73..fb66536fc 100644
--- a/docs/developing-locally.rst
+++ b/docs/developing-locally.rst
@@ -24,9 +24,8 @@ First things first.
$ source /bin/activate
-#. Install cookiecutter-django: ::
-
- $ cookiecutter gh:cookiecutter/cookiecutter-django
+#.
+ .. include:: generate-project-block.rst
#. Install development requirements: ::
@@ -43,6 +42,7 @@ First things first.
#. Create a new PostgreSQL database using createdb_: ::
$ createdb --username=postgres
+
``project_slug`` is what you have entered as the project_slug at the setup stage.
.. note::
@@ -141,21 +141,38 @@ In production, we have Mailgun_ configured to have your back!
Celery
------
-If the project is configured to use Celery as a task scheduler then by default tasks are set to run on the main thread
-when developing locally. If you have the appropriate setup on your local machine then set the following
-in ``config/settings/local.py``::
+If the project is configured to use Celery as a task scheduler then, by default, tasks are set to run on the main thread when developing locally instead of getting sent to a broker. However, if you have Redis setup on your local machine, you can set the following in ``config/settings/local.py``::
CELERY_TASK_ALWAYS_EAGER = False
-To run Celery locally, make sure redis-server is installed (instructions are available at https://redis.io/topics/quickstart), run the server in one terminal with `redis-server`, and then start celery in another terminal with the following command::
+Next, make sure `redis-server` is installed (per the `Getting started with Redis`_ guide) and run the server in one terminal::
- celery -A config.celery_app worker --loglevel=info
+ $ redis-server
+
+Start the Celery worker by running the following command in another terminal::
+
+ $ celery -A config.celery_app worker --loglevel=info
+
+That Celery worker should be running whenever your app is running, typically as a background process,
+so that it can pick up any tasks that get queued. Learn more from the `Celery Workers Guide`_.
+
+The project comes with a simple task for manual testing purposes, inside `/users/tasks.py`. To queue that task locally, start the Django shell, import the task, and call `delay()` on it::
+
+ $ python manage.py shell
+ >> from .users.tasks import get_users_count
+ >> get_users_count.delay()
+
+You can also use Django admin to queue up tasks, thanks to the `django-celerybeat`_ package.
+
+.. _Getting started with Redis guide: https://redis.io/docs/getting-started/
+.. _Celery Workers Guide: https://docs.celeryq.dev/en/stable/userguide/workers.html
+.. _django-celerybeat: https://django-celery-beat.readthedocs.io/en/latest/
Sass Compilation & Live Reloading
---------------------------------
-If you've opted for Gulp as front-end pipeline, the project comes configured with `Sass`_ compilation and `live reloading`_. As you change you Sass/JS source files, the task runner will automatically rebuild the corresponding CSS and JS assets and reload them in your browser without refreshing the page.
+If you've opted for Gulp or Webpack as front-end pipeline, the project comes configured with `Sass`_ compilation and `live reloading`_. As you change you Sass/JS source files, the task runner will automatically rebuild the corresponding CSS and JS assets and reload them in your browser without refreshing the page.
#. Make sure that `Node.js`_ v16 is installed on your machine.
#. In the project root, install the JS dependencies with::
diff --git a/docs/generate-project-block.rst b/docs/generate-project-block.rst
new file mode 100644
index 000000000..2842b551d
--- /dev/null
+++ b/docs/generate-project-block.rst
@@ -0,0 +1,7 @@
+Generate a new cookiecutter-django project: ::
+
+ $ cookiecutter gh:cookiecutter/cookiecutter-django
+
+For more information refer to
+:ref:`Project Generation Options `.
+
diff --git a/docs/index.rst b/docs/index.rst
index dae641d10..da5186487 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -27,6 +27,7 @@ Contents
websocket
faq
troubleshooting
+ contributing
Indices and tables
------------------
diff --git a/docs/project-generation-options.rst b/docs/project-generation-options.rst
index 5499c79c6..a1d788173 100644
--- a/docs/project-generation-options.rst
+++ b/docs/project-generation-options.rst
@@ -73,9 +73,10 @@ cloud_provider:
1. AWS_
2. GCP_
- 3. None
+ 3. Azure_
+ 4. None
- Note that if you choose no cloud provider, media files won't work.
+ If you choose no cloud provider and docker, the production stack will serve the media files via an nginx Docker service. Without Docker, the media files won't work.
mail_service:
Select an email service that Django-Anymail provides
@@ -101,7 +102,10 @@ frontend_pipeline:
1. None
2. `Django Compressor`_
- 3. `Gulp`_: support Bootstrap recompilation with real-time variables alteration.
+ 3. `Gulp`_
+ 4. `Webpack`_
+
+Both Gulp and Webpack support Bootstrap recompilation with real-time variables alteration.
use_celery:
Indicates whether the project should be configured to use Celery_.
@@ -151,9 +155,11 @@ debug:
.. _PostgreSQL: https://www.postgresql.org/docs/
.. _Gulp: https://github.com/gulpjs/gulp
+.. _Webpack: https://webpack.js.org
.. _AWS: https://aws.amazon.com/s3/
.. _GCP: https://cloud.google.com/storage/
+.. _Azure: https://azure.microsoft.com/en-us/products/storage/blobs/
.. _Amazon SES: https://aws.amazon.com/ses/
.. _Mailgun: https://www.mailgun.com
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 2cc8302a2..c16401c84 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,2 +1,3 @@
-sphinx==5.3.0
-sphinx-rtd-theme==1.1.1
+sphinx==6.1.3
+sphinx-rtd-theme==1.2.0
+myst-parser==1.0.0
diff --git a/docs/settings.rst b/docs/settings.rst
index b8c6d448e..4691adbbd 100644
--- a/docs/settings.rst
+++ b/docs/settings.rst
@@ -49,6 +49,9 @@ DJANGO_AWS_S3_CUSTOM_DOMAIN AWS_S3_CUSTOM_DOMAIN n/a
DJANGO_AWS_S3_MAX_MEMORY_SIZE AWS_S3_MAX_MEMORY_SIZE n/a 100_000_000
DJANGO_GCP_STORAGE_BUCKET_NAME GS_BUCKET_NAME n/a raises error
GOOGLE_APPLICATION_CREDENTIALS n/a n/a raises error
+DJANGO_AZURE_ACCOUNT_KEY AZURE_ACCOUNT_KEY n/a raises error
+DJANGO_AZURE_ACCOUNT_NAME AZURE_ACCOUNT_NAME n/a raises error
+DJANGO_AZURE_CONTAINER_NAME AZURE_CONTAINER n/a raises error
SENTRY_DSN SENTRY_DSN n/a raises error
SENTRY_ENVIRONMENT n/a n/a production
SENTRY_TRACES_SAMPLE_RATE n/a n/a 0.0
diff --git a/docs/testing.rst b/docs/testing.rst
index dd6fcb48f..bea45c6dd 100644
--- a/docs/testing.rst
+++ b/docs/testing.rst
@@ -28,10 +28,15 @@ Coverage
You should build your tests to provide the highest level of **code coverage**. You can run the ``pytest`` with code ``coverage`` by typing in the following command: ::
- $ docker-compose -f local.yml run --rm django coverage run -m pytest
+ $ coverage run -m pytest
Once the tests are complete, in order to see the code coverage, run the following command: ::
+ $ coverage report
+
+If you're running the project locally with Docker, use these commands instead: ::
+
+ $ docker-compose -f local.yml run --rm django coverage run -m pytest
$ docker-compose -f local.yml run --rm django coverage report
.. note::
@@ -53,4 +58,4 @@ Once the tests are complete, in order to see the code coverage, run the followin
.. _develop locally with docker: ./developing-locally-docker.html
.. _customize: https://docs.pytest.org/en/latest/customize.html
.. _unittest: https://docs.python.org/3/library/unittest.html#module-unittest
-.. _configuring: https://coverage.readthedocs.io/en/v4.5.x/config.html
+.. _configuring: https://coverage.readthedocs.io/en/latest/config.html
diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst
index ba8ab53e6..293e9b652 100644
--- a/docs/troubleshooting.rst
+++ b/docs/troubleshooting.rst
@@ -1,5 +1,5 @@
Troubleshooting
-=====================================
+===============
This page contains some advice about errors and problems commonly encountered during the development of Cookiecutter Django applications.
@@ -38,6 +38,16 @@ To fix this, you can either:
.. _rm: https://docs.docker.com/engine/reference/commandline/volume_rm/
.. _prune: https://docs.docker.com/v17.09/engine/reference/commandline/system_prune/
+Variable is not set. Defaulting to a blank string
+-------------------------------------------------
+
+Example::
+
+ WARN[0000] The "DJANGO_AWS_STORAGE_BUCKET_NAME" variable is not set. Defaulting to a blank string.
+ WARN[0000] The "DJANGO_AWS_S3_CUSTOM_DOMAIN" variable is not set. Defaulting to a blank string.
+
+You have probably opted for Docker + Webpack without Whitenoise. This is a know limitation of the combination, which needs a little bit of manual intervention. See the :ref:`dedicated section about it `.
+
Others
------
diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py
index 9c3d946c1..dbc367175 100644
--- a/hooks/post_gen_project.py
+++ b/hooks/post_gen_project.py
@@ -10,6 +10,7 @@ TODO: restrict Cookiecutter Django project initialization to
"""
from __future__ import print_function
+import json
import os
import random
import shutil
@@ -87,15 +88,30 @@ def remove_heroku_build_hooks():
shutil.rmtree("bin")
+def remove_sass_files():
+ shutil.rmtree(os.path.join("{{cookiecutter.project_slug}}", "static", "sass"))
+
+
def remove_gulp_files():
file_names = ["gulpfile.js"]
for file_name in file_names:
os.remove(file_name)
- remove_sass_files()
-def remove_sass_files():
- shutil.rmtree(os.path.join("{{cookiecutter.project_slug}}", "static", "sass"))
+def remove_webpack_files():
+ shutil.rmtree("webpack")
+ remove_vendors_js()
+
+
+def remove_vendors_js():
+ vendors_js_path = os.path.join(
+ "{{ cookiecutter.project_slug }}",
+ "static",
+ "js",
+ "vendors.js",
+ )
+ if os.path.exists(vendors_js_path):
+ os.remove(vendors_js_path)
def remove_packagejson_file():
@@ -104,6 +120,83 @@ def remove_packagejson_file():
os.remove(file_name)
+def update_package_json(remove_dev_deps=None, remove_keys=None, scripts=None):
+ remove_dev_deps = remove_dev_deps or []
+ remove_keys = remove_keys or []
+ scripts = scripts or {}
+ with open("package.json", mode="r") as fd:
+ content = json.load(fd)
+ for package_name in remove_dev_deps:
+ content["devDependencies"].pop(package_name)
+ for key in remove_keys:
+ content.pop(key)
+ content["scripts"].update(scripts)
+ with open("package.json", mode="w") as fd:
+ json.dump(content, fd, ensure_ascii=False, indent=2)
+ fd.write("\n")
+
+
+def handle_js_runner(choice, use_docker, use_async):
+ if choice == "Gulp":
+ update_package_json(
+ remove_dev_deps=[
+ "@babel/core",
+ "@babel/preset-env",
+ "babel-loader",
+ "concurrently",
+ "css-loader",
+ "mini-css-extract-plugin",
+ "postcss-loader",
+ "postcss-preset-env",
+ "sass-loader",
+ "webpack",
+ "webpack-bundle-tracker",
+ "webpack-cli",
+ "webpack-dev-server",
+ "webpack-merge",
+ ],
+ remove_keys=["babel"],
+ scripts={
+ "dev": "gulp",
+ "build": "gulp generate-assets",
+ },
+ )
+ remove_webpack_files()
+ elif choice == "Webpack":
+ scripts = {
+ "dev": "webpack serve --config webpack/dev.config.js",
+ "build": "webpack --config webpack/prod.config.js",
+ }
+ remove_dev_deps = [
+ "browser-sync",
+ "cssnano",
+ "gulp",
+ "gulp-imagemin",
+ "gulp-plumber",
+ "gulp-postcss",
+ "gulp-rename",
+ "gulp-sass",
+ "gulp-uglify-es",
+ ]
+ if not use_docker:
+ dev_django_cmd = (
+ "uvicorn config.asgi:application --reload"
+ if use_async
+ else "python manage.py runserver_plus"
+ )
+ scripts.update(
+ {
+ "dev": "concurrently npm:dev:*",
+ "dev:webpack": "webpack serve --config webpack/dev.config.js",
+ "dev:django": dev_django_cmd,
+ }
+ )
+ else:
+ remove_dev_deps.append("concurrently")
+ update_package_json(remove_dev_deps=remove_dev_deps, scripts=scripts)
+ remove_gulp_files()
+
+
def remove_celery_files():
file_names = [
os.path.join("config", "celery_app.py"),
@@ -293,6 +386,7 @@ def set_flags_in_settings_files():
def remove_envs_and_associated_files():
shutil.rmtree(".envs")
os.remove("merge_production_dotenvs_in_dotenv.py")
+ shutil.rmtree("tests")
def remove_celery_compose_dirs():
@@ -383,15 +477,26 @@ def main():
if "{{ cookiecutter.keep_local_envs_in_vcs }}".lower() == "y":
append_to_gitignore_file("!.envs/.local/")
- if "{{ cookiecutter.frontend_pipeline }}" != "Gulp":
+ if "{{ cookiecutter.frontend_pipeline }}" in ["None", "Django Compressor"]:
remove_gulp_files()
+ remove_webpack_files()
+ remove_sass_files()
remove_packagejson_file()
if "{{ cookiecutter.use_docker }}".lower() == "y":
remove_node_dockerfile()
+ else:
+ handle_js_runner(
+ "{{ cookiecutter.frontend_pipeline }}",
+ use_docker=("{{ cookiecutter.use_docker }}".lower() == "y"),
+ use_async=("{{ cookiecutter.use_async }}".lower() == "y"),
+ )
- if "{{ cookiecutter.cloud_provider}}" == "None":
+ if (
+ "{{ cookiecutter.cloud_provider }}" == "None"
+ and "{{ cookiecutter.use_docker }}".lower() == "n"
+ ):
print(
- WARNING + "You chose not to use a cloud provider, "
+ WARNING + "You chose to not use any cloud providers nor Docker, "
"media files won't be served in production." + TERMINATOR
)
remove_storages_module()
diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py
index 2845f012c..c3eef1e43 100644
--- a/hooks/pre_gen_project.py
+++ b/hooks/pre_gen_project.py
@@ -72,11 +72,8 @@ if (
sys.exit(1)
if (
- "{{ cookiecutter.cloud_provider }}" == "GCP"
- and "{{ cookiecutter.mail_service }}" == "Amazon SES"
-) or (
- "{{ cookiecutter.cloud_provider }}" == "None"
- and "{{ cookiecutter.mail_service }}" == "Amazon SES"
+ "{{ cookiecutter.mail_service }}" == "Amazon SES"
+ and "{{ cookiecutter.cloud_provider }}" != "AWS"
):
print(
"You should either use AWS or select a different "
diff --git a/requirements.txt b/requirements.txt
index 783edeb18..5a65d6160 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,26 +1,26 @@
cookiecutter==2.1.1
-sh==1.14.3; sys_platform != "win32"
+sh==2.0.2; sys_platform != "win32"
binaryornot==0.4.4
# Code quality
# ------------------------------------------------------------------------------
-black==22.10.0
-isort==5.10.1
-flake8==5.0.4
-flake8-isort==5.0.0
-pre-commit==2.20.0
+black==23.1.0
+isort==5.12.0
+flake8==6.0.0
+flake8-isort==6.0.0
+pre-commit==3.1.1
# Testing
# ------------------------------------------------------------------------------
-tox==3.27.1
-pytest==7.2.0
+tox==4.4.6
+pytest==7.2.2
pytest-cookies==0.6.1
pytest-instafail==0.4.2
pyyaml==6.0
# Scripting
# ------------------------------------------------------------------------------
-PyGithub==1.57
-gitpython==3.1.29
+PyGithub==1.58.0
+gitpython==3.1.31
jinja2==3.1.2
-requests==2.28.1
+requests==2.28.2
diff --git a/scripts/update_changelog.py b/scripts/update_changelog.py
index b50d25066..57d915a4b 100644
--- a/scripts/update_changelog.py
+++ b/scripts/update_changelog.py
@@ -82,14 +82,20 @@ def group_pulls_by_change_type(
grouped_pulls = {
"Changed": [],
"Fixed": [],
+ "Documentation": [],
"Updated": [],
}
for pull in pull_requests_list:
label_names = {label.name for label in pull.labels}
+ if "project infrastructure" in label_names:
+ # Don't mention it in the changelog
+ continue
if "update" in label_names:
group_name = "Updated"
elif "bug" in label_names:
group_name = "Fixed"
+ elif "docs" in label_names:
+ group_name = "Documentation"
else:
group_name = "Changed"
grouped_pulls[group_name].append(pull)
diff --git a/setup.py b/setup.py
index ea0d169e3..e6f1f0b65 100644
--- a/setup.py
+++ b/setup.py
@@ -5,7 +5,7 @@ except ImportError:
from distutils.core import setup
# We use calendar versioning
-version = "2022.11.11"
+version = "2023.03.09"
with open("README.rst") as readme_file:
long_description = readme_file.read()
diff --git a/tests/test_bare.sh b/tests/test_bare.sh
index 05da9328b..afd12fec8 100755
--- a/tests/test_bare.sh
+++ b/tests/test_bare.sh
@@ -32,13 +32,11 @@ pytest
# Make sure the check doesn't raise any warnings
python manage.py check --fail-level WARNING
+# Run npm build script if package.json is present
if [ -f "package.json" ]
then
npm install
- if [ -f "gulpfile.js" ]
- then
- npm run build
- fi
+ npm run build
fi
# Generate the HTML for the documentation
diff --git a/tests/test_cookiecutter_generation.py b/tests/test_cookiecutter_generation.py
index 3a757fcb7..7628e5e1d 100755
--- a/tests/test_cookiecutter_generation.py
+++ b/tests/test_cookiecutter_generation.py
@@ -56,6 +56,8 @@ SUPPORTED_COMBINATIONS = [
{"cloud_provider": "AWS", "use_whitenoise": "n"},
{"cloud_provider": "GCP", "use_whitenoise": "y"},
{"cloud_provider": "GCP", "use_whitenoise": "n"},
+ {"cloud_provider": "Azure", "use_whitenoise": "y"},
+ {"cloud_provider": "Azure", "use_whitenoise": "n"},
{"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Mailgun"},
{"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Mailjet"},
{"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Mandrill"},
@@ -82,7 +84,16 @@ SUPPORTED_COMBINATIONS = [
{"cloud_provider": "GCP", "mail_service": "SendinBlue"},
{"cloud_provider": "GCP", "mail_service": "SparkPost"},
{"cloud_provider": "GCP", "mail_service": "Other SMTP"},
- # Note: cloud_providers GCP and None with mail_service Amazon SES is not supported
+ {"cloud_provider": "Azure", "mail_service": "Mailgun"},
+ {"cloud_provider": "Azure", "mail_service": "Mailjet"},
+ {"cloud_provider": "Azure", "mail_service": "Mandrill"},
+ {"cloud_provider": "Azure", "mail_service": "Postmark"},
+ {"cloud_provider": "Azure", "mail_service": "Sendgrid"},
+ {"cloud_provider": "Azure", "mail_service": "SendinBlue"},
+ {"cloud_provider": "Azure", "mail_service": "SparkPost"},
+ {"cloud_provider": "Azure", "mail_service": "Other SMTP"},
+ # Note: cloud_providers GCP, Azure, and None
+ # with mail_service Amazon SES is not supported
{"use_async": "y"},
{"use_async": "n"},
{"use_drf": "y"},
@@ -90,6 +101,7 @@ SUPPORTED_COMBINATIONS = [
{"frontend_pipeline": "None"},
{"frontend_pipeline": "Django Compressor"},
{"frontend_pipeline": "Gulp"},
+ {"frontend_pipeline": "Webpack"},
{"use_celery": "y"},
{"use_celery": "n"},
{"use_mailhog": "y"},
@@ -113,6 +125,7 @@ SUPPORTED_COMBINATIONS = [
UNSUPPORTED_COMBINATIONS = [
{"cloud_provider": "None", "use_whitenoise": "n"},
{"cloud_provider": "GCP", "mail_service": "Amazon SES"},
+ {"cloud_provider": "Azure", "mail_service": "Amazon SES"},
{"cloud_provider": "None", "mail_service": "Amazon SES"},
]
@@ -122,11 +135,11 @@ def _fixture_id(ctx):
return "-".join(f"{key}:{value}" for key, value in ctx.items())
-def build_files_list(root_dir):
+def build_files_list(base_dir):
"""Build a list containing absolute paths to the generated files."""
return [
os.path.join(dirpath, file_path)
- for dirpath, subdirs, files in os.walk(root_dir)
+ for dirpath, subdirs, files in os.walk(base_dir)
for file_path in files
]
@@ -219,7 +232,7 @@ def test_travis_invokes_pytest(cookies, context, use_docker, expected_test_scrip
("y", "docker-compose -f local.yml run django pytest"),
],
)
-def test_gitlab_invokes_flake8_and_pytest(
+def test_gitlab_invokes_precommit_and_pytest(
cookies, context, use_docker, expected_test_script
):
context.update({"ci_tool": "Gitlab", "use_docker": use_docker})
@@ -233,7 +246,9 @@ def test_gitlab_invokes_flake8_and_pytest(
with open(f"{result.project_path}/.gitlab-ci.yml") as gitlab_yml:
try:
gitlab_config = yaml.safe_load(gitlab_yml)
- assert gitlab_config["flake8"]["script"] == ["flake8"]
+ assert gitlab_config["precommit"]["script"] == [
+ "pre-commit run --show-diff-on-failure --color=always --all-files"
+ ]
assert gitlab_config["pytest"]["script"] == [expected_test_script]
except yaml.YAMLError as e:
pytest.fail(e)
diff --git a/tests/test_docker.sh b/tests/test_docker.sh
index b3663bd2c..28d232896 100755
--- a/tests/test_docker.sh
+++ b/tests/test_docker.sh
@@ -41,3 +41,9 @@ docker-compose -f local.yml run django python manage.py check --fail-level WARNI
# Generate the HTML for the documentation
docker-compose -f local.yml run docs make html
+
+# Run npm build script if package.json is present
+if [ -f "package.json" ]
+then
+ docker-compose -f local.yml run node npm run build
+fi
diff --git a/{{cookiecutter.project_slug}}/.dockerignore b/{{cookiecutter.project_slug}}/.dockerignore
index 5518e60af..7369480e3 100644
--- a/{{cookiecutter.project_slug}}/.dockerignore
+++ b/{{cookiecutter.project_slug}}/.dockerignore
@@ -8,3 +8,4 @@
.readthedocs.yml
.travis.yml
venv
+.git
diff --git a/{{cookiecutter.project_slug}}/.envs/.production/.django b/{{cookiecutter.project_slug}}/.envs/.production/.django
index e7e8461c9..ad652c9ad 100644
--- a/{{cookiecutter.project_slug}}/.envs/.production/.django
+++ b/{{cookiecutter.project_slug}}/.envs/.production/.django
@@ -44,6 +44,12 @@ DJANGO_AWS_STORAGE_BUCKET_NAME=
# ------------------------------------------------------------------------------
GOOGLE_APPLICATION_CREDENTIALS=
DJANGO_GCP_STORAGE_BUCKET_NAME=
+{% elif cookiecutter.cloud_provider == 'Azure' %}
+# Azure
+# ------------------------------------------------------------------------------
+DJANGO_AZURE_ACCOUNT_KEY=
+DJANGO_AZURE_ACCOUNT_NAME=
+DJANGO_AZURE_CONTAINER_NAME=
{% endif %}
# django-allauth
# ------------------------------------------------------------------------------
diff --git a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml
index 0790187bd..89d83f257 100644
--- a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml
+++ b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml
@@ -27,16 +27,15 @@ jobs:
uses: actions/checkout@v3
- name: Set up Python
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v4
with:
python-version: "3.10"
- cache: pip
- cache-dependency-path: |
- requirements/base.txt
- requirements/local.txt
+ {%- if cookiecutter.open_source_license != 'Not open source' %}
+ # Consider using pre-commit.ci for open source project
+ {%- endif %}
- name: Run pre-commit
- uses: pre-commit/action@v2.0.3
+ uses: pre-commit/action@v3.0.0
# With no caching at all the entire ci process takes 4m 30s to complete!
pytest:
@@ -85,7 +84,7 @@ jobs:
{%- else %}
- name: Set up Python
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v4
with:
python-version: "3.10"
cache: pip
diff --git a/{{cookiecutter.project_slug}}/.gitignore b/{{cookiecutter.project_slug}}/.gitignore
index ede26ef72..19bb2bc07 100644
--- a/{{cookiecutter.project_slug}}/.gitignore
+++ b/{{cookiecutter.project_slug}}/.gitignore
@@ -326,6 +326,9 @@ Session.vim
# Auto-generated tag files
tags
+# Redis dump file
+dump.rdb
+
### Project template
{%- if cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'n' %}
MailHog
@@ -343,4 +346,9 @@ project.css
project.min.css
vendors.js
*.min.js
+*.min.js.map
+{%- endif %}
+{%- if cookiecutter.frontend_pipeline == 'Webpack' %}
+{{ cookiecutter.project_slug }}/static/webpack_bundles/
+webpack-stats.json
{%- endif %}
diff --git a/{{cookiecutter.project_slug}}/.gitlab-ci.yml b/{{cookiecutter.project_slug}}/.gitlab-ci.yml
index dbb65fb73..7892ad63a 100644
--- a/{{cookiecutter.project_slug}}/.gitlab-ci.yml
+++ b/{{cookiecutter.project_slug}}/.gitlab-ci.yml
@@ -7,21 +7,26 @@ variables:
POSTGRES_PASSWORD: ''
POSTGRES_DB: 'test_{{ cookiecutter.project_slug }}'
POSTGRES_HOST_AUTH_METHOD: trust
- {% if cookiecutter.use_celery == 'y' -%}
+ {%- if cookiecutter.use_celery == 'y' %}
CELERY_BROKER_URL: 'redis://redis:6379/0'
{%- endif %}
-flake8:
+precommit:
stage: lint
- image: python:3.10-alpine
+ image: python:3.10
+ variables:
+ PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
+ cache:
+ paths:
+ - ${PRE_COMMIT_HOME}
before_script:
- - pip install -q flake8
+ - pip install -q pre-commit
script:
- - flake8
+ - pre-commit run --show-diff-on-failure --color=always --all-files
pytest:
stage: test
- {% if cookiecutter.use_docker == 'y' -%}
+ {%- if cookiecutter.use_docker == 'y' %}
image: docker/compose:1.29.2
tags:
- docker
@@ -34,7 +39,7 @@ pytest:
- docker-compose -f local.yml up -d
script:
- docker-compose -f local.yml run django pytest
- {%- else -%}
+ {%- else %}
image: python:3.10
tags:
- python
@@ -42,11 +47,8 @@ pytest:
- postgres:{{ cookiecutter.postgresql_version }}
variables:
DATABASE_URL: pgsql://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres/$POSTGRES_DB
-
before_script:
- pip install -r requirements/local.txt
-
script:
- pytest
{%- endif %}
-
diff --git a/{{cookiecutter.project_slug}}/.idea/runConfigurations/docker_compose_up_django.xml b/{{cookiecutter.project_slug}}/.idea/runConfigurations/docker_compose_up_django.xml
index ad3b6a35a..e84c5ffdd 100644
--- a/{{cookiecutter.project_slug}}/.idea/runConfigurations/docker_compose_up_django.xml
+++ b/{{cookiecutter.project_slug}}/.idea/runConfigurations/docker_compose_up_django.xml
@@ -10,7 +10,7 @@
{%- endif %}
- {%- if cookiecutter.frontend_pipeline == 'Gulp' %}
+ {%- if cookiecutter.frontend_pipeline in ['Gulp', 'Webpack'] %}
{%- endif %}
diff --git a/{{cookiecutter.project_slug}}/.idea/{{cookiecutter.project_slug}}.iml b/{{cookiecutter.project_slug}}/.idea/{{cookiecutter.project_slug}}.iml
index 98759fa1b..fe1aeae52 100644
--- a/{{cookiecutter.project_slug}}/.idea/{{cookiecutter.project_slug}}.iml
+++ b/{{cookiecutter.project_slug}}/.idea/{{cookiecutter.project_slug}}.iml
@@ -13,7 +13,7 @@
- {% if cookiecutter.frontend_pipeline == 'Gulp' %}
+ {% if cookiecutter.frontend_pipeline in ['Gulp', 'Webpack'] %}
diff --git a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml
index 433d97de9..a8cdb1357 100644
--- a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml
+++ b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml
@@ -3,30 +3,30 @@ default_stages: [commit]
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.3.0
+ rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- repo: https://github.com/asottile/pyupgrade
- rev: v3.2.2
+ rev: v3.3.1
hooks:
- id: pyupgrade
args: [--py310-plus]
- repo: https://github.com/psf/black
- rev: 22.10.0
+ rev: 23.1.0
hooks:
- id: black
- repo: https://github.com/PyCQA/isort
- rev: 5.10.1
+ rev: 5.12.0
hooks:
- id: isort
- repo: https://github.com/PyCQA/flake8
- rev: 5.0.4
+ rev: 6.0.0
hooks:
- id: flake8
args: ["--config=setup.cfg"]
diff --git a/{{cookiecutter.project_slug}}/.readthedocs.yml b/{{cookiecutter.project_slug}}/.readthedocs.yml
index e943a5fa9..08d7f9c24 100644
--- a/{{cookiecutter.project_slug}}/.readthedocs.yml
+++ b/{{cookiecutter.project_slug}}/.readthedocs.yml
@@ -1,12 +1,20 @@
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
version: 2
+# Set the version of Python and other tools you might need
+build:
+ os: ubuntu-22.04
+ tools:
+ python: "3.10"
+
+# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
-build:
- image: testing
-
+# Python requirements required to build your docs
python:
- version: 3.10
install:
- requirements: requirements/local.txt
diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_slug}}/README.md
index f7c29fb22..83f9a7e48 100644
--- a/{{cookiecutter.project_slug}}/README.md
+++ b/{{cookiecutter.project_slug}}/README.md
@@ -63,6 +63,20 @@ celery -A config.celery_app worker -l info
Please note: For Celery's import magic to work, it is important *where* the celery commands are run. If you are in the same folder with *manage.py*, you should be right.
+To run [periodic tasks](https://docs.celeryq.dev/en/stable/userguide/periodic-tasks.html), you'll need to start the celery beat scheduler service. You can start it as a standalone process:
+
+``` bash
+cd {{cookiecutter.project_slug}}
+celery -A config.celery_app beat
+```
+
+or you can embed the beat service inside a worker with the `-B` option (not recommended for production use):
+
+``` bash
+cd {{cookiecutter.project_slug}}
+celery -A config.celery_app worker -B -l info
+```
+
{%- endif %}
{%- if cookiecutter.use_mailhog == "y" %}
@@ -128,13 +142,14 @@ See detailed [cookiecutter-django Heroku documentation](http://cookiecutter-djan
See detailed [cookiecutter-django Docker documentation](http://cookiecutter-django.readthedocs.io/en/latest/deployment-with-docker.html).
{%- endif %}
-{%- if cookiecutter.frontend_pipeline == 'Gulp' %}
+{%- if cookiecutter.frontend_pipeline in ['Gulp', 'Webpack'] %}
+
### Custom Bootstrap Compilation
The generated CSS is set up with automatic Bootstrap recompilation with variables of your choice.
Bootstrap v5 is installed using npm and customised by tweaking your variables in `static/sass/custom_bootstrap_vars`.
-You can find a list of available variables [in the bootstrap source](https://github.com/twbs/bootstrap/blob/main/scss/_variables.scss), or get explanations on them in the [Bootstrap docs](https://getbootstrap.com/docs/5.1/customize/sass/).
+You can find a list of available variables [in the bootstrap source](https://github.com/twbs/bootstrap/blob/v5.1.3/scss/_variables.scss), or get explanations on them in the [Bootstrap docs](https://getbootstrap.com/docs/5.1/customize/sass/).
-Bootstrap's javascript as well as its dependencies is concatenated into a single file: `static/js/vendors.js`.
+Bootstrap's javascript as well as its dependencies are concatenated into a single file: `static/js/vendors.js`.
{%- endif %}
diff --git a/{{cookiecutter.project_slug}}/compose/local/django/celery/beat/start b/{{cookiecutter.project_slug}}/compose/local/django/celery/beat/start
index c04a7365e..61f83968b 100644
--- a/{{cookiecutter.project_slug}}/compose/local/django/celery/beat/start
+++ b/{{cookiecutter.project_slug}}/compose/local/django/celery/beat/start
@@ -5,4 +5,4 @@ set -o nounset
rm -f './celerybeat.pid'
-celery -A config.celery_app beat -l INFO
+exec watchfiles celery.__main__.main --args '-A config.celery_app beat -l INFO'
diff --git a/{{cookiecutter.project_slug}}/compose/local/django/celery/flower/start b/{{cookiecutter.project_slug}}/compose/local/django/celery/flower/start
index bd3c9f2fd..ac3cc6b36 100644
--- a/{{cookiecutter.project_slug}}/compose/local/django/celery/flower/start
+++ b/{{cookiecutter.project_slug}}/compose/local/django/celery/flower/start
@@ -3,9 +3,6 @@
set -o errexit
set -o nounset
-
-celery \
- -A config.celery_app \
- -b "${CELERY_BROKER_URL}" \
- flower \
- --basic_auth="${CELERY_FLOWER_USER}:${CELERY_FLOWER_PASSWORD}"
+exec watchfiles celery.__main__.main \
+ --args \
+ "-A config.celery_app -b \"${CELERY_BROKER_URL}\" flower --basic_auth=\"${CELERY_FLOWER_USER}:${CELERY_FLOWER_PASSWORD}\""
diff --git a/{{cookiecutter.project_slug}}/compose/local/django/celery/worker/start b/{{cookiecutter.project_slug}}/compose/local/django/celery/worker/start
index 4ddcfa137..16341fdd1 100644
--- a/{{cookiecutter.project_slug}}/compose/local/django/celery/worker/start
+++ b/{{cookiecutter.project_slug}}/compose/local/django/celery/worker/start
@@ -4,4 +4,4 @@ set -o errexit
set -o nounset
-watchfiles celery.__main__.main --args '-A config.celery_app worker -l INFO'
+exec watchfiles celery.__main__.main --args '-A config.celery_app worker -l INFO'
diff --git a/{{cookiecutter.project_slug}}/compose/local/django/start b/{{cookiecutter.project_slug}}/compose/local/django/start
index 3fe547357..ec57dc8e4 100644
--- a/{{cookiecutter.project_slug}}/compose/local/django/start
+++ b/{{cookiecutter.project_slug}}/compose/local/django/start
@@ -7,7 +7,7 @@ set -o nounset
python manage.py migrate
{%- if cookiecutter.use_async == 'y' %}
-uvicorn config.asgi:application --host 0.0.0.0 --reload --reload-include '*.html'
+exec uvicorn config.asgi:application --host 0.0.0.0 --reload --reload-include '*.html'
{%- else %}
-python manage.py runserver_plus 0.0.0.0:8000
+exec python manage.py runserver_plus 0.0.0.0:8000
{%- endif %}
diff --git a/{{cookiecutter.project_slug}}/compose/local/docs/start b/{{cookiecutter.project_slug}}/compose/local/docs/start
index fd2e0de6a..96a94f566 100644
--- a/{{cookiecutter.project_slug}}/compose/local/docs/start
+++ b/{{cookiecutter.project_slug}}/compose/local/docs/start
@@ -4,4 +4,4 @@ set -o errexit
set -o pipefail
set -o nounset
-make livehtml
+exec make livehtml
diff --git a/{{cookiecutter.project_slug}}/compose/production/aws/Dockerfile b/{{cookiecutter.project_slug}}/compose/production/aws/Dockerfile
index 8282047b3..4d1ecbb20 100644
--- a/{{cookiecutter.project_slug}}/compose/production/aws/Dockerfile
+++ b/{{cookiecutter.project_slug}}/compose/production/aws/Dockerfile
@@ -1,4 +1,4 @@
-FROM garland/aws-cli-docker:1.15.47
+FROM garland/aws-cli-docker:1.16.140
COPY ./compose/production/aws/maintenance /usr/local/bin/maintenance
COPY ./compose/production/postgres/maintenance/_sourced /usr/local/bin/maintenance/_sourced
diff --git a/{{cookiecutter.project_slug}}/compose/production/django/Dockerfile b/{{cookiecutter.project_slug}}/compose/production/django/Dockerfile
index 4652f0898..ef80441ba 100644
--- a/{{cookiecutter.project_slug}}/compose/production/django/Dockerfile
+++ b/{{cookiecutter.project_slug}}/compose/production/django/Dockerfile
@@ -1,6 +1,6 @@
ARG PYTHON_VERSION=3.10-slim-bullseye
-{% if cookiecutter.frontend_pipeline == 'Gulp' -%}
+{% if cookiecutter.frontend_pipeline in ['Gulp', 'Webpack'] -%}
FROM node:16-bullseye-slim as client-builder
ARG APP_HOME=/app
@@ -9,6 +9,20 @@ WORKDIR ${APP_HOME}
COPY ./package.json ${APP_HOME}
RUN npm install && npm cache clean --force
COPY . ${APP_HOME}
+{%- if cookiecutter.frontend_pipeline == 'Webpack' and cookiecutter.use_whitenoise == 'n' %}
+{%- if cookiecutter.cloud_provider == 'AWS' %}
+ARG DJANGO_AWS_STORAGE_BUCKET_NAME
+ENV DJANGO_AWS_STORAGE_BUCKET_NAME=${DJANGO_AWS_STORAGE_BUCKET_NAME}
+ARG DJANGO_AWS_S3_CUSTOM_DOMAIN
+ENV DJANGO_AWS_S3_CUSTOM_DOMAIN=${DJANGO_AWS_S3_CUSTOM_DOMAIN}
+{%- elif cookiecutter.cloud_provider == 'GCP' %}
+ARG DJANGO_GCP_STORAGE_BUCKET_NAME
+ENV DJANGO_GCP_STORAGE_BUCKET_NAME=${DJANGO_GCP_STORAGE_BUCKET_NAME}
+{%- elif cookiecutter.cloud_provider == 'Azure' %}
+ARG DJANGO_AZURE_ACCOUNT_NAME
+ENV DJANGO_AZURE_ACCOUNT_NAME=${DJANGO_AZURE_ACCOUNT_NAME}
+{%- endif %}
+{%- endif %}
RUN npm run build
{%- endif %}
@@ -99,7 +113,7 @@ RUN chmod +x /start-flower
# copy application code to WORKDIR
-{%- if cookiecutter.frontend_pipeline == 'Gulp' %}
+{%- if cookiecutter.frontend_pipeline in ['Gulp', 'Webpack'] %}
COPY --from=client-builder --chown=django:django ${APP_HOME} ${APP_HOME}
{% else %}
COPY --chown=django:django . ${APP_HOME}
diff --git a/{{cookiecutter.project_slug}}/compose/production/django/start b/{{cookiecutter.project_slug}}/compose/production/django/start
index 2ba79501c..73f686bd7 100644
--- a/{{cookiecutter.project_slug}}/compose/production/django/start
+++ b/{{cookiecutter.project_slug}}/compose/production/django/start
@@ -28,7 +28,7 @@ if compress_enabled; then
fi
{%- endif %}
{%- if cookiecutter.use_async == 'y' %}
-/usr/local/bin/gunicorn config.asgi --bind 0.0.0.0:5000 --chdir=/app -k uvicorn.workers.UvicornWorker
+exec /usr/local/bin/gunicorn config.asgi --bind 0.0.0.0:5000 --chdir=/app -k uvicorn.workers.UvicornWorker
{%- else %}
-/usr/local/bin/gunicorn config.wsgi --bind 0.0.0.0:5000 --chdir=/app
+exec /usr/local/bin/gunicorn config.wsgi --bind 0.0.0.0:5000 --chdir=/app
{%- endif %}
diff --git a/{{cookiecutter.project_slug}}/compose/production/nginx/Dockerfile b/{{cookiecutter.project_slug}}/compose/production/nginx/Dockerfile
new file mode 100644
index 000000000..911b16f71
--- /dev/null
+++ b/{{cookiecutter.project_slug}}/compose/production/nginx/Dockerfile
@@ -0,0 +1,2 @@
+FROM nginx:1.17.8-alpine
+COPY ./compose/production/nginx/default.conf /etc/nginx/conf.d/default.conf
diff --git a/{{cookiecutter.project_slug}}/compose/production/nginx/default.conf b/{{cookiecutter.project_slug}}/compose/production/nginx/default.conf
new file mode 100644
index 000000000..aafdd5bee
--- /dev/null
+++ b/{{cookiecutter.project_slug}}/compose/production/nginx/default.conf
@@ -0,0 +1,7 @@
+server {
+ listen 80;
+ server_name localhost;
+ location /media/ {
+ alias /usr/share/nginx/media/;
+ }
+}
diff --git a/{{cookiecutter.project_slug}}/compose/production/traefik/Dockerfile b/{{cookiecutter.project_slug}}/compose/production/traefik/Dockerfile
index aa879052b..2365c4c49 100644
--- a/{{cookiecutter.project_slug}}/compose/production/traefik/Dockerfile
+++ b/{{cookiecutter.project_slug}}/compose/production/traefik/Dockerfile
@@ -1,4 +1,4 @@
-FROM traefik:v2.2.11
+FROM traefik:2.9.8
RUN mkdir -p /etc/traefik/acme \
&& touch /etc/traefik/acme/acme.json \
&& chmod 600 /etc/traefik/acme/acme.json
diff --git a/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.yml b/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.yml
index cc183cd6c..ea57f4a51 100644
--- a/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.yml
+++ b/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.yml
@@ -57,6 +57,18 @@ http:
# https://docs.traefik.io/master/routing/routers/#certresolver
certResolver: letsencrypt
{%- endif %}
+ {%- if cookiecutter.cloud_provider == 'None' %}
+
+ web-media-router:
+ rule: "Host(`{{ cookiecutter.domain_name }}`) && PathPrefix(`/media/`)"
+ entryPoints:
+ - web-secure
+ middlewares:
+ - csrf
+ service: django-media
+ tls:
+ certResolver: letsencrypt
+ {%- endif %}
middlewares:
csrf:
@@ -77,6 +89,13 @@ http:
servers:
- url: http://flower:5555
{%- endif %}
+ {%- if cookiecutter.cloud_provider == 'None' %}
+
+ django-media:
+ loadBalancer:
+ servers:
+ - url: http://nginx:80
+ {%- endif %}
providers:
# https://docs.traefik.io/master/providers/file/
diff --git a/{{cookiecutter.project_slug}}/config/asgi.py b/{{cookiecutter.project_slug}}/config/asgi.py
index 8c99bbf53..65e76ca0a 100644
--- a/{{cookiecutter.project_slug}}/config/asgi.py
+++ b/{{cookiecutter.project_slug}}/config/asgi.py
@@ -15,8 +15,8 @@ from django.core.asgi import get_asgi_application
# This allows easy placement of apps within the interior
# {{ cookiecutter.project_slug }} directory.
-ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent
-sys.path.append(str(ROOT_DIR / "{{ cookiecutter.project_slug }}"))
+BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
+sys.path.append(str(BASE_DIR / "{{ cookiecutter.project_slug }}"))
# If DJANGO_SETTINGS_MODULE is unset, default to the local settings
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
diff --git a/{{cookiecutter.project_slug}}/config/settings/base.py b/{{cookiecutter.project_slug}}/config/settings/base.py
index eab0129e2..e297abb64 100644
--- a/{{cookiecutter.project_slug}}/config/settings/base.py
+++ b/{{cookiecutter.project_slug}}/config/settings/base.py
@@ -5,15 +5,15 @@ from pathlib import Path
import environ
-ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent.parent
+BASE_DIR = Path(__file__).resolve(strict=True).parent.parent.parent
# {{ cookiecutter.project_slug }}/
-APPS_DIR = ROOT_DIR / "{{ cookiecutter.project_slug }}"
+APPS_DIR = BASE_DIR / "{{ cookiecutter.project_slug }}"
env = environ.Env()
READ_DOT_ENV_FILE = env.bool("DJANGO_READ_DOT_ENV_FILE", default=False)
if READ_DOT_ENV_FILE:
# OS environment variables take precedence over variables from .env
- env.read_env(str(ROOT_DIR / ".env"))
+ env.read_env(str(BASE_DIR / ".env"))
# GENERAL
# ------------------------------------------------------------------------------
@@ -30,12 +30,10 @@ LANGUAGE_CODE = "en-us"
SITE_ID = 1
# https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n
USE_I18N = True
-# https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n
-USE_L10N = True
# https://docs.djangoproject.com/en/dev/ref/settings/#use-tz
USE_TZ = True
# https://docs.djangoproject.com/en/dev/ref/settings/#locale-paths
-LOCALE_PATHS = [str(ROOT_DIR / "locale")]
+LOCALE_PATHS = [str(BASE_DIR / "locale")]
# DATABASES
# ------------------------------------------------------------------------------
@@ -89,6 +87,9 @@ THIRD_PARTY_APPS = [
"corsheaders",
"drf_spectacular",
{%- endif %}
+{%- if cookiecutter.frontend_pipeline == 'Webpack' %}
+ "webpack_loader",
+{%- endif %}
]
LOCAL_APPS = [
@@ -154,14 +155,13 @@ MIDDLEWARE = [
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
- "django.middleware.common.BrokenLinkEmailsMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
# STATIC
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#static-root
-STATIC_ROOT = str(ROOT_DIR / "staticfiles")
+STATIC_ROOT = str(BASE_DIR / "staticfiles")
# https://docs.djangoproject.com/en/dev/ref/settings/#static-url
STATIC_URL = "/static/"
# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
@@ -285,6 +285,11 @@ CELERY_BROKER_URL = env("CELERY_BROKER_URL")
CELERY_RESULT_BACKEND = CELERY_BROKER_URL
# https://docs.celeryq.dev/en/stable/userguide/configuration.html#result-extended
CELERY_RESULT_EXTENDED = True
+# https://docs.celeryq.dev/en/stable/userguide/configuration.html#result-backend-always-retry
+# https://github.com/celery/celery/pull/6122
+CELERY_RESULT_BACKEND_ALWAYS_RETRY = True
+# https://docs.celeryq.dev/en/stable/userguide/configuration.html#result-backend-max-retries
+CELERY_RESULT_BACKEND_MAX_RETRIES = 10
# https://docs.celeryq.dev/en/stable/userguide/configuration.html#std:setting-accept_content
CELERY_ACCEPT_CONTENT = ["json"]
# https://docs.celeryq.dev/en/stable/userguide/configuration.html#std:setting-task_serializer
@@ -299,6 +304,10 @@ CELERY_TASK_TIME_LIMIT = 5 * 60
CELERY_TASK_SOFT_TIME_LIMIT = 60
# https://docs.celeryq.dev/en/stable/userguide/configuration.html#beat-scheduler
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
+# https://docs.celeryq.dev/en/stable/userguide/configuration.html#worker-send-task-events
+CELERY_WORKER_SEND_TASK_EVENTS = True
+# https://docs.celeryq.dev/en/stable/userguide/configuration.html#std-setting-task_send_sent_event
+CELERY_TASK_SEND_SENT_EVENT = True
{%- endif %}
# django-allauth
@@ -354,11 +363,20 @@ SPECTACULAR_SETTINGS = {
"DESCRIPTION": "Documentation of API endpoints of {{ cookiecutter.project_name }}",
"VERSION": "1.0.0",
"SERVE_PERMISSIONS": ["rest_framework.permissions.IsAdminUser"],
- "SERVERS": [
- {"url": "http://127.0.0.1:8000", "description": "Local Development server"},
- {"url": "https://{{ cookiecutter.domain_name }}", "description": "Production server"},
- ],
}
+{%- endif %}
+{%- if cookiecutter.frontend_pipeline == 'Webpack' %}
+# django-webpack-loader
+# ------------------------------------------------------------------------------
+WEBPACK_LOADER = {
+ "DEFAULT": {
+ "CACHE": not DEBUG,
+ "STATS_FILE": BASE_DIR / "webpack-stats.json",
+ "POLL_INTERVAL": 0.1,
+ "IGNORE": [r".+\.hot-update.js", r".+\.map"],
+ }
+}
+
{%- endif %}
# Your stuff...
# ------------------------------------------------------------------------------
diff --git a/{{cookiecutter.project_slug}}/config/settings/local.py b/{{cookiecutter.project_slug}}/config/settings/local.py
index a5fe0f71c..7220c5e4c 100644
--- a/{{cookiecutter.project_slug}}/config/settings/local.py
+++ b/{{cookiecutter.project_slug}}/config/settings/local.py
@@ -69,7 +69,7 @@ if env("USE_DOCKER") == "yes":
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
INTERNAL_IPS += [".".join(ip.split(".")[:-1] + ["1"]) for ip in ips]
- {%- if cookiecutter.frontend_pipeline == 'Gulp' %}
+ {%- if cookiecutter.frontend_pipeline in ['Gulp', 'Webpack'] %}
try:
_, _, ips = socket.gethostbyname_ex("node")
INTERNAL_IPS.extend(ips)
@@ -94,6 +94,12 @@ CELERY_TASK_ALWAYS_EAGER = True
# https://docs.celeryq.dev/en/stable/userguide/configuration.html#task-eager-propagates
CELERY_TASK_EAGER_PROPAGATES = True
+{%- endif %}
+{%- if cookiecutter.frontend_pipeline == 'Webpack' %}
+# django-webpack-loader
+# ------------------------------------------------------------------------------
+WEBPACK_LOADER["DEFAULT"]["CACHE"] = not DEBUG # noqa F405
+
{%- endif %}
# Your stuff...
# ------------------------------------------------------------------------------
diff --git a/{{cookiecutter.project_slug}}/config/settings/production.py b/{{cookiecutter.project_slug}}/config/settings/production.py
index 8eed5a805..5de0529e2 100644
--- a/{{cookiecutter.project_slug}}/config/settings/production.py
+++ b/{{cookiecutter.project_slug}}/config/settings/production.py
@@ -22,8 +22,6 @@ ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["{{ cookiecutter.domai
# DATABASES
# ------------------------------------------------------------------------------
-DATABASES["default"] = env.db("DATABASE_URL") # noqa F405
-DATABASES["default"]["ATOMIC_REQUESTS"] = True # noqa F405
DATABASES["default"]["CONN_MAX_AGE"] = env.int("CONN_MAX_AGE", default=60) # noqa F405
# CACHES
@@ -100,6 +98,10 @@ aws_s3_domain = AWS_S3_CUSTOM_DOMAIN or f"{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws
{% elif cookiecutter.cloud_provider == 'GCP' %}
GS_BUCKET_NAME = env("DJANGO_GCP_STORAGE_BUCKET_NAME")
GS_DEFAULT_ACL = "publicRead"
+{% elif cookiecutter.cloud_provider == 'Azure' %}
+AZURE_ACCOUNT_KEY = env("DJANGO_AZURE_ACCOUNT_KEY")
+AZURE_ACCOUNT_NAME = env("DJANGO_AZURE_ACCOUNT_NAME")
+AZURE_CONTAINER = env("DJANGO_AZURE_CONTAINER_NAME")
{% endif -%}
{% if cookiecutter.cloud_provider != 'None' or cookiecutter.use_whitenoise == 'y' -%}
@@ -116,6 +118,9 @@ STATIC_URL = f"https://{aws_s3_domain}/static/"
STATICFILES_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.StaticRootGoogleCloudStorage"
COLLECTFAST_STRATEGY = "collectfast.strategies.gcloud.GoogleCloudStrategy"
STATIC_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/static/"
+{% elif cookiecutter.cloud_provider == 'Azure' -%}
+STATICFILES_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.StaticRootAzureStorage"
+STATIC_URL = f"https://{AZURE_ACCOUNT_NAME}.blob.core.windows.net/static/"
{% endif -%}
# MEDIA
@@ -126,6 +131,9 @@ MEDIA_URL = f"https://{aws_s3_domain}/media/"
{%- elif cookiecutter.cloud_provider == 'GCP' %}
DEFAULT_FILE_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.MediaRootGoogleCloudStorage"
MEDIA_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/media/"
+{%- elif cookiecutter.cloud_provider == 'Azure' %}
+DEFAULT_FILE_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.MediaRootAzureStorage"
+MEDIA_URL = f"https://{AZURE_ACCOUNT_NAME}.blob.core.windows.net/media/"
{%- endif %}
# EMAIL
@@ -228,7 +236,7 @@ COMPRESS_ENABLED = env.bool("COMPRESS_ENABLED", default=True)
{%- if cookiecutter.cloud_provider == 'None' %}
# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_STORAGE
COMPRESS_STORAGE = "compressor.storage.GzipCompressorFileStorage"
-{%- elif cookiecutter.cloud_provider in ('AWS', 'GCP') and cookiecutter.use_whitenoise == 'n' %}
+{%- elif cookiecutter.cloud_provider in ('AWS', 'GCP', 'Azure') and cookiecutter.use_whitenoise == 'n' %}
# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_STORAGE
COMPRESS_STORAGE = STATICFILES_STORAGE
{%- endif %}
@@ -360,5 +368,15 @@ sentry_sdk.init(
traces_sample_rate=env.float("SENTRY_TRACES_SAMPLE_RATE", default=0.0),
)
{% endif %}
+{% if cookiecutter.use_drf == "y" -%}
+
+# django-rest-framework
+# -------------------------------------------------------------------------------
+# Tools that generate code samples can use SERVERS to point to the correct domain
+SPECTACULAR_SETTINGS["SERVERS"] = [ # noqa F405
+ {"url": "https://{{ cookiecutter.domain_name }}", "description": "Production server"}
+]
+
+{%- endif %}
# Your stuff...
# ------------------------------------------------------------------------------
diff --git a/{{cookiecutter.project_slug}}/config/settings/test.py b/{{cookiecutter.project_slug}}/config/settings/test.py
index f103eb10b..587b99324 100644
--- a/{{cookiecutter.project_slug}}/config/settings/test.py
+++ b/{{cookiecutter.project_slug}}/config/settings/test.py
@@ -25,9 +25,17 @@ PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
-# DEBUGING FOR TEMPLATES
+# DEBUGGING FOR TEMPLATES
# ------------------------------------------------------------------------------
TEMPLATES[0]["OPTIONS"]["debug"] = True # type: ignore # noqa F405
+{%- if cookiecutter.frontend_pipeline == 'Webpack' %}
+# django-webpack-loader
+# ------------------------------------------------------------------------------
+WEBPACK_LOADER["DEFAULT"][ # noqa F405
+ "LOADER_CLASS"
+] = "webpack_loader.loader.FakeWebpackLoader"
+
+{%- endif %}
# Your stuff...
# ------------------------------------------------------------------------------
diff --git a/{{cookiecutter.project_slug}}/config/wsgi.py b/{{cookiecutter.project_slug}}/config/wsgi.py
index a7de581ca..3fd809ef3 100644
--- a/{{cookiecutter.project_slug}}/config/wsgi.py
+++ b/{{cookiecutter.project_slug}}/config/wsgi.py
@@ -21,8 +21,8 @@ from django.core.wsgi import get_wsgi_application
# This allows easy placement of apps within the interior
# {{ cookiecutter.project_slug }} directory.
-ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent
-sys.path.append(str(ROOT_DIR / "{{ cookiecutter.project_slug }}"))
+BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
+sys.path.append(str(BASE_DIR / "{{ cookiecutter.project_slug }}"))
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
# if running multiple sites in the same mod_wsgi process. To fix this, use
# mod_wsgi daemon mode with each site in its own daemon process, or use
diff --git a/{{cookiecutter.project_slug}}/gulpfile.js b/{{cookiecutter.project_slug}}/gulpfile.js
index 680e3672e..fa3636eab 100644
--- a/{{cookiecutter.project_slug}}/gulpfile.js
+++ b/{{cookiecutter.project_slug}}/gulpfile.js
@@ -10,6 +10,7 @@ const pjson = require('./package.json')
const autoprefixer = require('autoprefixer')
const browserSync = require('browser-sync').create()
const concat = require('gulp-concat')
+const tildeImporter = require('node-sass-tilde-importer');
const cssnano = require ('cssnano')
const imagemin = require('gulp-imagemin')
const pixrem = require('pixrem')
@@ -27,7 +28,6 @@ function pathsConfig(appName) {
const vendorsRoot = 'node_modules'
return {
- bootstrapSass: `${vendorsRoot}/bootstrap/scss`,
vendorsJs: [
`${vendorsRoot}/@popperjs/core/dist/umd/popper.js`,
`${vendorsRoot}/bootstrap/dist/js/bootstrap.js`,
@@ -61,8 +61,8 @@ function styles() {
return src(`${paths.sass}/project.scss`)
.pipe(sass({
+ importer: tildeImporter,
includePaths: [
- paths.bootstrapSass,
paths.sass
]
}).on('error', sass.logError))
@@ -85,13 +85,13 @@ function scripts() {
// Vendor Javascript minification
function vendorScripts() {
- return src(paths.vendorsJs)
+ return src(paths.vendorsJs, { sourcemaps: true })
.pipe(concat('vendors.js'))
.pipe(dest(paths.js))
.pipe(plumber()) // Checks for errors
.pipe(uglify()) // Minifies the js
.pipe(rename({ suffix: '.min' }))
- .pipe(dest(paths.js))
+ .pipe(dest(paths.js, { sourcemaps: '.' }))
}
// Image compression
diff --git a/{{cookiecutter.project_slug}}/local.yml b/{{cookiecutter.project_slug}}/local.yml
index fb203acd6..66ca3b0ef 100644
--- a/{{cookiecutter.project_slug}}/local.yml
+++ b/{{cookiecutter.project_slug}}/local.yml
@@ -11,7 +11,6 @@ services:
dockerfile: ./compose/local/django/Dockerfile
image: {{ cookiecutter.project_slug }}_local_django
container_name: {{ cookiecutter.project_slug }}_local_django
- platform: linux/x86_64
depends_on:
- postgres
{%- if cookiecutter.use_celery == 'y' %}
@@ -36,15 +35,14 @@ services:
image: {{ cookiecutter.project_slug }}_production_postgres
container_name: {{ cookiecutter.project_slug }}_local_postgres
volumes:
- - {{ cookiecutter.project_slug }}_local_postgres_data:/var/lib/postgresql/data:Z
- - {{ cookiecutter.project_slug }}_local_postgres_data_backups:/backups:z
+ - {{ cookiecutter.project_slug }}_local_postgres_data:/var/lib/postgresql/data
+ - {{ cookiecutter.project_slug }}_local_postgres_data_backups:/backups
env_file:
- ./.envs/.local/.postgres
docs:
image: {{ cookiecutter.project_slug }}_local_docs
container_name: {{ cookiecutter.project_slug }}_local_docs
- platform: linux/x86_64
build:
context: .
dockerfile: ./compose/local/docs/Dockerfile
@@ -107,7 +105,7 @@ services:
command: /start-flower
{%- endif %}
- {%- if cookiecutter.frontend_pipeline == 'Gulp' %}
+ {%- if cookiecutter.frontend_pipeline in ['Gulp', 'Webpack'] %}
node:
build:
@@ -124,7 +122,9 @@ services:
command: npm run dev
ports:
- "3000:3000"
+ {%- if cookiecutter.frontend_pipeline == 'Gulp' %}
# Expose browsersync UI: https://www.browsersync.io/docs/options/#option-ui
- "3001:3001"
+ {%- endif %}
{%- endif %}
diff --git a/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py b/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py
index d702a5f67..35139fb2e 100644
--- a/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py
+++ b/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py
@@ -2,66 +2,25 @@ import os
from collections.abc import Sequence
from pathlib import Path
-import pytest
-
-ROOT_DIR_PATH = Path(__file__).parent.resolve()
-PRODUCTION_DOTENVS_DIR_PATH = ROOT_DIR_PATH / ".envs" / ".production"
-PRODUCTION_DOTENV_FILE_PATHS = [
- PRODUCTION_DOTENVS_DIR_PATH / ".django",
- PRODUCTION_DOTENVS_DIR_PATH / ".postgres",
+BASE_DIR = Path(__file__).parent.resolve()
+PRODUCTION_DOTENVS_DIR = BASE_DIR / ".envs" / ".production"
+PRODUCTION_DOTENV_FILES = [
+ PRODUCTION_DOTENVS_DIR / ".django",
+ PRODUCTION_DOTENVS_DIR / ".postgres",
]
-DOTENV_FILE_PATH = ROOT_DIR_PATH / ".env"
+DOTENV_FILE = BASE_DIR / ".env"
def merge(
- output_file_path: str, merged_file_paths: Sequence[str], append_linesep: bool = True
+ output_file: Path,
+ files_to_merge: Sequence[Path],
) -> None:
- with open(output_file_path, "w") as output_file:
- for merged_file_path in merged_file_paths:
- with open(merged_file_path) as merged_file:
- merged_file_content = merged_file.read()
- output_file.write(merged_file_content)
- if append_linesep:
- output_file.write(os.linesep)
-
-
-def main():
- merge(DOTENV_FILE_PATH, PRODUCTION_DOTENV_FILE_PATHS)
-
-
-@pytest.mark.parametrize("merged_file_count", range(3))
-@pytest.mark.parametrize("append_linesep", [True, False])
-def test_merge(tmpdir_factory, merged_file_count: int, append_linesep: bool):
- tmp_dir_path = Path(str(tmpdir_factory.getbasetemp()))
-
- output_file_path = tmp_dir_path / ".env"
-
- expected_output_file_content = ""
- merged_file_paths = []
- for i in range(merged_file_count):
- merged_file_ord = i + 1
-
- merged_filename = f".service{merged_file_ord}"
- merged_file_path = tmp_dir_path / merged_filename
-
- merged_file_content = merged_filename * merged_file_ord
-
- with open(merged_file_path, "w+") as file:
- file.write(merged_file_content)
-
- expected_output_file_content += merged_file_content
- if append_linesep:
- expected_output_file_content += os.linesep
-
- merged_file_paths.append(merged_file_path)
-
- merge(output_file_path, merged_file_paths, append_linesep)
-
- with open(output_file_path) as output_file:
- actual_output_file_content = output_file.read()
-
- assert actual_output_file_content == expected_output_file_content
+ merged_content = ""
+ for merge_file in files_to_merge:
+ merged_content += merge_file.read_text()
+ merged_content += os.linesep
+ output_file.write_text(merged_content)
if __name__ == "__main__":
- main()
+ merge(DOTENV_FILE, PRODUCTION_DOTENV_FILES)
diff --git a/{{cookiecutter.project_slug}}/package.json b/{{cookiecutter.project_slug}}/package.json
index bff0a34af..90ac63763 100644
--- a/{{cookiecutter.project_slug}}/package.json
+++ b/{{cookiecutter.project_slug}}/package.json
@@ -1,13 +1,17 @@
{
"name": "{{cookiecutter.project_slug}}",
"version": "{{ cookiecutter.version }}",
- "dependencies": {},
"devDependencies": {
- "bootstrap": "^5.1.3",
- "gulp-concat": "^2.6.1",
+ "@babel/core": "^7.16.5",
+ "@babel/preset-env": "^7.16.5",
"@popperjs/core": "^2.10.2",
"autoprefixer": "^10.4.0",
+ "babel-loader": "^9.1.2",
+ "bootstrap": "^5.1.3",
"browser-sync": "^2.27.7",
+ "css-loader": "^6.5.1",
+ "gulp-concat": "^2.6.1",
+ "concurrently": "^7.0.0",
"cssnano": "^5.0.11",
"gulp": "^4.0.2",
"gulp-imagemin": "^7.1.0",
@@ -16,9 +20,19 @@
"gulp-rename": "^2.0.0",
"gulp-sass": "^5.0.0",
"gulp-uglify-es": "^3.0.0",
+ "mini-css-extract-plugin": "^2.4.5",
+ "node-sass-tilde-importer": "^1.0.2",
"pixrem": "^5.0.0",
"postcss": "^8.3.11",
- "sass": "^1.43.4"
+ "postcss-loader": "^7.0.2",
+ "postcss-preset-env": "^8.0.1",
+ "sass": "^1.43.4",
+ "sass-loader": "^13.2.0",
+ "webpack": "^5.65.0",
+ "webpack-bundle-tracker": "^1.4.0",
+ "webpack-cli": "^5.0.1",
+ "webpack-dev-server": "^4.6.0",
+ "webpack-merge": "^5.8.0"
},
"engines": {
"node": "16"
@@ -26,8 +40,11 @@
"browserslist": [
"last 2 versions"
],
+ "babel": {
+ "presets": ["@babel/preset-env"]
+ },
"scripts": {
- "dev": "gulp",
- "build": "gulp generate-assets"
+ "dev": "",
+ "build": ""
}
}
diff --git a/{{cookiecutter.project_slug}}/production.yml b/{{cookiecutter.project_slug}}/production.yml
index 4c1d64c20..4e9b9e048 100644
--- a/{{cookiecutter.project_slug}}/production.yml
+++ b/{{cookiecutter.project_slug}}/production.yml
@@ -4,14 +4,33 @@ volumes:
production_postgres_data: {}
production_postgres_data_backups: {}
production_traefik: {}
+ {%- if cookiecutter.cloud_provider == 'None' %}
+ production_django_media: {}
+ {%- endif %}
services:
django:{% if cookiecutter.use_celery == 'y' %} &django{% endif %}
build:
context: .
dockerfile: ./compose/production/django/Dockerfile
+ {%- if cookiecutter.frontend_pipeline == 'Webpack' and cookiecutter.use_whitenoise == 'n' %}
+ args:
+ # These variable can be defined in an .env file in the root of the repo
+ {%- if cookiecutter.cloud_provider == 'AWS' %}
+ DJANGO_AWS_STORAGE_BUCKET_NAME: ${DJANGO_AWS_STORAGE_BUCKET_NAME}
+ DJANGO_AWS_S3_CUSTOM_DOMAIN: ${DJANGO_AWS_S3_CUSTOM_DOMAIN}
+ {%- elif cookiecutter.cloud_provider == 'GCP' %}
+ DJANGO_GCP_STORAGE_BUCKET_NAME: ${DJANGO_GCP_STORAGE_BUCKET_NAME}
+ {%- elif cookiecutter.cloud_provider == 'Azure' %}
+ DJANGO_AZURE_ACCOUNT_NAME: ${DJANGO_AZURE_ACCOUNT_NAME}
+ {%- endif %}
+ {%- endif %}
+
image: {{ cookiecutter.project_slug }}_production_django
- platform: linux/x86_64
+ {%- if cookiecutter.cloud_provider == 'None' %}
+ volumes:
+ - production_django_media:/app/{{ cookiecutter.project_slug }}/media
+ {%- endif %}
depends_on:
- postgres
- redis
@@ -26,8 +45,8 @@ services:
dockerfile: ./compose/production/postgres/Dockerfile
image: {{ cookiecutter.project_slug }}_production_postgres
volumes:
- - production_postgres_data:/var/lib/postgresql/data:Z
- - production_postgres_data_backups:/backups:z
+ - production_postgres_data:/var/lib/postgresql/data
+ - production_postgres_data_backups:/backups
env_file:
- ./.envs/.production/.postgres
@@ -39,7 +58,7 @@ services:
depends_on:
- django
volumes:
- - production_traefik:/etc/traefik/acme:z
+ - production_traefik:/etc/traefik/acme
ports:
- "0.0.0.0:80:80"
- "0.0.0.0:443:443"
@@ -77,3 +96,14 @@ services:
volumes:
- production_postgres_data_backups:/backups:z
{%- endif %}
+ {%- if cookiecutter.cloud_provider == 'None' %}
+ nginx:
+ build:
+ context: .
+ dockerfile: ./compose/production/nginx/Dockerfile
+ image: {{ cookiecutter.project_slug }}_local_nginx
+ depends_on:
+ - django
+ volumes:
+ - production_django_media:/usr/share/nginx/media:ro
+ {%- endif %}
diff --git a/{{cookiecutter.project_slug}}/requirements/base.txt b/{{cookiecutter.project_slug}}/requirements/base.txt
index fcc7c48a1..1550be1bb 100644
--- a/{{cookiecutter.project_slug}}/requirements/base.txt
+++ b/{{cookiecutter.project_slug}}/requirements/base.txt
@@ -1,20 +1,20 @@
-pytz==2022.6 # https://github.com/stub42/pytz
-python-slugify==7.0.0 # https://github.com/un33k/python-slugify
-Pillow==9.3.0 # https://github.com/python-pillow/Pillow
+pytz==2022.7.1 # https://github.com/stub42/pytz
+python-slugify==8.0.1 # https://github.com/un33k/python-slugify
+Pillow==9.4.0 # https://github.com/python-pillow/Pillow
{%- if cookiecutter.frontend_pipeline == 'Django Compressor' %}
{%- if cookiecutter.windows == 'y' and cookiecutter.use_docker == 'n' %}
rcssmin==1.1.0 --install-option="--without-c-extensions" # https://github.com/ndparker/rcssmin
{%- else %}
-rcssmin==1.1.0 # https://github.com/ndparker/rcssmin
+rcssmin==1.1.1 # https://github.com/ndparker/rcssmin
{%- endif %}
{%- endif %}
argon2-cffi==21.3.0 # https://github.com/hynek/argon2_cffi
{%- if cookiecutter.use_whitenoise == 'y' %}
-whitenoise==6.2.0 # https://github.com/evansd/whitenoise
+whitenoise==6.4.0 # https://github.com/evansd/whitenoise
{%- endif %}
-redis==4.3.5 # https://github.com/redis/redis-py
+redis==4.5.1 # https://github.com/redis/redis-py
{%- if cookiecutter.use_docker == "y" or cookiecutter.windows == "n" %}
-hiredis==2.0.0 # https://github.com/redis/hiredis-py
+hiredis==2.2.2 # https://github.com/redis/hiredis-py
{%- endif %}
{%- if cookiecutter.use_celery == "y" %}
celery==5.2.7 # pyup: < 6.0 # https://github.com/celery/celery
@@ -29,20 +29,23 @@ uvicorn[standard]==0.20.0 # https://github.com/encode/uvicorn
# Django
# ------------------------------------------------------------------------------
-django==4.0.8 # pyup: < 4.1 # https://www.djangoproject.com/
-django-environ==0.9.0 # https://github.com/joke2k/django-environ
-django-model-utils==4.2.0 # https://github.com/jazzband/django-model-utils
-django-allauth==0.51.0 # https://github.com/pennersr/django-allauth
-django-crispy-forms==1.14.0 # https://github.com/django-crispy-forms/django-crispy-forms
+django==4.0.10 # pyup: < 4.1 # https://www.djangoproject.com/
+django-environ==0.10.0 # https://github.com/joke2k/django-environ
+django-model-utils==4.3.1 # https://github.com/jazzband/django-model-utils
+django-allauth==0.52.0 # https://github.com/pennersr/django-allauth
+django-crispy-forms==2.0 # https://github.com/django-crispy-forms/django-crispy-forms
crispy-bootstrap5==0.7 # https://github.com/django-crispy-forms/crispy-bootstrap5
{%- if cookiecutter.frontend_pipeline == 'Django Compressor' %}
-django-compressor==4.1 # https://github.com/django-compressor/django-compressor
+django-compressor==4.3.1 # https://github.com/django-compressor/django-compressor
{%- endif %}
django-redis==5.2.0 # https://github.com/jazzband/django-redis
{%- if cookiecutter.use_drf == 'y' %}
# Django REST Framework
djangorestframework==3.14.0 # https://github.com/encode/django-rest-framework
-django-cors-headers==3.13.0 # https://github.com/adamchainz/django-cors-headers
+django-cors-headers==3.14.0 # https://github.com/adamchainz/django-cors-headers
# DRF-spectacular for api documentation
-drf-spectacular==0.24.2 # https://github.com/tfranzel/drf-spectacular
+drf-spectacular==0.26.0 # https://github.com/tfranzel/drf-spectacular
+{%- endif %}
+{%- if cookiecutter.frontend_pipeline == 'Webpack' %}
+django-webpack-loader==1.8.1 # https://github.com/django-webpack/django-webpack-loader
{%- endif %}
diff --git a/{{cookiecutter.project_slug}}/requirements/local.txt b/{{cookiecutter.project_slug}}/requirements/local.txt
index 130cb24cc..218cdcaad 100644
--- a/{{cookiecutter.project_slug}}/requirements/local.txt
+++ b/{{cookiecutter.project_slug}}/requirements/local.txt
@@ -1,7 +1,7 @@
-r base.txt
-Werkzeug[watchdog]==2.2.2 # https://github.com/pallets/werkzeug
-ipdb==0.13.9 # https://github.com/gotcha/ipdb
+Werkzeug[watchdog]==2.2.3 # https://github.com/pallets/werkzeug
+ipdb==0.13.13 # https://github.com/gotcha/ipdb
{%- if cookiecutter.use_docker == 'y' %}
psycopg2==2.9.5 # https://github.com/psycopg/psycopg2
{%- else %}
@@ -13,36 +13,36 @@ watchfiles==0.18.1 # https://github.com/samuelcolvin/watchfiles
# Testing
# ------------------------------------------------------------------------------
-mypy==0.982 # https://github.com/python/mypy
-django-stubs==1.12.0 # https://github.com/typeddjango/django-stubs
-pytest==7.2.0 # https://github.com/pytest-dev/pytest
+mypy==1.1.1 # https://github.com/python/mypy
+django-stubs==1.15.0 # https://github.com/typeddjango/django-stubs
+pytest==7.2.2 # https://github.com/pytest-dev/pytest
pytest-sugar==0.9.6 # https://github.com/Frozenball/pytest-sugar
{%- if cookiecutter.use_drf == "y" %}
-djangorestframework-stubs==1.7.0 # https://github.com/typeddjango/djangorestframework-stubs
+djangorestframework-stubs==1.9.1 # https://github.com/typeddjango/djangorestframework-stubs
{%- endif %}
# Documentation
# ------------------------------------------------------------------------------
-sphinx==5.3.0 # https://github.com/sphinx-doc/sphinx
+sphinx==6.1.3 # https://github.com/sphinx-doc/sphinx
sphinx-autobuild==2021.3.14 # https://github.com/GaretJax/sphinx-autobuild
# Code quality
# ------------------------------------------------------------------------------
-flake8==5.0.4 # https://github.com/PyCQA/flake8
-flake8-isort==5.0.0 # https://github.com/gforcada/flake8-isort
-coverage==6.5.0 # https://github.com/nedbat/coveragepy
-black==22.10.0 # https://github.com/psf/black
+flake8==6.0.0 # https://github.com/PyCQA/flake8
+flake8-isort==6.0.0 # https://github.com/gforcada/flake8-isort
+coverage==7.2.1 # https://github.com/nedbat/coveragepy
+black==23.1.0 # https://github.com/psf/black
pylint-django==2.5.3 # https://github.com/PyCQA/pylint-django
{%- if cookiecutter.use_celery == 'y' %}
pylint-celery==0.3 # https://github.com/PyCQA/pylint-celery
{%- endif %}
-pre-commit==2.20.0 # https://github.com/pre-commit/pre-commit
+pre-commit==3.1.1 # https://github.com/pre-commit/pre-commit
# Django
# ------------------------------------------------------------------------------
factory-boy==3.2.1 # https://github.com/FactoryBoy/factory_boy
-django-debug-toolbar==3.7.0 # https://github.com/jazzband/django-debug-toolbar
+django-debug-toolbar==3.8.1 # https://github.com/jazzband/django-debug-toolbar
django-extensions==3.2.1 # https://github.com/django-extensions/django-extensions
-django-coverage-plugin==2.0.4 # https://github.com/nedbat/django_coverage_plugin
+django-coverage-plugin==3.0.0 # https://github.com/nedbat/django_coverage_plugin
pytest-django==4.5.2 # https://github.com/pytest-dev/pytest-django
diff --git a/{{cookiecutter.project_slug}}/requirements/production.txt b/{{cookiecutter.project_slug}}/requirements/production.txt
index 275633dd4..3d725fae7 100644
--- a/{{cookiecutter.project_slug}}/requirements/production.txt
+++ b/{{cookiecutter.project_slug}}/requirements/production.txt
@@ -8,35 +8,37 @@ psycopg2==2.9.5 # https://github.com/psycopg/psycopg2
Collectfast==2.2.0 # https://github.com/antonagestam/collectfast
{%- endif %}
{%- if cookiecutter.use_sentry == "y" %}
-sentry-sdk==1.11.1 # https://github.com/getsentry/sentry-python
+sentry-sdk==1.16.0 # https://github.com/getsentry/sentry-python
{%- endif %}
{%- if cookiecutter.use_docker == "n" and cookiecutter.windows == "y" %}
-hiredis==2.0.0 # https://github.com/redis/hiredis-py
+hiredis==2.2.2 # https://github.com/redis/hiredis-py
{%- endif %}
# Django
# ------------------------------------------------------------------------------
{%- if cookiecutter.cloud_provider == 'AWS' %}
-django-storages[boto3]==1.13.1 # https://github.com/jschneier/django-storages
+django-storages[boto3]==1.13.2 # https://github.com/jschneier/django-storages
{%- elif cookiecutter.cloud_provider == 'GCP' %}
-django-storages[google]==1.13.1 # https://github.com/jschneier/django-storages
+django-storages[google]==1.13.2 # https://github.com/jschneier/django-storages
+{%- elif cookiecutter.cloud_provider == 'Azure' %}
+django-storages[azure]==1.13.2 # https://github.com/jschneier/django-storages
{%- endif %}
{%- if cookiecutter.mail_service == 'Mailgun' %}
-django-anymail[mailgun]==8.6 # https://github.com/anymail/django-anymail
+django-anymail[mailgun]==9.0 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'Amazon SES' %}
-django-anymail[amazon_ses]==8.6 # https://github.com/anymail/django-anymail
+django-anymail[amazon_ses]==9.0 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'Mailjet' %}
-django-anymail[mailjet]==8.6 # https://github.com/anymail/django-anymail
+django-anymail[mailjet]==9.0 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'Mandrill' %}
-django-anymail[mandrill]==8.6 # https://github.com/anymail/django-anymail
+django-anymail[mandrill]==9.0 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'Postmark' %}
-django-anymail[postmark]==8.6 # https://github.com/anymail/django-anymail
+django-anymail[postmark]==9.0 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'Sendgrid' %}
-django-anymail[sendgrid]==8.6 # https://github.com/anymail/django-anymail
+django-anymail[sendgrid]==9.0 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'SendinBlue' %}
-django-anymail[sendinblue]==8.6 # https://github.com/anymail/django-anymail
+django-anymail[sendinblue]==9.0 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'SparkPost' %}
-django-anymail[sparkpost]==8.6 # https://github.com/anymail/django-anymail
+django-anymail[sparkpost]==9.0 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'Other SMTP' %}
-django-anymail==8.6 # https://github.com/anymail/django-anymail
+django-anymail==9.0 # https://github.com/anymail/django-anymail
{%- endif %}
diff --git a/{{cookiecutter.project_slug}}/setup.cfg b/{{cookiecutter.project_slug}}/setup.cfg
index 3bec1fbee..e282af36a 100644
--- a/{{cookiecutter.project_slug}}/setup.cfg
+++ b/{{cookiecutter.project_slug}}/setup.cfg
@@ -1,10 +1,10 @@
[flake8]
max-line-length = 120
-exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv
+exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv,.venv
[pycodestyle]
max-line-length = 120
-exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv
+exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv,.venv
[isort]
line_length = 88
@@ -34,7 +34,7 @@ django_settings_module = config.settings.test
ignore_errors = True
[coverage:run]
-include = {{cookiecutter.project_slug}}/*
-omit = *migrations*, *tests*
+include = {{cookiecutter.project_slug}}/**
+omit = */migrations/*, */tests/*
plugins =
django_coverage_plugin
diff --git a/{{cookiecutter.project_slug}}/tests/test_merge_production_dotenvs_in_dotenv.py b/{{cookiecutter.project_slug}}/tests/test_merge_production_dotenvs_in_dotenv.py
new file mode 100644
index 000000000..c0e68f60a
--- /dev/null
+++ b/{{cookiecutter.project_slug}}/tests/test_merge_production_dotenvs_in_dotenv.py
@@ -0,0 +1,34 @@
+from pathlib import Path
+
+import pytest
+
+from merge_production_dotenvs_in_dotenv import merge
+
+
+@pytest.mark.parametrize(
+ ("input_contents", "expected_output"),
+ [
+ ([], ""),
+ ([""], "\n"),
+ (["JANE=doe"], "JANE=doe\n"),
+ (["SEP=true", "AR=ator"], "SEP=true\nAR=ator\n"),
+ (["A=0", "B=1", "C=2"], "A=0\nB=1\nC=2\n"),
+ (["X=x\n", "Y=y", "Z=z\n"], "X=x\n\nY=y\nZ=z\n\n"),
+ ],
+)
+def test_merge(
+ tmp_path: Path,
+ input_contents: list[str],
+ expected_output: str,
+):
+ output_file = tmp_path / ".env"
+
+ files_to_merge = []
+ for num, input_content in enumerate(input_contents, start=1):
+ merge_file = tmp_path / f".service{num}"
+ merge_file.write_text(input_content)
+ files_to_merge.append(merge_file)
+
+ merge(output_file, files_to_merge)
+
+ assert output_file.read_text() == expected_output
diff --git a/{{cookiecutter.project_slug}}/webpack/common.config.js b/{{cookiecutter.project_slug}}/webpack/common.config.js
new file mode 100644
index 000000000..41efb1c77
--- /dev/null
+++ b/{{cookiecutter.project_slug}}/webpack/common.config.js
@@ -0,0 +1,55 @@
+const path = require('path');
+const BundleTracker = require('webpack-bundle-tracker');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+
+module.exports = {
+ target: "web",
+ context: path.join(__dirname, '../'),
+ entry: {
+ 'project': path.resolve(__dirname, '../{{cookiecutter.project_slug}}/static/js/project'),
+ 'vendors': path.resolve(__dirname, '../{{cookiecutter.project_slug}}/static/js/vendors'),
+ },
+ output: {
+ path: path.resolve(__dirname, '../{{cookiecutter.project_slug}}/static/webpack_bundles/'),
+ publicPath: '/static/webpack_bundles/',
+ filename: 'js/[name]-[fullhash].js',
+ chunkFilename: 'js/[name]-[hash].js',
+ },
+ plugins: [
+ new BundleTracker({filename: path.resolve(__dirname, '../webpack-stats.json')}),
+ new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash].css' }),
+ ],
+ module: {
+ rules: [
+ // we pass the output from babel loader to react-hot loader
+ {
+ test: /\.js$/,
+ loader: 'babel-loader',
+ },
+ {
+ test: /\.s?css$/i,
+ use: [
+ MiniCssExtractPlugin.loader,
+ 'css-loader',
+ {
+ loader: 'postcss-loader',
+ options: {
+ postcssOptions: {
+ plugins: [
+ 'postcss-preset-env',
+ 'autoprefixer',
+ 'pixrem',
+ ],
+ },
+ },
+ },
+ 'sass-loader',
+ ],
+ },
+ ],
+ },
+ resolve: {
+ modules: ['node_modules'],
+ extensions: ['.js', '.jsx'],
+ },
+};
diff --git a/{{cookiecutter.project_slug}}/webpack/dev.config.js b/{{cookiecutter.project_slug}}/webpack/dev.config.js
new file mode 100644
index 000000000..c2f14abb1
--- /dev/null
+++ b/{{cookiecutter.project_slug}}/webpack/dev.config.js
@@ -0,0 +1,20 @@
+const { merge } = require('webpack-merge');
+const commonConfig = require('./common.config');
+
+module.exports = merge(commonConfig, {
+ mode: 'development',
+ devtool: 'inline-source-map',
+ devServer: {
+ port: 3000,
+ proxy: {
+ {%- if cookiecutter.use_docker == 'n' %}
+ '/': 'http://0.0.0.0:8000',
+ {%- else %}
+ '/': 'http://django:8000',
+ {%- endif %}
+ },
+ // We need hot=false (Disable HMR) to set liveReload=true
+ hot: false,
+ liveReload: true,
+ },
+});
diff --git a/{{cookiecutter.project_slug}}/webpack/prod.config.js b/{{cookiecutter.project_slug}}/webpack/prod.config.js
new file mode 100644
index 000000000..b9c2186e5
--- /dev/null
+++ b/{{cookiecutter.project_slug}}/webpack/prod.config.js
@@ -0,0 +1,28 @@
+const { merge } = require('webpack-merge');
+const commonConfig = require('./common.config');
+
+// This variable should mirror the one from config/settings/production.py
+{%- if cookiecutter.use_whitenoise == 'n' %}
+{%- if cookiecutter.cloud_provider == 'AWS' %}
+const s3BucketName = process.env.DJANGO_AWS_STORAGE_BUCKET_NAME;
+const awsS3Domain = process.env.DJANGO_AWS_S3_CUSTOM_DOMAIN ?
+ process.env.DJANGO_AWS_S3_CUSTOM_DOMAIN
+ : `${s3BucketName}.s3.amazonaws.com`;
+const staticUrl = `https://${awsS3Domain}/static/`;
+{%- elif cookiecutter.cloud_provider == 'GCP' %}
+const staticUrl = `https://storage.googleapis.com/${process.env.DJANGO_GCP_STORAGE_BUCKET_NAME}/static/`;
+{%- elif cookiecutter.cloud_provider == 'Azure' %}
+const staticUrl = `https://${process.env.DJANGO_AZURE_ACCOUNT_NAME}.blob.core.windows.net/static/`;
+{%- endif %}
+{%- else %}
+const staticUrl = '/static/';
+{%- endif %}
+
+module.exports = merge(commonConfig, {
+ mode: 'production',
+ devtool: 'source-map',
+ bail: true,
+ output: {
+ publicPath: `${staticUrl}webpack_bundles/`,
+ },
+});
diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/js/project.js b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/js/project.js
index d26d23b9b..62770f1c9 100644
--- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/js/project.js
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/js/project.js
@@ -1 +1,5 @@
+{%- if cookiecutter.frontend_pipeline == 'Webpack' %}
+import '../sass/project.scss';
+{%- endif %}
+
/* Project specific Javascript goes here. */
diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/js/vendors.js b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/js/vendors.js
new file mode 100644
index 000000000..093840f87
--- /dev/null
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/js/vendors.js
@@ -0,0 +1,2 @@
+import '@popperjs/core';
+import 'bootstrap';
diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/sass/project.scss b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/sass/project.scss
index 370096bb3..c9511e720 100644
--- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/sass/project.scss
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/sass/project.scss
@@ -1,5 +1,5 @@
@import "custom_bootstrap_vars";
-@import "bootstrap";
+@import "~bootstrap/scss/bootstrap";
// project specific CSS goes here
diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html
index 78db7701d..abb0f1cd1 100644
--- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html
@@ -1,4 +1,8 @@
-{% raw %}{% load static i18n {% endraw %}{% if cookiecutter.frontend_pipeline == 'Django Compressor' %}compress{% endif %}{% raw %}%}
+{% raw %}{% load static i18n {% endraw %}
+{%- if cookiecutter.frontend_pipeline == 'Django Compressor' %}compress
+{%- endif %}{% raw %}%}{% endraw %}
+{%- if cookiecutter.frontend_pipeline == 'Webpack' %}{% raw %}{% load render_bundle from webpack_loader %}{% endraw %}
+{%- endif %}{% raw %}
{% get_current_language as LANGUAGE_CODE %}
@@ -13,7 +17,7 @@
{% block css %}
{%- endraw %}
- {%- if cookiecutter.frontend_pipeline != 'Gulp' %}
+ {%- if cookiecutter.frontend_pipeline in ['None', 'Django Compressor'] %}
{%- raw %}
@@ -31,6 +35,8 @@
{% endcompress %}
{%- endraw %}{% elif cookiecutter.frontend_pipeline == 'Gulp' %}{% raw %}
+ {%- endraw %}{% elif cookiecutter.frontend_pipeline == "Webpack" %}{% raw %}
+ {% render_bundle 'project' 'css' %}
{%- endraw %}{% endif %}{% raw %}
{% endblock %}
+
+ {%- endraw %}{% elif cookiecutter.frontend_pipeline == "Webpack" %}{% raw %}
+
+ {% render_bundle 'vendors' 'js' attrs='defer' %}
{%- endraw %}{% else %}{% raw %}
@@ -55,6 +64,8 @@
{% endcompress %}
{%- endraw %}{% elif cookiecutter.frontend_pipeline == 'Gulp' %}{% raw %}
+ {%- endraw %}{% elif cookiecutter.frontend_pipeline == "Webpack" %}{% raw %}
+ {% render_bundle 'project' 'js' attrs='defer' %}
{%- endraw %}{% endif %}{% raw %}
{% endblock javascript %}
diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py
index 233994bb0..a13674517 100644
--- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py
@@ -10,7 +10,6 @@ User = get_user_model()
@admin.register(User)
class UserAdmin(auth_admin.UserAdmin):
-
form = UserAdminChangeForm
add_form = UserAdminCreationForm
fieldsets = (
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 65c4d6987..7c89a8b8d 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
@@ -1,27 +1,32 @@
-from django.test import RequestFactory
+import pytest
+from rest_framework.test import APIRequestFactory
from {{ cookiecutter.project_slug }}.users.api.views import UserViewSet
from {{ cookiecutter.project_slug }}.users.models import User
class TestUserViewSet:
- def test_get_queryset(self, user: User, rf: RequestFactory):
+ @pytest.fixture
+ def api_rf(self) -> APIRequestFactory:
+ return APIRequestFactory()
+
+ def test_get_queryset(self, user: User, api_rf: APIRequestFactory):
view = UserViewSet()
- request = rf.get("/fake-url/")
+ request = api_rf.get("/fake-url/")
request.user = user
view.request = request
assert user in view.get_queryset()
- def test_me(self, user: User, rf: RequestFactory):
+ def test_me(self, user: User, api_rf: APIRequestFactory):
view = UserViewSet()
- request = rf.get("/fake-url/")
+ request = api_rf.get("/fake-url/")
request.user = user
view.request = request
- response = view.me(request)
+ response = view.me(request) # type: ignore
assert response.data == {
{% if cookiecutter.username_type == "email" -%}
diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py
index bb19f9f4e..bbdecddeb 100644
--- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py
@@ -9,7 +9,6 @@ User = get_user_model()
class UserDetailView(LoginRequiredMixin, DetailView):
-
model = User
{%- if cookiecutter.username_type == "email" -%}
slug_field = "id"
@@ -24,7 +23,6 @@ user_detail_view = UserDetailView.as_view()
class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
-
model = User
fields = ["name"]
success_message = _("Information successfully updated")
@@ -43,7 +41,6 @@ user_update_view = UserUpdateView.as_view()
class UserRedirectView(LoginRequiredMixin, RedirectView):
-
permanent = False
def get_redirect_url(self):
diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/storages.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/storages.py
index b712d3239..27595ad1a 100644
--- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/storages.py
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/storages.py
@@ -22,4 +22,15 @@ class StaticRootGoogleCloudStorage(GoogleCloudStorage):
class MediaRootGoogleCloudStorage(GoogleCloudStorage):
location = "media"
file_overwrite = False
+{%- elif cookiecutter.cloud_provider == 'Azure' -%}
+from storages.backends.azure_storage import AzureStorage
+
+
+class StaticRootAzureStorage(AzureStorage):
+ location = "static"
+
+
+class MediaRootAzureStorage(AzureStorage):
+ location = "media"
+ file_overwrite = False
{%- endif %}
|