diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000..3a87b269b --- /dev/null +++ b/.flake8 @@ -0,0 +1,4 @@ +[flake8] +exclude = docs +max-line-length = 119 +extend-ignore = E203 diff --git a/.github/CONTRIBUTORS-template.md b/.github/CONTRIBUTORS-template.md index d8ba28c63..bc0928478 100644 --- a/.github/CONTRIBUTORS-template.md +++ b/.github/CONTRIBUTORS-template.md @@ -22,8 +22,8 @@ accept and merge pull requests. {%- endfor %} -*Audrey is also the creator of Cookiecutter. Audrey and Daniel are on -the Cookiecutter core team.* +_Audrey is also the creator of Cookiecutter. Audrey and Daniel are on +the Cookiecutter core team._ ## Other Contributors @@ -51,6 +51,6 @@ Listed in alphabetical order. The following haven't provided code directly, but have provided guidance and advice. -- Jannis Leidel -- Nate Aune -- Barry Morrison +- Jannis Leidel +- Nate Aune +- Barry Morrison diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 53a486671..23ca7a37f 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -2,11 +2,4 @@ github: [pydanny, browniebroke] patreon: feldroy -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -custom: ["https://www.patreon.com/browniebroke"] +open_collective: cookiecutter-django diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index e984493d8..da0480f1e 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -12,41 +12,47 @@ labels: bug -* Host system configuration: - * Version of cookiecutter CLI (get it with `cookiecutter --version`): - * OS name and version: +- Host system configuration: - On Linux, run - ```bash - lsb_release -a 2> /dev/null || cat /etc/redhat-release 2> /dev/null || cat /etc/*-release 2> /dev/null || cat /etc/issue 2> /dev/null - ``` + - Version of cookiecutter CLI (get it with `cookiecutter --version`): + - OS name and version: - On MacOs, run - ```bash - sw_vers - ``` + On Linux, run - On Windows, via CMD, run - ``` - systeminfo | findstr /B /C:"OS Name" /C:"OS Version" - ``` - - - ```bash - # Insert here the OS name and version - - ``` - - * Python version, run `python3 -V`: - * Docker version (if using Docker), run `docker --version`: - * docker-compose version (if using Docker), run `docker-compose --version`: - * ... -* Options selected and/or [replay file](https://cookiecutter.readthedocs.io/en/latest/advanced/replay.html): - On Linux and MacOS: `cat ${HOME}/.cookiecutter_replay/cookiecutter-django.json` - (Please, take care to remove sensitive information) - ```json - # Insert here the replay file content + ```bash + lsb_release -a 2> /dev/null || cat /etc/redhat-release 2> /dev/null || cat /etc/*-release 2> /dev/null || cat /etc/issue 2> /dev/null ``` + + On MacOs, run + + ```bash + sw_vers + ``` + + On Windows, via CMD, run + + ``` + systeminfo | findstr /B /C:"OS Name" /C:"OS Version" + ``` + + ```bash + # Insert here the OS name and version + + ``` + + - Python version, run `python3 -V`: + - Docker version (if using Docker), run `docker --version`: + - docker compose version (if using Docker), run `docker compose --version`: + - ... + +- Options selected and/or [replay file](https://cookiecutter.readthedocs.io/en/latest/advanced/replay.html): + On Linux and macOS: `cat ${HOME}/.cookiecutter_replay/cookiecutter-django.json` + (Please, take care to remove sensitive information) + +```json + +``` + Logs:
diff --git a/.github/ISSUE_TEMPLATE/paid-support.md b/.github/ISSUE_TEMPLATE/paid-support.md index 9f997a8c8..f4e7578a8 100644 --- a/.github/ISSUE_TEMPLATE/paid-support.md +++ b/.github/ISSUE_TEMPLATE/paid-support.md @@ -5,8 +5,8 @@ about: Ask Core Team members to help you out Provided your question goes beyond [regular support](https://github.com/cookiecutter/cookiecutter-django/issues/new?template=question.md), and/or the task at hand is of timely/high priority nature use the below information to reach out for contributors directly. -* Daniel Roy Greenfeld, Project Lead ([GitHub](https://github.com/pydanny), [Patreon](https://www.patreon.com/danielroygreenfeld)): expertise in Django and AWS ELB. +- Bruno Alla, Core Developer ([GitHub](https://github.com/sponsors/browniebroke)). -* Nikita Shupeyko, Core Developer ([GitHub](https://github.com/webyneter)): expertise in Python/Django, hands-on DevOps and frontend experience. +- Daniel Roy Greenfeld, Project Lead ([GitHub](https://github.com/pydanny), [Patreon](https://www.patreon.com/danielroygreenfeld)): expertise in Django and AWS ELB. -* Bruno Alla, Core Developer ([GitHub](https://github.com/sponsors/browniebroke)). +- Nikita Shupeyko, Core Developer ([GitHub](https://github.com/webyneter)): expertise in Python/Django, hands-on DevOps and frontend experience. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 10b66e266..2b197873f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,5 @@ - ## Description diff --git a/.github/changelog-template.md b/.github/changelog-template.md index 50aab38e1..64bb71790 100644 --- a/.github/changelog-template.md +++ b/.github/changelog-template.md @@ -1,8 +1,11 @@ {%- for change_type, pulls in grouped_pulls.items() %} {%- if pulls %} + ### {{ change_type }} + {%- for pull_request in pulls %} + - {{ pull_request.title }} ([#{{ pull_request.number }}]({{ pull_request.html_url }})) -{%- endfor -%} -{% endif -%} -{% endfor -%} + {%- endfor -%} + {% endif -%} + {% endfor -%} diff --git a/.github/contributors.json b/.github/contributors.json index 190b81d38..b74dc4d15 100644 --- a/.github/contributors.json +++ b/.github/contributors.json @@ -53,6 +53,12 @@ "twitter_username": "sfdye", "is_core": true }, + { + "name": "Jelmer Draaijer", + "github_login": "foarsitter", + "twitter_username": "", + "is_core": true + }, { "name": "18", "github_login": "dezoito", @@ -553,11 +559,6 @@ "github_login": "jvanbrug", "twitter_username": "" }, - { - "name": "Jelmer Draaijer", - "github_login": "foarsitter", - "twitter_username": "" - }, { "name": "Jerome Caisip", "github_login": "jeromecaisip", @@ -1114,7 +1115,7 @@ "twitter_username": "Qoyyuum" }, { - "name": "mfosterw", + "name": "Matthew Foster Walsh", "github_login": "mfosterw", "twitter_username": "" }, @@ -1302,5 +1303,270 @@ "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": "" + }, + { + "name": "Morten Kaae", + "github_login": "MortenKaae", + "twitter_username": "" + }, + { + "name": "Birtibu", + "github_login": "Birtibu", + "twitter_username": "" + }, + { + "name": "Matheus Jardim Bernardes", + "github_login": "matheusjardimb", + "twitter_username": "" + }, + { + "name": "masavini", + "github_login": "masavini", + "twitter_username": "" + }, + { + "name": "Joseph Hanna", + "github_login": "sanchimenea", + "twitter_username": "" + }, + { + "name": "tmajerech", + "github_login": "tmajerech", + "twitter_username": "" + }, + { + "name": "villancikos", + "github_login": "villancikos", + "twitter_username": "" + }, + { + "name": "Imran Rahman", + "github_login": "infraredCoding", + "twitter_username": "" + }, + { + "name": "hleroy", + "github_login": "hleroy", + "twitter_username": "" + }, + { + "name": "Shayan Karimi", + "github_login": "shywn-mrk", + "twitter_username": "shywn_mrk" + }, + { + "name": "Sadra Yahyapour", + "github_login": "lnxpy", + "twitter_username": "lnxpylnxpy" + }, + { + "name": "Tharushan", + "github_login": "Tharushan", + "twitter_username": "" + }, + { + "name": "Fateme Fouladkar", + "github_login": "FatemeFouladkar", + "twitter_username": "" + }, + { + "name": "zhaoruibing", + "github_login": "zhaoruibing", + "twitter_username": "" + }, + { + "name": "MinWoo Sung", + "github_login": "SungMinWoo", + "twitter_username": "" + }, + { + "name": "itisnotyourenv", + "github_login": "itisnotyourenv", + "twitter_username": "" + }, + { + "name": "Vageeshan Mankala", + "github_login": "vagi8", + "twitter_username": "" + }, + { + "name": "Jakub Boukal", + "github_login": "SukiCZ", + "twitter_username": "" + }, + { + "name": "Christian Jauvin", + "github_login": "cjauvin", + "twitter_username": "" + }, + { + "name": "Plurific", + "github_login": "paulschwenn", + "twitter_username": "" + }, + { + "name": "GitBib", + "github_login": "GitBib", + "twitter_username": "" + }, + { + "name": "Freddy", + "github_login": "Hraesvelg", + "twitter_username": "" + }, + { + "name": "aiden", + "github_login": "anyidea", + "twitter_username": "" + }, + { + "name": "Michael V. Battista", + "github_login": "mvbattista", + "twitter_username": "mvbattista" + }, + { + "name": "Nix Siow", + "github_login": "nixsiow", + "twitter_username": "nixsiow" + }, + { + "name": "Jens Kaeske", + "github_login": "jkaeske", + "twitter_username": "" + }, + { + "name": "henningbra", + "github_login": "henningbra", + "twitter_username": "" + }, + { + "name": "Paul Wulff", + "github_login": "mtmpaulwulff", + "twitter_username": "" + }, + { + "name": "Mounir", + "github_login": "mounirmesselmeni", + "twitter_username": "" + }, + { + "name": "JAEGYUN JUNG", + "github_login": "TGoddessana", + "twitter_username": "" + }, + { + "name": "Simeon Emanuilov", + "github_login": "s-emanuilov", + "twitter_username": "s_emanuilov" + }, + { + "name": "Patrick Zhang", + "github_login": "PatDuJour", + "twitter_username": "" + }, + { + "name": "GvS", + "github_login": "GvS666", + "twitter_username": "" + }, + { + "name": "David Păcioianu", + "github_login": "DavidPacioianu", + "twitter_username": "" + }, + { + "name": "farwill", + "github_login": "farwill", + "twitter_username": "" + }, + { + "name": "quroom", + "github_login": "quroom", + "twitter_username": "" + }, + { + "name": "Marios Frixou", + "github_login": "frixou89", + "twitter_username": "" } ] \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8922cd450..3582a2125 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,13 +3,29 @@ version: 2 updates: + # Update Python deps for the template (not the generated project) + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "daily" + labels: + - "project infrastructure" + + # Update Python deps for the documentation + - package-ecosystem: "pip" + directory: "docs/" + schedule: + interval: "daily" + labels: + - "project infrastructure" + # Update GitHub actions in workflows - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" labels: - - "update" + - "project infrastructure" # Update npm packages - package-ecosystem: "npm" @@ -18,3 +34,71 @@ 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" + ignore: + - dependency-name: "*" + update-types: + - "version-update:semver-major" + - "version-update:semver-minor" + labels: + - "update" + + - package-ecosystem: "docker" + directory: "{{cookiecutter.project_slug}}/compose/local/docs/" + schedule: + interval: "daily" + ignore: + - dependency-name: "*" + update-types: + - "version-update:semver-major" + - "version-update:semver-minor" + 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" + ignore: + - dependency-name: "*" + update-types: + - "version-update:semver-major" + - "version-update:semver-minor" + 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..1db462916 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,18 +19,18 @@ jobs: - windows-latest - macOS-latest - name: "Run tests" + name: "pytest ${{ matrix.os }}" runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.12" cache: pip - name: Install dependencies run: pip install -r requirements.txt - name: Run tests - run: pytest tests + run: pytest -n auto tests docker: strategy: @@ -48,21 +38,25 @@ jobs: matrix: script: - name: Basic - args: "" - - name: Extended - args: "use_celery=y use_drf=y frontend_pipeline=Gulp" + args: "ci_tool=Gitlab" + - 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 COMPOSE_DOCKER_CLI_BUILD: 1 steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.12" cache: pip - name: Install dependencies run: pip install -r requirements.txt @@ -74,12 +68,16 @@ 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 use_heroku=y" + - name: Email Username + args: "username_type=email ci_tool=Github project_name='Something superduper long - the great amazing project' project_slug=my_awesome_project" - name: "${{ matrix.script.name }} Bare metal" + name: "Bare metal ${{ matrix.script.name }}" runs-on: ubuntu-latest services: redis: @@ -99,10 +97,10 @@ jobs: DATABASE_URL: "postgres://postgres:postgres@localhost:5432/postgres" steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.12" cache: pip cache-dependency-path: | requirements.txt @@ -110,8 +108,8 @@ jobs: {{cookiecutter.project_slug}}/requirements/local.txt - name: Install dependencies run: pip install -r requirements.txt - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: - node-version: "16" + node-version: "20" - name: Bare Metal ${{ matrix.script.name }} run: sh tests/test_bare.sh ${{ matrix.script.args }} diff --git a/.github/workflows/django-issue-checker.yml b/.github/workflows/django-issue-checker.yml index fd250604f..2185da81b 100644 --- a/.github/workflows/django-issue-checker.yml +++ b/.github/workflows/django-issue-checker.yml @@ -16,10 +16,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.12" - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml index a6e074137..103612cfe 100644 --- a/.github/workflows/issue-manager.yml +++ b/.github/workflows/issue-manager.yml @@ -23,18 +23,25 @@ jobs: runs-on: ubuntu-latest steps: - - uses: tiangolo/issue-manager@0.4.0 + - uses: tiangolo/issue-manager@0.5.0 with: token: ${{ secrets.GITHUB_TOKEN }} config: > { "answered": { + "delay": 864000, "message": "Assuming the question was answered, this will be automatically closed now." }, "solved": { + "delay": 864000, "message": "Assuming the original issue was solved, it will be automatically closed now." }, "waiting": { + "delay": 864000, "message": "Automatically closing after waiting for additional info. To re-open, please provide the additional information requested." + }, + "wontfix": { + "delay": 864000, + "message": "As discussed, we won't be implementing this. Automatically closing." } } diff --git a/.github/workflows/pre-commit-autoupdate.yml b/.github/workflows/pre-commit-autoupdate.yml index 1708e8b82..c01cd5141 100644 --- a/.github/workflows/pre-commit-autoupdate.yml +++ b/.github/workflows/pre-commit-autoupdate.yml @@ -16,15 +16,15 @@ jobs: # Disables this workflow from running in a repository that is not part of the indicated organization/user if: github.repository_owner == 'cookiecutter' permissions: - contents: write # for peter-evans/create-pull-request to create branch - pull-requests: write # for peter-evans/create-pull-request to create a PR + contents: write # for peter-evans/create-pull-request to create branch + pull-requests: write # for peter-evans/create-pull-request to create a PR runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.12" - name: Install pre-commit run: pip install pre-commit @@ -37,7 +37,7 @@ jobs: run: pre-commit autoupdate - name: Create Pull Request - uses: peter-evans/create-pull-request@v4 + uses: peter-evans/create-pull-request@v6 with: token: ${{ secrets.GITHUB_TOKEN }} branch: update/pre-commit-autoupdate diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml index 27113a89f..635d26f23 100644 --- a/.github/workflows/update-changelog.yml +++ b/.github/workflows/update-changelog.yml @@ -8,18 +8,18 @@ on: workflow_dispatch: jobs: - release: + update: # Disables this workflow from running in a repository that is not part of the indicated organization/user if: github.repository_owner == 'cookiecutter' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.12" - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/update-contributors.yml b/.github/workflows/update-contributors.yml index ee0c547e1..c77bc5e8f 100644 --- a/.github/workflows/update-contributors.yml +++ b/.github/workflows/update-contributors.yml @@ -13,25 +13,27 @@ jobs: # Disables this workflow from running in a repository that is not part of the indicated organization/user if: github.repository_owner == 'cookiecutter' permissions: - contents: write # for stefanzweifel/git-auto-commit-action to push code in repo + contents: write # for stefanzweifel/git-auto-commit-action to push code in repo runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.12" - name: Install dependencies run: | python -m pip install --upgrade pip 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@v5.0.1 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 10af89e41..985e519db 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,32 +1,49 @@ -exclude: "{{cookiecutter.project_slug}}" +exclude: "{{cookiecutter.project_slug}}|.github/contributors.json|CHANGELOG.md|CONTRIBUTORS.md" default_stages: [commit] +default_language_version: + python: python3.12 + repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.6.0 hooks: - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-json + - id: check-toml + - id: check-xml - id: check-yaml + - id: debug-statements + - id: check-builtin-literals + - id: check-case-conflict + - id: detect-private-key + + - repo: https://github.com/pre-commit/mirrors-prettier + rev: "v4.0.0-alpha.8" + hooks: + - id: prettier + args: ["--tab-width", "2"] - repo: https://github.com/asottile/pyupgrade - rev: v3.2.0 + rev: v3.15.2 hooks: - id: pyupgrade - args: [--py310-plus] + args: [--py312-plus] exclude: hooks/ - repo: https://github.com/psf/black - rev: 22.10.0 + rev: 24.4.2 hooks: - id: black - repo: https://github.com/PyCQA/isort - rev: 5.10.1 + rev: 5.13.2 hooks: - id: isort - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 + rev: 7.0.0 hooks: - id: flake8 diff --git a/.pyup.yml b/.pyup.yml index e5d4752e4..13d336d57 100644 --- a/.pyup.yml +++ b/.pyup.yml @@ -14,8 +14,6 @@ pin: True label_prs: update requirements: - - "requirements.txt" - - "docs/requirements.txt" - "{{cookiecutter.project_slug}}/requirements/base.txt" - "{{cookiecutter.project_slug}}/requirements/local.txt" - "{{cookiecutter.project_slug}}/requirements/production.txt" diff --git a/.readthedocs.yaml b/.readthedocs.yaml index beb30d845..872e43a2e 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -4,12 +4,17 @@ # Required version: 2 +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.12" + # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/conf.py -# Version of Python and requirements required to build the docs +# Declare the Python requirements required to build your docs python: - version: "3.8" install: - requirements: docs/requirements.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index bc6394eac..b2856f307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,2583 @@ All enhancements and patches to Cookiecutter Django will be documented in this f +## 2024.05.11 + + +### Updated + +- Update pre-commit to 3.7.1 ([#5066](https://github.com/cookiecutter/cookiecutter-django/pull/5066)) + +- Auto-update pre-commit hooks ([#5067](https://github.com/cookiecutter/cookiecutter-django/pull/5067)) + +## 2024.05.10 + + +### Updated + +- Update psycopg to 3.1.19 ([#5064](https://github.com/cookiecutter/cookiecutter-django/pull/5064)) + +- Update django-upgrade to 1.17.0 ([#5065](https://github.com/cookiecutter/cookiecutter-django/pull/5065)) + +- Auto-update pre-commit hooks ([#5062](https://github.com/cookiecutter/cookiecutter-django/pull/5062)) + +- Update ruff to 0.4.4 ([#5061](https://github.com/cookiecutter/cookiecutter-django/pull/5061)) + +## 2024.05.07 + + +### Updated + +- Update django to 4.2.13 ([#5058](https://github.com/cookiecutter/cookiecutter-django/pull/5058)) + +## 2024.05.06 + + +### Fixed + +- Fix nginx image name in production.yml ([#5053](https://github.com/cookiecutter/cookiecutter-django/pull/5053)) + +### Updated + +- Update django-model-utils to 4.5.1 ([#5044](https://github.com/cookiecutter/cookiecutter-django/pull/5044)) + +- Update django to 4.2.12 ([#5056](https://github.com/cookiecutter/cookiecutter-django/pull/5056)) + +- Update sentry-sdk to 2.1.1 ([#5055](https://github.com/cookiecutter/cookiecutter-django/pull/5055)) + +- Update ruff to 0.4.3 ([#5049](https://github.com/cookiecutter/cookiecutter-django/pull/5049)) + +## 2024.05.05 + + +### Updated + +- Auto-update pre-commit hooks ([#5046](https://github.com/cookiecutter/cookiecutter-django/pull/5046)) + +- Update django-storages to 1.14.3 ([#5047](https://github.com/cookiecutter/cookiecutter-django/pull/5047)) + +- Update coverage to 7.5.1 ([#5048](https://github.com/cookiecutter/cookiecutter-django/pull/5048)) + +## 2024.04.28 + + +### Updated + +- Update pytest to 8.2.0 ([#5034](https://github.com/cookiecutter/cookiecutter-django/pull/5034)) + +## 2024.04.27 + + +### Updated + +- Update sentry-sdk to 2.0.1 ([#5030](https://github.com/cookiecutter/cookiecutter-django/pull/5030)) + +## 2024.04.26 + + +### Updated + +- Auto-update pre-commit hooks ([#5029](https://github.com/cookiecutter/cookiecutter-django/pull/5029)) + +- Update ruff to 0.4.2 ([#5028](https://github.com/cookiecutter/cookiecutter-django/pull/5028)) + +## 2024.04.25 + + +### Updated + +- Update coverage to 7.5.0 ([#5025](https://github.com/cookiecutter/cookiecutter-django/pull/5025)) + +- Bump cssnano from 6.1.2 to 7.0.0 ([#5024](https://github.com/cookiecutter/cookiecutter-django/pull/5024)) + +- Auto-update pre-commit hooks ([#5023](https://github.com/cookiecutter/cookiecutter-django/pull/5023)) + +## 2024.04.24 + + +### Changed + +- Disable UP038 Ruff rule to avoid introducing slower code ([#5020](https://github.com/cookiecutter/cookiecutter-django/pull/5020)) + +### Updated + +- Update django-allauth to 0.62.1 ([#5021](https://github.com/cookiecutter/cookiecutter-django/pull/5021)) + +## 2024.04.23 + + +### Changed + +- Update link to djlint on pyproject.toml ([#5019](https://github.com/cookiecutter/cookiecutter-django/pull/5019)) + +### Updated + +- Update redis to 5.0.4 ([#5018](https://github.com/cookiecutter/cookiecutter-django/pull/5018)) + +- Update django-allauth to 0.62.0 ([#5016](https://github.com/cookiecutter/cookiecutter-django/pull/5016)) + +## 2024.04.22 + + +### Fixed + +- Fix broken link for sphinx-doc in generated docs ([#5015](https://github.com/cookiecutter/cookiecutter-django/pull/5015)) + +## 2024.04.20 + + +### Updated + +- Auto-update pre-commit hooks ([#5014](https://github.com/cookiecutter/cookiecutter-django/pull/5014)) + +- Update pytest-xdist to 3.6.0 ([#5013](https://github.com/cookiecutter/cookiecutter-django/pull/5013)) + +## 2024.04.19 + + +### Updated + +- Update ruff to 0.4.1 ([#5011](https://github.com/cookiecutter/cookiecutter-django/pull/5011)) + +- Update ruff to 0.4.0 ([#5007](https://github.com/cookiecutter/cookiecutter-django/pull/5007)) + +- Auto-update pre-commit hooks ([#5008](https://github.com/cookiecutter/cookiecutter-django/pull/5008)) + +- Update sphinx to 7.3.7 ([#5010](https://github.com/cookiecutter/cookiecutter-django/pull/5010)) + +## 2024.04.18 + + +### Updated + +- Update celery to 5.4.0 ([#5005](https://github.com/cookiecutter/cookiecutter-django/pull/5005)) + +- Update sphinx to 7.3.6 ([#5004](https://github.com/cookiecutter/cookiecutter-django/pull/5004)) + +## 2024.04.17 + + +### Updated + +- Update sphinx to 7.3.5 ([#5003](https://github.com/cookiecutter/cookiecutter-django/pull/5003)) + +- Update gunicorn to 22.0.0 ([#5001](https://github.com/cookiecutter/cookiecutter-django/pull/5001)) + +- Update sphinx to 7.3.3 ([#5000](https://github.com/cookiecutter/cookiecutter-django/pull/5000)) + +## 2024.04.16 + + +### Changed + +- Add a prefix setting so that swagger tags are generated in a readable way ([#4975](https://github.com/cookiecutter/cookiecutter-django/pull/4975)) + +### Fixed + +- Fix `runserver_plus` hot-reload when under Windows + Docker ([#4971](https://github.com/cookiecutter/cookiecutter-django/pull/4971)) + +### Documentation + +- Update docs for `test_bare.sh` ([#4996](https://github.com/cookiecutter/cookiecutter-django/pull/4996)) + +### Updated + +- Bump traefik from 2.11.0 to 2.11.2 ([#4993](https://github.com/cookiecutter/cookiecutter-django/pull/4993)) + +- Update sphinx-autobuild to 2024.4.16 ([#4999](https://github.com/cookiecutter/cookiecutter-django/pull/4999)) + +- Update sphinx-autobuild to 2024.4.13 ([#4991](https://github.com/cookiecutter/cookiecutter-django/pull/4991)) + +- Update sentry-sdk to 1.45.0 ([#4982](https://github.com/cookiecutter/cookiecutter-django/pull/4982)) + +- Update ruff to 0.3.7 ([#4989](https://github.com/cookiecutter/cookiecutter-django/pull/4989)) + +- Auto-update pre-commit hooks ([#4988](https://github.com/cookiecutter/cookiecutter-django/pull/4988)) + +## 2024.04.10 + + +### Updated + +- Bump python from 3.12.2 to 3.12.3 in docs ([#4979](https://github.com/cookiecutter/cookiecutter-django/pull/4979)) + +- Bump python from 3.12.2 to 3.12.3 in local ([#4981](https://github.com/cookiecutter/cookiecutter-django/pull/4981)) + +- Bump python from 3.12.2 to 3.12.3 in production ([#4980](https://github.com/cookiecutter/cookiecutter-django/pull/4980)) + +## 2024.04.09 + + +### Documentation + +- Fix start command for docs ([#4978](https://github.com/cookiecutter/cookiecutter-django/pull/4978)) + +## 2024.04.07 + + +### Updated + +- Auto-update pre-commit hooks ([#4974](https://github.com/cookiecutter/cookiecutter-django/pull/4974)) + +## 2024.04.06 + + +### Fixed + +- Fix syntax error in GitHub CI workflow ([#4972](https://github.com/cookiecutter/cookiecutter-django/pull/4972)) + +## 2024.04.05 + + +### Updated + +- Update django-webpack-loader to 3.1.0 ([#4965](https://github.com/cookiecutter/cookiecutter-django/pull/4965)) + +## 2024.04.03 + + +### Changed + +- Update GH actions to resolve deprecation warnings ([#4964](https://github.com/cookiecutter/cookiecutter-django/pull/4964)) + +### Updated + +- Update sentry-sdk to 1.44.1 ([#4963](https://github.com/cookiecutter/cookiecutter-django/pull/4963)) + +## 2024.04.02 + + +### Changed + +- Change pytest import mode to importlib ([#4950](https://github.com/cookiecutter/cookiecutter-django/pull/4950)) + +- Use main over master for branch name in deployment-on-heroku instruction ([#4954](https://github.com/cookiecutter/cookiecutter-django/pull/4954)) + +- change obsolete docker image "docker/compose:1.29.2" to "docker:25.0" ([#4961](https://github.com/cookiecutter/cookiecutter-django/pull/4961)) + +### Updated + +- Update sentry-sdk to 1.44.0 ([#4948](https://github.com/cookiecutter/cookiecutter-django/pull/4948)) + +- Update ruff to 0.3.5 ([#4955](https://github.com/cookiecutter/cookiecutter-django/pull/4955)) + +- Update gitpython to 3.1.43 ([#4951](https://github.com/cookiecutter/cookiecutter-django/pull/4951)) + +- Update pillow to 10.3.0 ([#4953](https://github.com/cookiecutter/cookiecutter-django/pull/4953)) + +- Update django-model-utils to 4.5.0 ([#4956](https://github.com/cookiecutter/cookiecutter-django/pull/4956)) + +- Update drf-spectacular to 0.27.2 ([#4957](https://github.com/cookiecutter/cookiecutter-django/pull/4957)) + +- Update werkzeug to 3.0.2 ([#4958](https://github.com/cookiecutter/cookiecutter-django/pull/4958)) + +- Auto-update pre-commit hooks ([#4959](https://github.com/cookiecutter/cookiecutter-django/pull/4959)) + +## 2024.03.29 + + +### Documentation + +- Add instruction for adding a django app ([#4944](https://github.com/cookiecutter/cookiecutter-django/pull/4944)) + +## 2024.03.27 + + +### Updated + +- Update pre-commit to 3.7.0 ([#4943](https://github.com/cookiecutter/cookiecutter-django/pull/4943)) + +- Update djangorestframework to 3.15.1 ([#4941](https://github.com/cookiecutter/cookiecutter-django/pull/4941)) + +- Update ruff to 0.3.4 ([#4936](https://github.com/cookiecutter/cookiecutter-django/pull/4936)) + +- Auto-update pre-commit hooks ([#4937](https://github.com/cookiecutter/cookiecutter-django/pull/4937)) + +## 2024.03.26 + + +### Documentation + +- Update mentions of psycopg in comments ([#4947](https://github.com/cookiecutter/cookiecutter-django/pull/4947)) + +## 2024.03.21 + + +### Changed + +- Add PostgreSQL 16, remove Postgres 10 and 11 ([#4935](https://github.com/cookiecutter/cookiecutter-django/pull/4935)) + +### Updated + +- Update uvicorn to 0.29.0 ([#4933](https://github.com/cookiecutter/cookiecutter-django/pull/4933)) + +- Update sentry-sdk to 1.43.0 ([#4934](https://github.com/cookiecutter/cookiecutter-django/pull/4934)) + +## 2024.03.19 + + +### Changed + +- Add documentation to upgrade Postgres in Docker environment. Fix: #461 ([#4898](https://github.com/cookiecutter/cookiecutter-django/pull/4898)) + +- Upgrade Python to version 3.12 ([#4930](https://github.com/cookiecutter/cookiecutter-django/pull/4930)) + +## 2024.03.18 + + +### Changed + +- Split the docs from local.yml and build the service in CI ([#4909](https://github.com/cookiecutter/cookiecutter-django/pull/4909)) + +### Updated + +- Update django-anymail to 10.3 ([#4919](https://github.com/cookiecutter/cookiecutter-django/pull/4919)) + +- Update sentry-sdk to 1.42.0 ([#4921](https://github.com/cookiecutter/cookiecutter-django/pull/4921)) + +- Update coverage to 7.4.4 ([#4926](https://github.com/cookiecutter/cookiecutter-django/pull/4926)) + +- Update ruff to 0.3.3 ([#4927](https://github.com/cookiecutter/cookiecutter-django/pull/4927)) + +- Auto-update pre-commit hooks ([#4928](https://github.com/cookiecutter/cookiecutter-django/pull/4928)) + +## 2024.03.17 + + +### Updated + +- Update djangorestframework to 3.15.0 ([#4929](https://github.com/cookiecutter/cookiecutter-django/pull/4929)) + +## 2024.03.10 + + +### Updated + +- Auto-update pre-commit hooks ([#4912](https://github.com/cookiecutter/cookiecutter-django/pull/4912)) + +- Update ruff to 0.3.2 ([#4911](https://github.com/cookiecutter/cookiecutter-django/pull/4911)) + +- Update uvicorn to 0.28.0 ([#4913](https://github.com/cookiecutter/cookiecutter-django/pull/4913)) + +- Update redis to 5.0.3 ([#4916](https://github.com/cookiecutter/cookiecutter-django/pull/4916)) + +- Update pytest to 8.1.1 ([#4914](https://github.com/cookiecutter/cookiecutter-django/pull/4914)) + +## 2024.03.07 + + +### Updated + +- Auto-update pre-commit hooks ([#4907](https://github.com/cookiecutter/cookiecutter-django/pull/4907)) + +- Update sentry-sdk to 1.41.0 ([#4908](https://github.com/cookiecutter/cookiecutter-django/pull/4908)) + +## 2024.03.06 + + +### Fixed + +- Fix fully qualified docker images ([#4905](https://github.com/cookiecutter/cookiecutter-django/pull/4905)) + +## 2024.03.04 + + +### Updated + +- Update pytest to 8.1.0 ([#4900](https://github.com/cookiecutter/cookiecutter-django/pull/4900)) + +- Update django to 4.2.11 ([#4901](https://github.com/cookiecutter/cookiecutter-django/pull/4901)) + +## 2024.03.03 + + +### Updated + +- Update django-celery-beat to 2.6.0 ([#4899](https://github.com/cookiecutter/cookiecutter-django/pull/4899)) + +## 2024.03.01 + + +### Changed + +- Add a maintainer guide to the docs ([#4884](https://github.com/cookiecutter/cookiecutter-django/pull/4884)) + +### Updated + +- Auto-update pre-commit hooks ([#4897](https://github.com/cookiecutter/cookiecutter-django/pull/4897)) + +- Update ruff to 0.3.0 ([#4896](https://github.com/cookiecutter/cookiecutter-django/pull/4896)) + +## 2024.02.28 + + +### Fixed + +- Fix invalid HTML in django-allauth field element template ([#4894](https://github.com/cookiecutter/cookiecutter-django/pull/4894)) + +- Fix permissions for media files when served by nginx ([#4889](https://github.com/cookiecutter/cookiecutter-django/pull/4889)) + +### Documentation + +- Fix broken "Two scoops of django" link in FAQ ([#4892](https://github.com/cookiecutter/cookiecutter-django/pull/4892)) + +### Updated + +- Update redis to 5.0.2 ([#4895](https://github.com/cookiecutter/cookiecutter-django/pull/4895)) + +- Update sentry-sdk to 1.40.6 ([#4893](https://github.com/cookiecutter/cookiecutter-django/pull/4893)) + +## 2024.02.26 + + +### Changed + +- Allauth elements & MFA ([#4843](https://github.com/cookiecutter/cookiecutter-django/pull/4843)) + +### Updated + +- Update pytest to 8.0.2 ([#4890](https://github.com/cookiecutter/cookiecutter-django/pull/4890)) + +- Update crispy-bootstrap5 to 2024.2 ([#4891](https://github.com/cookiecutter/cookiecutter-django/pull/4891)) + +## 2024.02.24 + + +### Updated + +- Update coverage to 7.4.3 ([#4888](https://github.com/cookiecutter/cookiecutter-django/pull/4888)) + +## 2024.02.23 + + +### Changed + +- Switch to local imports within app ([#4883](https://github.com/cookiecutter/cookiecutter-django/pull/4883)) + +- Install ruff extension in `devcontainer.json` ([#4887](https://github.com/cookiecutter/cookiecutter-django/pull/4887)) + +### Updated + +- Bump webpack-dev-server to 5.0.2 ([#4875](https://github.com/cookiecutter/cookiecutter-django/pull/4875)) + +## 2024.02.21 + + +### Changed + +- Switch to `celery.shared_task` to define tasks ([#4881](https://github.com/cookiecutter/cookiecutter-django/pull/4881)) + +- Replace usages of `get_user_model` by importing model directly ([#4879](https://github.com/cookiecutter/cookiecutter-django/pull/4879)) + +### Updated + +- Auto-update pre-commit hooks ([#4873](https://github.com/cookiecutter/cookiecutter-django/pull/4873)) + +- Update pre-commit to 3.6.2 ([#4874](https://github.com/cookiecutter/cookiecutter-django/pull/4874)) + +- Update ruff to 0.2.2 ([#4871](https://github.com/cookiecutter/cookiecutter-django/pull/4871)) + +## 2024.02.19 + + +### Updated + +- Update sentry-sdk to 1.40.5 ([#4876](https://github.com/cookiecutter/cookiecutter-django/pull/4876)) + +## 2024.02.17 + + +### Updated + +- Update pytest to 8.0.1 ([#4870](https://github.com/cookiecutter/cookiecutter-django/pull/4870)) + +## 2024.02.16 + + +### Changed + +- Speed up GitHub CI for Docker setup ([#4863](https://github.com/cookiecutter/cookiecutter-django/pull/4863)) + +### Documentation + +- Add link to the ruff repository in requirements ([#4866](https://github.com/cookiecutter/cookiecutter-django/pull/4866)) + +## 2024.02.13 + + +### Changed + +- Ruff linting & formatting ([#4834](https://github.com/cookiecutter/cookiecutter-django/pull/4834)) + +### Updated + +- Update uvicorn to 0.27.1 ([#4848](https://github.com/cookiecutter/cookiecutter-django/pull/4848)) + +- Update sentry-sdk to 1.40.4 ([#4858](https://github.com/cookiecutter/cookiecutter-django/pull/4858)) + +- Bump traefik to 2.11.0 ([#4857](https://github.com/cookiecutter/cookiecutter-django/pull/4857)) + +- Auto-update pre-commit hooks ([#4855](https://github.com/cookiecutter/cookiecutter-django/pull/4855)) + +- Update black to 24.2.0 ([#4853](https://github.com/cookiecutter/cookiecutter-django/pull/4853)) + +## 2024.02.12 + + +### Updated + +- Update django-model-utils to 4.4.0 ([#4850](https://github.com/cookiecutter/cookiecutter-django/pull/4850)) + +- Update pre-commit to 3.6.1 ([#4849](https://github.com/cookiecutter/cookiecutter-django/pull/4849)) + +- Update django-upgrade to 1.16.0 ([#4851](https://github.com/cookiecutter/cookiecutter-django/pull/4851)) + +- Auto-update pre-commit hooks ([#4852](https://github.com/cookiecutter/cookiecutter-django/pull/4852)) + +## 2024.02.09 + + +### Updated + +- Update sentry-sdk to 1.40.3 ([#4847](https://github.com/cookiecutter/cookiecutter-django/pull/4847)) + +- Update django-allauth to 0.61.1 ([#4846](https://github.com/cookiecutter/cookiecutter-django/pull/4846)) + +- Update python-slugify to 8.0.4 ([#4844](https://github.com/cookiecutter/cookiecutter-django/pull/4844)) + +## 2024.02.08 + + +### Updated + +- Bump python to 3.11.8 in compose/local/docs ([#4840](https://github.com/cookiecutter/cookiecutter-django/pull/4840)) + +- Bump python to 3.11.8 in compose/local/django ([#4841](https://github.com/cookiecutter/cookiecutter-django/pull/4841)) + +- Bump python to 3.11.8 in compose/production/django ([#4842](https://github.com/cookiecutter/cookiecutter-django/pull/4842)) + +## 2024.02.07 + + +### Changed + +- Extend docker test with deploy check ([#4838](https://github.com/cookiecutter/cookiecutter-django/pull/4838)) + +- Generic UserManager ([#4836](https://github.com/cookiecutter/cookiecutter-django/pull/4836)) + +### Updated + +- Update django-allauth to 0.61.0 ([#4839](https://github.com/cookiecutter/cookiecutter-django/pull/4839)) + +- Bump gulp-postcss to 10.0.0 ([#4835](https://github.com/cookiecutter/cookiecutter-django/pull/4835)) + +- Update sentry-sdk to 1.40.2 ([#4837](https://github.com/cookiecutter/cookiecutter-django/pull/4837)) + +- Update django to 4.2.10 ([#4833](https://github.com/cookiecutter/cookiecutter-django/pull/4833)) + +## 2024.02.05 + + +### Updated + +- Update pytest to 8.0.0 ([#4813](https://github.com/cookiecutter/cookiecutter-django/pull/4813)) + +- Update pytest-sugar to 1.0.0 ([#4828](https://github.com/cookiecutter/cookiecutter-django/pull/4828)) + +- Update sphinx-autobuild to 2024.2.4 ([#4830](https://github.com/cookiecutter/cookiecutter-django/pull/4830)) + +- Update django-debug-toolbar to 4.3.0 ([#4829](https://github.com/cookiecutter/cookiecutter-django/pull/4829)) + +- Update psycopg to 3.1.18 ([#4831](https://github.com/cookiecutter/cookiecutter-django/pull/4831)) + +## 2024.01.31 + + +### Updated + +- Update python-slugify to 8.0.3 ([#4826](https://github.com/cookiecutter/cookiecutter-django/pull/4826)) + +## 2024.01.30 + + +### Updated + +- Update pytest-django to 4.8.0 ([#4823](https://github.com/cookiecutter/cookiecutter-django/pull/4823)) + +- Update sentry-sdk to 1.40.0 ([#4822](https://github.com/cookiecutter/cookiecutter-django/pull/4822)) + +- Update uvicorn to 0.27.0.post1 ([#4818](https://github.com/cookiecutter/cookiecutter-django/pull/4818)) + +## 2024.01.29 + + +### Changed + +- Fix deprecation warning about renaming of `webpack_loader.loader` to `webpack_loader.loaders` ([#4815](https://github.com/cookiecutter/cookiecutter-django/pull/4815)) + +### Documentation + +- Update mention of coverage config file to `pyproject.toml` in documentation ([#4816](https://github.com/cookiecutter/cookiecutter-django/pull/4816)) + +### Updated + +- Update black to 24.1.1 ([#4814](https://github.com/cookiecutter/cookiecutter-django/pull/4814)) + +- Auto-update pre-commit hooks ([#4817](https://github.com/cookiecutter/cookiecutter-django/pull/4817)) + +## 2024.01.27 + + +### Changed + +- Do not show webpack devserver overlay for warnings ([#4809](https://github.com/cookiecutter/cookiecutter-django/pull/4809)) + +### Updated + +- Update python-slugify to 8.0.2 ([#4805](https://github.com/cookiecutter/cookiecutter-django/pull/4805)) + +- Update coverage to 7.4.1 ([#4807](https://github.com/cookiecutter/cookiecutter-django/pull/4807)) + +## 2024.01.26 + + +### Updated + +- Update black to 24.1.0 ([#4806](https://github.com/cookiecutter/cookiecutter-django/pull/4806)) + +## 2024.01.25 + + +### Changed + +- Replace custom static & media storage classes by passing options in the `STORAGES` setting ([#4803](https://github.com/cookiecutter/cookiecutter-django/pull/4803)) + +- Add registry to Docker images names ([#4804](https://github.com/cookiecutter/cookiecutter-django/pull/4804)) + +## 2024.01.24 + + +### Changed + +- Migrate to the unified `STORAGES` setting added in Django 4.2 ([#4477](https://github.com/cookiecutter/cookiecutter-django/pull/4477)) + +### Updated + +- Update uvicorn to 0.27.0 ([#4800](https://github.com/cookiecutter/cookiecutter-django/pull/4800)) + +## 2024.01.21 + + +### Documentation + +- Update traefik doc links ([#4798](https://github.com/cookiecutter/cookiecutter-django/pull/4798)) + +### Updated + +- Bump browser-sync from 2.29.3 to 3.0.2 in /{{cookiecutter.project_slug}} ([#4765](https://github.com/cookiecutter/cookiecutter-django/pull/4765)) + +## 2024.01.19 + + +### Updated + +- Update drf-spectacular to 0.27.1 ([#4797](https://github.com/cookiecutter/cookiecutter-django/pull/4797)) + +## 2024.01.17 + + +### Changed + +- Add a test to cover `DJANGO_ADMIN_FORCE_ALLAUTH` ([#4790](https://github.com/cookiecutter/cookiecutter-django/pull/4790)) + +### Updated + +- Bump webpack-bundle-tracker to 3.0.1 ([#4781](https://github.com/cookiecutter/cookiecutter-django/pull/4781)) + +- Update django-webpack-loader to 3.0.1 ([#4793](https://github.com/cookiecutter/cookiecutter-django/pull/4793)) + +- Bump postcss-loader to 8.0.0 ([#4795](https://github.com/cookiecutter/cookiecutter-django/pull/4795)) + +- Update uvicorn to 0.26.0 ([#4794](https://github.com/cookiecutter/cookiecutter-django/pull/4794)) + +## 2024.01.16 + + +### Updated + +- Bump sass-loader from 13.3.3 to 14.0.0 in /{{cookiecutter.project_slug}} ([#4791](https://github.com/cookiecutter/cookiecutter-django/pull/4791)) + +## 2024.01.15 + + +### Documentation + +- Update allauth documentation links ([#4786](https://github.com/cookiecutter/cookiecutter-django/pull/4786)) + +### Updated + +- Update django-allauth to 0.60.1 ([#4787](https://github.com/cookiecutter/cookiecutter-django/pull/4787)) + +## 2024.01.11 + + +### Updated + +- Update jinja2 to 3.1.3 ([#4784](https://github.com/cookiecutter/cookiecutter-django/pull/4784)) + +## 2024.01.10 + + +### Updated + +- Update sentry-sdk to 1.39.2 ([#4782](https://github.com/cookiecutter/cookiecutter-django/pull/4782)) + +## 2024.01.09 + + +### Documentation + +- Update allauth settings documentation links ([#4769](https://github.com/cookiecutter/cookiecutter-django/pull/4769)) + +### Updated + +- Update django-allauth to 0.60.0 ([#4776](https://github.com/cookiecutter/cookiecutter-django/pull/4776)) + +- Update psycopg to 3.1.17 ([#4777](https://github.com/cookiecutter/cookiecutter-django/pull/4777)) + +## 2024.01.05 + + +### Updated + +- Auto-update pre-commit hooks ([#4774](https://github.com/cookiecutter/cookiecutter-django/pull/4774)) + +- Update flake8 to 7.0.0 ([#4775](https://github.com/cookiecutter/cookiecutter-django/pull/4775)) + +## 2024.01.02 + + +### Updated + +- Update psycopg to 3.1.16 ([#4753](https://github.com/cookiecutter/cookiecutter-django/pull/4753)) + +- Update djlint to 1.34.1 ([#4773](https://github.com/cookiecutter/cookiecutter-django/pull/4773)) + +- Update uvicorn to 0.25.0 ([#4760](https://github.com/cookiecutter/cookiecutter-django/pull/4760)) + +- Auto-update pre-commit hooks ([#4749](https://github.com/cookiecutter/cookiecutter-django/pull/4749)) + +- Update black to 23.12.1 ([#4772](https://github.com/cookiecutter/cookiecutter-django/pull/4772)) + +- Update pillow to 10.2.0 ([#4770](https://github.com/cookiecutter/cookiecutter-django/pull/4770)) + +- Update django to 4.2.9 ([#4771](https://github.com/cookiecutter/cookiecutter-django/pull/4771)) + +- Update pytest to 7.4.4 ([#4767](https://github.com/cookiecutter/cookiecutter-django/pull/4767)) + +- Update coverage to 7.4.0 ([#4764](https://github.com/cookiecutter/cookiecutter-django/pull/4764)) + +## 2023.12.19 + + +### Changed + +- Upgrade debian to 12 bookworm ([#4745](https://github.com/cookiecutter/cookiecutter-django/pull/4745)) + +### Updated + +- Update hiredis to 2.3.2 ([#4750](https://github.com/cookiecutter/cookiecutter-django/pull/4750)) + +## 2023.12.15 + + +### Updated + +- Update coverage to 7.3.3 ([#4748](https://github.com/cookiecutter/cookiecutter-django/pull/4748)) + +- Update psycopg to 3.1.15 ([#4747](https://github.com/cookiecutter/cookiecutter-django/pull/4747)) + +- Update sentry-sdk to 1.39.1 ([#4746](https://github.com/cookiecutter/cookiecutter-django/pull/4746)) + +- Auto-update pre-commit hooks ([#4743](https://github.com/cookiecutter/cookiecutter-django/pull/4743)) + +## 2023.12.13 + + +### Updated + +- Auto-update pre-commit hooks ([#4740](https://github.com/cookiecutter/cookiecutter-django/pull/4740)) + +## 2023.12.12 + + +### Updated + +- Update django-allauth to 0.59.0 ([#4739](https://github.com/cookiecutter/cookiecutter-django/pull/4739)) + +- Update sentry-sdk to 1.39.0 ([#4738](https://github.com/cookiecutter/cookiecutter-django/pull/4738)) + +- Update black to 23.12.0 ([#4737](https://github.com/cookiecutter/cookiecutter-django/pull/4737)) + +- Auto-update pre-commit hooks ([#4736](https://github.com/cookiecutter/cookiecutter-django/pull/4736)) + +- Update drf-spectacular to 0.27.0 ([#4735](https://github.com/cookiecutter/cookiecutter-django/pull/4735)) + +## 2023.12.11 + + +### Updated + +- Auto-update pre-commit hooks ([#4730](https://github.com/cookiecutter/cookiecutter-django/pull/4730)) + +## 2023.12.10 + + +### Updated + +- Update pre-commit to 3.6.0 ([#4728](https://github.com/cookiecutter/cookiecutter-django/pull/4728)) + +- Auto-update pre-commit hooks ([#4729](https://github.com/cookiecutter/cookiecutter-django/pull/4729)) + +## 2023.12.09 + + +### Changed + +- Add missing __init__.py file to api module ([#4726](https://github.com/cookiecutter/cookiecutter-django/pull/4726)) + +## 2023.12.07 + + +### Updated + +- Bump actions/setup-python from 4 to 5 ([#4723](https://github.com/cookiecutter/cookiecutter-django/pull/4723)) + +- Auto-update pre-commit hooks ([#4709](https://github.com/cookiecutter/cookiecutter-django/pull/4709)) + +- Bump traefik from 2.10.6 to 2.10.7 ([#4722](https://github.com/cookiecutter/cookiecutter-django/pull/4722)) + +## 2023.12.06 + + +### Updated + +- Bump python from 3.11.6 to 3.11.7 ([#4719](https://github.com/cookiecutter/cookiecutter-django/pull/4719)) + +- Update mypy to 1.7.1, django-stubs to 4.2.7 and djangorestframework-stubs to 3.14.5 ([#4694](https://github.com/cookiecutter/cookiecutter-django/pull/4694)) + +## 2023.12.04 + + +### Updated + +- Update django to 4.2.8 ([#4713](https://github.com/cookiecutter/cookiecutter-django/pull/4713)) + +- Bump node from 18 to 20 ([#4283](https://github.com/cookiecutter/cookiecutter-django/pull/4283)) + +- Update psycopg to 3.1.14 ([#4711](https://github.com/cookiecutter/cookiecutter-django/pull/4711)) + +## 2023.12.02 + + +### Updated + +- Update mailpit to latest ([#4710](https://github.com/cookiecutter/cookiecutter-django/pull/4710)) + +## 2023.11.30 + + +### Fixed + +- Removed tmp mount in devcontainer.json. Fix #4686 ([#4708](https://github.com/cookiecutter/cookiecutter-django/pull/4708)) + +### Updated + +- Bump traefik from 2.10.5 to 2.10.6 ([#4706](https://github.com/cookiecutter/cookiecutter-django/pull/4706)) + +## 2023.11.29 + + +### Updated + +- Update sentry-sdk to 1.38.0 ([#4705](https://github.com/cookiecutter/cookiecutter-django/pull/4705)) + +## 2023.11.28 + + +### Fixed + +- Excludes devcontainer.json from the pre-commit ([#4702](https://github.com/cookiecutter/cookiecutter-django/pull/4702)) + +### Updated + +- Update sphinx-rtd-theme to 2.0.0 ([#4700](https://github.com/cookiecutter/cookiecutter-django/pull/4700)) + +## 2023.11.24 + + +### Updated + +- Update sentry-sdk to 1.37.1 ([#4696](https://github.com/cookiecutter/cookiecutter-django/pull/4696)) + +- Update sentry-sdk to 1.37.0 ([#4695](https://github.com/cookiecutter/cookiecutter-django/pull/4695)) + +## 2023.11.22 + + +### Updated + +- Update celery to 5.3.6 ([#4693](https://github.com/cookiecutter/cookiecutter-django/pull/4693)) + +## 2023.11.21 + + +### Updated + +- Update sentry-sdk to 1.36.0 ([#4687](https://github.com/cookiecutter/cookiecutter-django/pull/4687)) + +## 2023.11.20 + + +### Fixed + +- Fix bug with social account adapter name override, in very specific conditions ([#4650](https://github.com/cookiecutter/cookiecutter-django/pull/4650)) + +### Updated + +- Update django-cors-headers to 4.3.1 ([#4684](https://github.com/cookiecutter/cookiecutter-django/pull/4684)) + +- Update psycopg to 3.1.13 ([#4685](https://github.com/cookiecutter/cookiecutter-django/pull/4685)) + +## 2023.11.14 + + +### Updated + +- Update sentry-sdk to 1.35.0 ([#4681](https://github.com/cookiecutter/cookiecutter-django/pull/4681)) + +- Auto-update pre-commit hooks ([#4683](https://github.com/cookiecutter/cookiecutter-django/pull/4683)) + +## 2023.11.11 + + +### Updated + +- Update celery to 5.3.5 ([#4678](https://github.com/cookiecutter/cookiecutter-django/pull/4678)) + +## 2023.11.09 + + +### Updated + +- Auto-update pre-commit hooks ([#4673](https://github.com/cookiecutter/cookiecutter-django/pull/4673)) + +- Update black to 23.11.0 ([#4674](https://github.com/cookiecutter/cookiecutter-django/pull/4674)) + +## 2023.11.08 + + +### Updated + +- Update pytest-django to 4.7.0 ([#4672](https://github.com/cookiecutter/cookiecutter-django/pull/4672)) + +## 2023.11.06 + + +### Changed + +- Add `rmbackup` script to remove backups from `postgres/backups`. Fixes: #4663 ([#4664](https://github.com/cookiecutter/cookiecutter-django/pull/4664)) + +### Updated + +- Update django-allauth to 0.58.2 ([#4667](https://github.com/cookiecutter/cookiecutter-django/pull/4667)) + +- Update uvicorn to 0.24.0.post1 ([#4666](https://github.com/cookiecutter/cookiecutter-django/pull/4666)) + +## 2023.11.04 + + +### Updated + +- Update uvicorn to 0.24.0 ([#4665](https://github.com/cookiecutter/cookiecutter-django/pull/4665)) + +## 2023.11.03 + + +### Updated + +- Update flake8-isort to 6.1.1 ([#4662](https://github.com/cookiecutter/cookiecutter-django/pull/4662)) + +## 2023.11.02 + + +### Updated + +- Update sentry-sdk to 1.34.0 ([#4660](https://github.com/cookiecutter/cookiecutter-django/pull/4660)) + +## 2023.11.01 + + +### Updated + +- Update django to 4.2.7 ([#4658](https://github.com/cookiecutter/cookiecutter-django/pull/4658)) + +- Update django-stubs to 4.2.6 ([#4657](https://github.com/cookiecutter/cookiecutter-django/pull/4657)) + +## 2023.10.31 + + +### Updated + +- Update pytest-django to 4.6.0 ([#4656](https://github.com/cookiecutter/cookiecutter-django/pull/4656)) + +- Update pytest to 7.4.3 ([#4654](https://github.com/cookiecutter/cookiecutter-django/pull/4654)) + +- Update werkzeug to 3.0.1 ([#4655](https://github.com/cookiecutter/cookiecutter-django/pull/4655)) + +- Update sentry-sdk to 1.33.1 ([#4653](https://github.com/cookiecutter/cookiecutter-django/pull/4653)) + +- Update sentry-sdk to 1.33.0 ([#4652](https://github.com/cookiecutter/cookiecutter-django/pull/4652)) + +- Update crispy-bootstrap5 to 2023.10 ([#4651](https://github.com/cookiecutter/cookiecutter-django/pull/4651)) + +## 2023.10.26 + + +### Updated + +- Update django-anymail to 10.2 ([#4645](https://github.com/cookiecutter/cookiecutter-django/pull/4645)) + +## 2023.10.24 + + +### Updated + +- Update black to 23.10.1 ([#4639](https://github.com/cookiecutter/cookiecutter-django/pull/4639)) + +- Auto-update pre-commit hooks ([#4641](https://github.com/cookiecutter/cookiecutter-django/pull/4641)) + +## 2023.10.23 + + +### Updated + +- Update pylint-django to 2.5.5 ([#4638](https://github.com/cookiecutter/cookiecutter-django/pull/4638)) + +## 2023.10.19 + + +### Updated + +- Update mypy to 1.6.1 ([#4634](https://github.com/cookiecutter/cookiecutter-django/pull/4634)) + +- Update djangorestframework-stubs to 3.14.4 ([#4637](https://github.com/cookiecutter/cookiecutter-django/pull/4637)) + +- Update django-stubs to 4.2.5 ([#4636](https://github.com/cookiecutter/cookiecutter-django/pull/4636)) + +## 2023.10.17 + + +### Updated + +- Auto-update pre-commit hooks ([#4633](https://github.com/cookiecutter/cookiecutter-django/pull/4633)) + +- Update black to 23.10.0 ([#4632](https://github.com/cookiecutter/cookiecutter-django/pull/4632)) + +- Update pillow to 10.1.0 ([#4630](https://github.com/cookiecutter/cookiecutter-django/pull/4630)) + +- Update django-crispy-forms to 2.1 ([#4629](https://github.com/cookiecutter/cookiecutter-django/pull/4629)) + +## 2023.10.13 + + +### Updated + +- Update pre-commit to 3.5.0 ([#4628](https://github.com/cookiecutter/cookiecutter-django/pull/4628)) + +- Update watchfiles to 0.21.0 ([#4627](https://github.com/cookiecutter/cookiecutter-django/pull/4627)) + +## 2023.10.12 + + +### Updated + +- Update django-cors-headers to 4.3.0 ([#4625](https://github.com/cookiecutter/cookiecutter-django/pull/4625)) + +- Update whitenoise to 6.6.0 ([#4624](https://github.com/cookiecutter/cookiecutter-django/pull/4624)) + +- Update sentry-sdk to 1.32.0 ([#4623](https://github.com/cookiecutter/cookiecutter-django/pull/4623)) + +- Bump traefik from 2.10.4 to 2.10.5 ([#4626](https://github.com/cookiecutter/cookiecutter-django/pull/4626)) + +## 2023.10.09 + + +### Updated + +- Bump stefanzweifel/git-auto-commit-action from 4.16.0 to 5.0.0 ([#4621](https://github.com/cookiecutter/cookiecutter-django/pull/4621)) + +- Update django-storages to 1.14.2 ([#4620](https://github.com/cookiecutter/cookiecutter-django/pull/4620)) + +## 2023.10.08 + + +### Updated + +- Auto-update pre-commit hooks ([#4619](https://github.com/cookiecutter/cookiecutter-django/pull/4619)) + +## 2023.10.05 + + +### Updated + +- Update djangorestframework-stubs to 3.14.3 ([#4618](https://github.com/cookiecutter/cookiecutter-django/pull/4618)) + +- Update django-stubs to 4.2.4 ([#4566](https://github.com/cookiecutter/cookiecutter-django/pull/4566)) + +- Update mypy to 1.5.1 ([#4568](https://github.com/cookiecutter/cookiecutter-django/pull/4568)) + +## 2023.10.04 + + +### Updated + +- Update django to 4.2.6 ([#4617](https://github.com/cookiecutter/cookiecutter-django/pull/4617)) + +- Update coverage to 7.3.2 ([#4616](https://github.com/cookiecutter/cookiecutter-django/pull/4616)) + +- Update werkzeug to 3.0.0 ([#4608](https://github.com/cookiecutter/cookiecutter-django/pull/4608)) + +- Update django-redis to 5.4.0 ([#4609](https://github.com/cookiecutter/cookiecutter-django/pull/4609)) + +- Bump docs Python docker image from 3.11.5 to 3.11.6 ([#4615](https://github.com/cookiecutter/cookiecutter-django/pull/4615)) + +## 2023.10.03 + + +### Changed + +- [pre-commit.ci] pre-commit autoupdate ([#4613](https://github.com/cookiecutter/cookiecutter-django/pull/4613)) + +### Updated + +- Bump prod Python docker image from 3.11.5 to 3.11.6 ([#4611](https://github.com/cookiecutter/cookiecutter-django/pull/4611)) + +- Bump local Python docker image from 3.11.5 to 3.11.6 ([#4612](https://github.com/cookiecutter/cookiecutter-django/pull/4612)) + +- Auto-update pre-commit hooks ([#4610](https://github.com/cookiecutter/cookiecutter-django/pull/4610)) + +## 2023.09.29 + + +### Updated + +- Update django-storages to 1.14.1 ([#4604](https://github.com/cookiecutter/cookiecutter-django/pull/4604)) + +## 2023.09.28 + + +### Updated + +- Update psycopg to 3.1.12 ([#4601](https://github.com/cookiecutter/cookiecutter-django/pull/4601)) + +## 2023.09.27 + + +### Fixed + +- Fix ownership for /start-flower script in production Dockerfile ([#4603](https://github.com/cookiecutter/cookiecutter-django/pull/4603)) + +## 2023.09.26 + + +### Updated + +- Update redis to 5.0.1 ([#4600](https://github.com/cookiecutter/cookiecutter-django/pull/4600)) + +## 2023.09.25 + + +### Updated + +- Update django-upgrade to 1.15.0 ([#4598](https://github.com/cookiecutter/cookiecutter-django/pull/4598)) + +- Update django-allauth to 0.57.0 ([#4597](https://github.com/cookiecutter/cookiecutter-django/pull/4597)) + +- Auto-update pre-commit hooks ([#4596](https://github.com/cookiecutter/cookiecutter-django/pull/4596)) + +## 2023.09.23 + + +### Updated + +- Update psycopg to 3.1.11 ([#4595](https://github.com/cookiecutter/cookiecutter-django/pull/4595)) + +- Auto-update pre-commit hooks ([#4591](https://github.com/cookiecutter/cookiecutter-django/pull/4591)) + +- Update drf-spectacular to 0.26.5 ([#4594](https://github.com/cookiecutter/cookiecutter-django/pull/4594)) + +## 2023.09.21 + + +### Updated + +- Auto-update pre-commit hooks ([#4589](https://github.com/cookiecutter/cookiecutter-django/pull/4589)) + +- Update djlint to 1.34.0 ([#4590](https://github.com/cookiecutter/cookiecutter-django/pull/4590)) + +## 2023.09.19 + + +### Updated + +- Auto-update pre-commit hooks ([#4588](https://github.com/cookiecutter/cookiecutter-django/pull/4588)) + +- Update djlint to 1.33.0 ([#4587](https://github.com/cookiecutter/cookiecutter-django/pull/4587)) + +## 2023.09.16 + + +### Updated + +- Auto-update pre-commit hooks ([#4586](https://github.com/cookiecutter/cookiecutter-django/pull/4586)) + +## 2023.09.15 + + +### Updated + +- Update flake8-isort to 6.1.0 ([#4585](https://github.com/cookiecutter/cookiecutter-django/pull/4585)) + +- Update pillow to 10.0.1 ([#4584](https://github.com/cookiecutter/cookiecutter-django/pull/4584)) + +## 2023.09.14 + + +### Updated + +- Update sphinx to 7.2.6 ([#4583](https://github.com/cookiecutter/cookiecutter-django/pull/4583)) + +## 2023.09.13 + + +### Updated + +- Update sentry-sdk to 1.31.0 ([#4582](https://github.com/cookiecutter/cookiecutter-django/pull/4582)) + +## 2023.09.12 + + +### Updated + +- Update django-storages to 1.14 ([#4564](https://github.com/cookiecutter/cookiecutter-django/pull/4564)) + +## 2023.09.11 + + +### Updated + +- Auto-update pre-commit hooks ([#4579](https://github.com/cookiecutter/cookiecutter-django/pull/4579)) + +- Update black to 23.9.1 ([#4580](https://github.com/cookiecutter/cookiecutter-django/pull/4580)) + +- Update django-allauth to 0.56.1 ([#4576](https://github.com/cookiecutter/cookiecutter-django/pull/4576)) + +## 2023.09.08 + + +### Updated + +- Update pytest to 7.4.2 ([#4573](https://github.com/cookiecutter/cookiecutter-django/pull/4573)) + +## 2023.09.07 + + +### Updated + +- Update django-allauth to 0.56.0 ([#4571](https://github.com/cookiecutter/cookiecutter-django/pull/4571)) + +## 2023.09.06 + + +### Changed + +- Replace Mailhog with Mailpit ([#4551](https://github.com/cookiecutter/cookiecutter-django/pull/4551)) + +### Updated + +- Update sphinx to 7.2.5 ([#4569](https://github.com/cookiecutter/cookiecutter-django/pull/4569)) + +- Bump actions/checkout from 3 to 4 ([#4565](https://github.com/cookiecutter/cookiecutter-django/pull/4565)) + +- Update coverage to 7.3.1 ([#4567](https://github.com/cookiecutter/cookiecutter-django/pull/4567)) + +## 2023.09.04 + + +### Updated + +- Update django to 4.2.5 ([#4563](https://github.com/cookiecutter/cookiecutter-django/pull/4563)) + +## 2023.09.03 + + +### Updated + +- Update celery to 5.3.4 ([#4562](https://github.com/cookiecutter/cookiecutter-django/pull/4562)) + +## 2023.09.02 + + +### Updated + +- Update pytest to 7.4.1 ([#4561](https://github.com/cookiecutter/cookiecutter-django/pull/4561)) + +- Update pre-commit to 3.4.0 ([#4560](https://github.com/cookiecutter/cookiecutter-django/pull/4560)) + +- Update django-environ to 0.11.2 ([#4558](https://github.com/cookiecutter/cookiecutter-django/pull/4558)) + +## 2023.09.01 + + +## 2023.08.31 + + +### Updated + +- Auto-update pre-commit hooks ([#4550](https://github.com/cookiecutter/cookiecutter-django/pull/4550)) + +- Update django-allauth to 0.55.2 ([#4549](https://github.com/cookiecutter/cookiecutter-django/pull/4549)) + +- Update celery to 5.3.3 ([#4553](https://github.com/cookiecutter/cookiecutter-django/pull/4553)) + +## 2023.08.30 + + +### Updated + +- Update django-environ to 0.11.0 ([#4548](https://github.com/cookiecutter/cookiecutter-django/pull/4548)) + +## 2023.08.29 + + +### Updated + +- Update sentry-sdk to 1.30.0 ([#4546](https://github.com/cookiecutter/cookiecutter-django/pull/4546)) + +## 2023.08.28 + + +### Changed + +- Add French translations ([#4454](https://github.com/cookiecutter/cookiecutter-django/pull/4454)) + +- Change `MEDIA_URL` to an absolute URL in tests ([#4460](https://github.com/cookiecutter/cookiecutter-django/pull/4460)) + +### Fixed + +- Fix a small compatibility issue between black and flake8 ([#4541](https://github.com/cookiecutter/cookiecutter-django/pull/4541)) + +### Updated + +- Update django-allauth to 0.55.0 ([#4535](https://github.com/cookiecutter/cookiecutter-django/pull/4535)) + +- Update watchfiles to 0.20.0 ([#4537](https://github.com/cookiecutter/cookiecutter-django/pull/4537)) + +- Update Python version from 3.11.4 to 3.11.5 ([#4542](https://github.com/cookiecutter/cookiecutter-django/pull/4542)) + +## 2023.08.19 + + +### Changed + +- Override `_after_postgeneration` to force save in `UserFactory` ([#4534](https://github.com/cookiecutter/cookiecutter-django/pull/4534)) + +## 2023.08.17 + + +### Updated + +- Update argon2-cffi to 23.1.0 ([#4527](https://github.com/cookiecutter/cookiecutter-django/pull/4527)) + +- Auto-update pre-commit hooks ([#4530](https://github.com/cookiecutter/cookiecutter-django/pull/4530)) + +## 2023.08.16 + + +### Updated + +- Update django-upgrade to 1.14.1 ([#4528](https://github.com/cookiecutter/cookiecutter-django/pull/4528)) + +## 2023.08.15 + + +### Updated + +- Update redis to 5.0.0 ([#4526](https://github.com/cookiecutter/cookiecutter-django/pull/4526)) + +## 2023.08.14 + + +### Changed + +- Install Django and DRF stubs with `compatible-mypy` extra (as per offical recommendation) ([#4361](https://github.com/cookiecutter/cookiecutter-django/pull/4361)) + +- Fix `overrideCommand` value in `devcontainer` so that the `django` container can run (#4517) ([#4517](https://github.com/cookiecutter/cookiecutter-django/pull/4517)) + +### Fixed + +- Prevent error in data migration caused by long project name ([#4525](https://github.com/cookiecutter/cookiecutter-django/pull/4525)) + +- Remove unused gulp-concat when Webpack is selected ([#4520](https://github.com/cookiecutter/cookiecutter-django/pull/4520)) + +- Exclude env files from container image (add .envs/ to .dockerignore) ([#4476](https://github.com/cookiecutter/cookiecutter-django/pull/4476)) + +### Updated + +- Update werkzeug to 2.3.7 ([#4521](https://github.com/cookiecutter/cookiecutter-django/pull/4521)) + +- Update coverage to 7.3.0 ([#4516](https://github.com/cookiecutter/cookiecutter-django/pull/4516)) + +- Update django-debug-toolbar to 4.2.0 ([#4511](https://github.com/cookiecutter/cookiecutter-django/pull/4511)) + +- Update flower to 2.0.1 ([#4518](https://github.com/cookiecutter/cookiecutter-django/pull/4518)) + +## 2023.08.10 + + +### Fixed + +- Corrected 'or' translation to pt-br ([#4507](https://github.com/cookiecutter/cookiecutter-django/pull/4507)) + +## 2023.08.04 + + +### Updated + +- Auto-update pre-commit hooks ([#4503](https://github.com/cookiecutter/cookiecutter-django/pull/4503)) + +## 2023.08.01 + + +### Updated + +- Auto-update pre-commit hooks ([#4499](https://github.com/cookiecutter/cookiecutter-django/pull/4499)) + +- Update django-anymail to 10.1 ([#4497](https://github.com/cookiecutter/cookiecutter-django/pull/4497)) + +- Update sentry-sdk to 1.29.2 ([#4496](https://github.com/cookiecutter/cookiecutter-django/pull/4496)) + +- Update django to 4.2.4 ([#4495](https://github.com/cookiecutter/cookiecutter-django/pull/4495)) + +- Update flake8 to 6.1.0 ([#4489](https://github.com/cookiecutter/cookiecutter-django/pull/4489)) + +- Update uvicorn to 0.23.2 ([#4490](https://github.com/cookiecutter/cookiecutter-django/pull/4490)) + +- Update sentry-sdk to 1.29.1 ([#4494](https://github.com/cookiecutter/cookiecutter-django/pull/4494)) + +## 2023.07.30 + + +### Fixed + +- Fix `README.md` file extension in `setup.py` ([#4488](https://github.com/cookiecutter/cookiecutter-django/pull/4488)) + +## 2023.07.28 + + +### Changed + +- Add support for Drone CI ([#4382](https://github.com/cookiecutter/cookiecutter-django/pull/4382)) + +## 2023.07.27 + + +### Documentation + +- Document that `docker exec` does not work for running management commands ([#4487](https://github.com/cookiecutter/cookiecutter-django/pull/4487)) + +- Add Webpack instructions for developping locally with HTTPS ([#4486](https://github.com/cookiecutter/cookiecutter-django/pull/4486)) + +## 2023.07.25 + + +### Updated + +- Upgrade to traefik 2.10.4 ([#4483](https://github.com/cookiecutter/cookiecutter-django/pull/4483)) + +## 2023.07.24 + + +### Fixed + +- Add missing custom CRSF error page in prod ([#4464](https://github.com/cookiecutter/cookiecutter-django/pull/4464)) + +### Documentation + +- Replace `docker-compose` by `docker compose` in docs ([#4463](https://github.com/cookiecutter/cookiecutter-django/pull/4463)) + +### Updated + +- Update drf-spectacular to 0.26.4 ([#4481](https://github.com/cookiecutter/cookiecutter-django/pull/4481)) + +## 2023.07.20 + + +### Updated + +- Update djlint to 1.32.1 ([#4475](https://github.com/cookiecutter/cookiecutter-django/pull/4475)) + +## 2023.07.19 + + +### Updated + +- Update factory-boy to 3.3.0 ([#4472](https://github.com/cookiecutter/cookiecutter-django/pull/4472)) + +- Update gunicorn to 21.2.0 ([#4473](https://github.com/cookiecutter/cookiecutter-django/pull/4473)) + +- Update djlint to 1.32.0 ([#4471](https://github.com/cookiecutter/cookiecutter-django/pull/4471)) + +## 2023.07.18 + + +### Updated + +- Update gunicorn to 21.1.0 ([#4470](https://github.com/cookiecutter/cookiecutter-django/pull/4470)) + +- Update uvicorn to 0.23.1 ([#4468](https://github.com/cookiecutter/cookiecutter-django/pull/4468)) + +- Update gunicorn to 21.0.1 ([#4466](https://github.com/cookiecutter/cookiecutter-django/pull/4466)) + +## 2023.07.13 + + +### Updated + +- Update sentry-sdk to 1.28.1 ([#4458](https://github.com/cookiecutter/cookiecutter-django/pull/4458)) + +## 2023.07.11 + + +### Changed + +- Improve type hints for `UserSerializer` ([#4429](https://github.com/cookiecutter/cookiecutter-django/pull/4429)) + +- [pre-commit.ci] pre-commit autoupdate ([#4453](https://github.com/cookiecutter/cookiecutter-django/pull/4453)) + +### Fixed + +- Fix `/tmp` bind mount in devcontainer config ([#4455](https://github.com/cookiecutter/cookiecutter-django/pull/4455)) + +### Updated + +- Update black to 23.7.0 ([#4452](https://github.com/cookiecutter/cookiecutter-django/pull/4452)) + +## 2023.07.10 + + +### Fixed + +- Prevent user's name being shown twice on user details page if username is set to email ([#4436](https://github.com/cookiecutter/cookiecutter-django/pull/4436)) + +- Add missing trailing space in `EMAIL_SUBJECT_PREFIX` setting ([#4434](https://github.com/cookiecutter/cookiecutter-django/pull/4434)) + +### Documentation + +- Clarify documentation on which port to use to access the application when using Webpack or Gulp ([#4413](https://github.com/cookiecutter/cookiecutter-django/pull/4413)) + +### Updated + +- Update django-coverage-plugin to 3.1.0 ([#4446](https://github.com/cookiecutter/cookiecutter-django/pull/4446)) + +- Update pillow to 10.0.0 ([#4432](https://github.com/cookiecutter/cookiecutter-django/pull/4432)) + +- Update django-cors-headers to 4.2.0 ([#4445](https://github.com/cookiecutter/cookiecutter-django/pull/4445)) + +- Update sentry-sdk to 1.28.0 ([#4444](https://github.com/cookiecutter/cookiecutter-django/pull/4444)) + +## 2023.07.09 + + +### Fixed + +- Fix missing run configurations when PyCharm is selected ([#4441](https://github.com/cookiecutter/cookiecutter-django/pull/4441)) + +## 2023.07.08 + + +### Updated + +- Update sentry-sdk to 1.27.1 ([#4440](https://github.com/cookiecutter/cookiecutter-django/pull/4440)) + +## 2023.07.04 + + +### Changed + +- Add PostgreSQL 15 ([#4431](https://github.com/cookiecutter/cookiecutter-django/pull/4431)) + +- [pre-commit.ci] pre-commit autoupdate ([#4438](https://github.com/cookiecutter/cookiecutter-django/pull/4438)) + +### Updated + +- Update sentry-sdk to 1.27.0 ([#4439](https://github.com/cookiecutter/cookiecutter-django/pull/4439)) + +- Update postcss-preset-env to 9.0.0 ([#4437](https://github.com/cookiecutter/cookiecutter-django/pull/4437)) + +## 2023.07.03 + + +### Changed + +- Add a devcontainer configuration with Docker ([#4198](https://github.com/cookiecutter/cookiecutter-django/pull/4198)) + +### Updated + +- Update django-stubs to 4.2.3 ([#4430](https://github.com/cookiecutter/cookiecutter-django/pull/4430)) + +- Update django to 4.2.3 ([#4435](https://github.com/cookiecutter/cookiecutter-django/pull/4435)) + +## 2023.06.30 + + +### Changed + +- Add option to use django-allauth workflow in the admin ([#1921](https://github.com/cookiecutter/cookiecutter-django/pull/1921)) + +## 2023.06.29 + + +### Changed + +- Replace psycopg2 by psycopg3 ([#4421](https://github.com/cookiecutter/cookiecutter-django/pull/4421)) + +## 2023.06.28 + + +### Changed + +- Upgrade to django 4.2 ([#4393](https://github.com/cookiecutter/cookiecutter-django/pull/4393)) + +### Fixed + +- Fix PostgreSQL version in GitHub workflow ([#4423](https://github.com/cookiecutter/cookiecutter-django/pull/4423)) + +### Updated + +- Update werkzeug to 2.3.6 ([#4427](https://github.com/cookiecutter/cookiecutter-django/pull/4427)) + +- Update django-compressor to 4.4 ([#4422](https://github.com/cookiecutter/cookiecutter-django/pull/4422)) + +## 2023.06.27 + + +### Changed + +- Populate User `name` field during social auth ([#3968](https://github.com/cookiecutter/cookiecutter-django/pull/3968)) + +- Add djLint for HTML formatting and linting ([#4389](https://github.com/cookiecutter/cookiecutter-django/pull/4389)) + +### Fixed + +- Only include prettier pre-commit hook with node-based front-end pipeline ([#4418](https://github.com/cookiecutter/cookiecutter-django/pull/4418)) + +### Updated + +- Update djangorestframework-stubs to 3.14.2 ([#4420](https://github.com/cookiecutter/cookiecutter-django/pull/4420)) + +- Update django-stubs to 4.2.2 ([#4419](https://github.com/cookiecutter/cookiecutter-django/pull/4419)) + +## 2023.06.26 + + +### Updated + +- Update pytest to 7.4.0 ([#4412](https://github.com/cookiecutter/cookiecutter-django/pull/4412)) + +- Update redis to 4.6.0 ([#4415](https://github.com/cookiecutter/cookiecutter-django/pull/4415)) + +- Update mypy to 1.4.1 ([#4416](https://github.com/cookiecutter/cookiecutter-django/pull/4416)) + +## 2023.06.22 + + +### Updated + +- Update pygithub to 1.59.0 ([#4410](https://github.com/cookiecutter/cookiecutter-django/pull/4410)) + +- Update drf-spectacular to 0.26.3 ([#4411](https://github.com/cookiecutter/cookiecutter-django/pull/4411)) + +- Update sentry-sdk to 1.26.0 ([#4409](https://github.com/cookiecutter/cookiecutter-django/pull/4409)) + +## 2023.06.21 + + +### Updated + +- Upgrade traefik to 2.10.3 ([#4408](https://github.com/cookiecutter/cookiecutter-django/pull/4408)) + +## 2023.06.19 + + +### Updated + +- Auto-update pre-commit hooks ([#4405](https://github.com/cookiecutter/cookiecutter-django/pull/4405)) + +- Update celery to 5.3.1 ([#4404](https://github.com/cookiecutter/cookiecutter-django/pull/4404)) + +## 2023.06.18 + + +### Changed + +- Fix missing celery env variable when running compilemessages ([#4403](https://github.com/cookiecutter/cookiecutter-django/pull/4403)) + +### Updated + +- Update flower to 2.0.0 ([#4402](https://github.com/cookiecutter/cookiecutter-django/pull/4402)) + +## 2023.06.17 + + +## 2023.06.16 + + +### Updated + +- Update whitenoise to 6.5.0 ([#4400](https://github.com/cookiecutter/cookiecutter-django/pull/4400)) + +- Update django-redis to 5.3.0 ([#4399](https://github.com/cookiecutter/cookiecutter-django/pull/4399)) + +- Auto-update pre-commit hooks ([#4395](https://github.com/cookiecutter/cookiecutter-django/pull/4395)) + +## 2023.06.14 + + +### Updated + +- Update django-cors-headers to 4.1.0 ([#4391](https://github.com/cookiecutter/cookiecutter-django/pull/4391)) + +- Update django-upgrade to 1.14.0 ([#4394](https://github.com/cookiecutter/cookiecutter-django/pull/4394)) + +- Update django-webpack-loader to 2.0.1 ([#4392](https://github.com/cookiecutter/cookiecutter-django/pull/4392)) + +- Update pre-commit to 3.3.3 ([#4390](https://github.com/cookiecutter/cookiecutter-django/pull/4390)) + +## 2023.06.11 + + +### Updated + +- Update pytest to 7.3.2 ([#4384](https://github.com/cookiecutter/cookiecutter-django/pull/4384)) + +- Auto-update pre-commit hooks ([#4385](https://github.com/cookiecutter/cookiecutter-django/pull/4385)) + +## 2023.06.09 + + +### Fixed + +- Fix missing `compilemessages` step before deploying to prod ([#4363](https://github.com/cookiecutter/cookiecutter-django/pull/4363)) + +## 2023.06.08 + + +### Fixed + +- Fix failure in user view test caused by translations ([#4374](https://github.com/cookiecutter/cookiecutter-django/pull/4374)) + +### Updated + +- Update to Python 3.11.4 in production Docker compose ([#4378](https://github.com/cookiecutter/cookiecutter-django/pull/4378)) + +- Update to Python 3.11.4 in docs Docker compose ([#4379](https://github.com/cookiecutter/cookiecutter-django/pull/4379)) + +- Update to Python 3.11.4 in local Docker compose ([#4380](https://github.com/cookiecutter/cookiecutter-django/pull/4380)) + +- Update celery to 5.3.0 ([#4369](https://github.com/cookiecutter/cookiecutter-django/pull/4369)) + +- Update werkzeug to 2.3.5 ([#4377](https://github.com/cookiecutter/cookiecutter-django/pull/4377)) + +## 2023.06.07 + + +### Changed + +- Replace `runserver` with `runserver_plus` ([#4373](https://github.com/cookiecutter/cookiecutter-django/pull/4373)) + +- Add translations for Brazilian Portuguese ([#4367](https://github.com/cookiecutter/cookiecutter-django/pull/4367)) + +### Updated + +- Update sentry-sdk to 1.25.1 ([#4376](https://github.com/cookiecutter/cookiecutter-django/pull/4376)) + +- Update django-extensions to 3.2.3 ([#4372](https://github.com/cookiecutter/cookiecutter-django/pull/4372)) + +- Update djangorestframework-stubs to 3.14.1 ([#4366](https://github.com/cookiecutter/cookiecutter-django/pull/4366)) + +- Update django-stubs to 4.2.1 ([#4365](https://github.com/cookiecutter/cookiecutter-django/pull/4365)) + +- Update mypy to 1.3.0 ([#4327](https://github.com/cookiecutter/cookiecutter-django/pull/4327)) + +## 2023.06.02 + + +### Updated + +- Update sentry-sdk to 1.25.0 ([#4364](https://github.com/cookiecutter/cookiecutter-django/pull/4364)) + +## 2023.05.30 + + +### Updated + +- Update hiredis to 2.2.3 ([#4360](https://github.com/cookiecutter/cookiecutter-django/pull/4360)) + +- Update django-debug-toolbar to 4.1.0 ([#4359](https://github.com/cookiecutter/cookiecutter-django/pull/4359)) + +- Update redis to 4.5.5 ([#4358](https://github.com/cookiecutter/cookiecutter-django/pull/4358)) + +- Update django-anymail to 10.0 ([#4357](https://github.com/cookiecutter/cookiecutter-django/pull/4357)) + +- Update coverage to 7.2.7 ([#4356](https://github.com/cookiecutter/cookiecutter-django/pull/4356)) + +## 2023.05.28 + + +## 2023.05.24 + + +### Fixed + +- Prevent Celery restarts on media file changes ([#4352](https://github.com/cookiecutter/cookiecutter-django/pull/4352)) + +### Updated + +- Update coverage to 7.2.6 ([#4351](https://github.com/cookiecutter/cookiecutter-django/pull/4351)) + +## 2023.05.23 + + +### Changed + +- Fix compatibility webpack-bundle-tracker>=2.0.0 js library required after upgrade django-webpack-loader>=2.0.0 ([#4350](https://github.com/cookiecutter/cookiecutter-django/pull/4350)) + +### Updated + +- Update sphinx-rtd-theme to 1.2.1 ([#4348](https://github.com/cookiecutter/cookiecutter-django/pull/4348)) + +- Update sentry-sdk to 1.24.0 ([#4349](https://github.com/cookiecutter/cookiecutter-django/pull/4349)) + +- Bump webpack-bundle-tracker from 1.8.1 to 2.0.0 in /{{cookiecutter.project_slug}} ([#4347](https://github.com/cookiecutter/cookiecutter-django/pull/4347)) + +- Update django-webpack-loader to 2.0.0 ([#4345](https://github.com/cookiecutter/cookiecutter-django/pull/4345)) + +- Update pytest-xdist to 3.3.1 ([#4344](https://github.com/cookiecutter/cookiecutter-django/pull/4344)) + +- Update requests to 2.31.0 ([#4346](https://github.com/cookiecutter/cookiecutter-django/pull/4346)) + +## 2023.05.18 + + +### Updated + +- Update pre-commit to 3.3.2 ([#4342](https://github.com/cookiecutter/cookiecutter-django/pull/4342)) + +## 2023.05.17 + + +### Updated + +- Update sentry-sdk to 1.23.1 ([#4341](https://github.com/cookiecutter/cookiecutter-django/pull/4341)) + +## 2023.05.15 + + +### Updated + +- Update django-cors-headers to 4.0.0 ([#4329](https://github.com/cookiecutter/cookiecutter-django/pull/4329)) + +- Update sentry-sdk to 1.23.0 ([#4337](https://github.com/cookiecutter/cookiecutter-django/pull/4337)) + +## 2023.05.09 + + +### Updated + +- Update werkzeug to 2.3.4 ([#4325](https://github.com/cookiecutter/cookiecutter-django/pull/4325)) + +## 2023.05.08 + + +### Updated + +- Auto-update pre-commit hooks ([#4320](https://github.com/cookiecutter/cookiecutter-django/pull/4320)) + +- Update sentry-sdk to 1.22.2 ([#4321](https://github.com/cookiecutter/cookiecutter-django/pull/4321)) + +## 2023.05.04 + + +### Changed + +- Remove pytz from dependencies ([#4309](https://github.com/cookiecutter/cookiecutter-django/pull/4309)) + +### Updated + +- Update django-anymail to 9.2 ([#4316](https://github.com/cookiecutter/cookiecutter-django/pull/4316)) + +- Update pre-commit to 3.3.1 ([#4315](https://github.com/cookiecutter/cookiecutter-django/pull/4315)) + +- Update coverage to 7.2.5 ([#4314](https://github.com/cookiecutter/cookiecutter-django/pull/4314)) + +- Update django to 4.1.9 ([#4313](https://github.com/cookiecutter/cookiecutter-django/pull/4313)) + +- Update sentry-sdk to 1.21.1 ([#4312](https://github.com/cookiecutter/cookiecutter-django/pull/4312)) + +- Update requests to 2.30.0 ([#4311](https://github.com/cookiecutter/cookiecutter-django/pull/4311)) + +## 2023.05.02 + + +### Updated + +- Upgrade traefik to 2.10.1 ([#4304](https://github.com/cookiecutter/cookiecutter-django/pull/4304)) + +- Update uvicorn to 0.22.0 ([#4305](https://github.com/cookiecutter/cookiecutter-django/pull/4305)) + +- Update werkzeug to 2.3.3 ([#4307](https://github.com/cookiecutter/cookiecutter-django/pull/4307)) + +## 2023.04.28 + + +### Changed + +- Add django-upgrade to pre-commit hooks ([#4298](https://github.com/cookiecutter/cookiecutter-django/pull/4298)) + +## 2023.04.27 + + +### Updated + +- Update djangorestframework-stubs to 3.14.0 ([#4303](https://github.com/cookiecutter/cookiecutter-django/pull/4303)) + +- Update werkzeug to 2.3.1 ([#4302](https://github.com/cookiecutter/cookiecutter-django/pull/4302)) + +- Update django-stubs to 4.2.0 ([#4301](https://github.com/cookiecutter/cookiecutter-django/pull/4301)) + +## 2023.04.26 + + +### Updated + +- Upgrade cssnano to v6.0.0 ([#4233](https://github.com/cookiecutter/cookiecutter-django/pull/4233)) + +- Upgrade concurrently to 8.0.1 ([#4237](https://github.com/cookiecutter/cookiecutter-django/pull/4237)) + +- Upgrade to node v18 ([#4294](https://github.com/cookiecutter/cookiecutter-django/pull/4294)) + +- Update coverage to 7.2.3 ([#4297](https://github.com/cookiecutter/cookiecutter-django/pull/4297)) + +- Update mypy to 1.2.0 ([#4295](https://github.com/cookiecutter/cookiecutter-django/pull/4295)) + +- Update werkzeug to 2.3.0 ([#4296](https://github.com/cookiecutter/cookiecutter-django/pull/4296)) + +## 2023.04.25 + + +### Updated + +- Update sentry-sdk to 1.21.0 ([#4293](https://github.com/cookiecutter/cookiecutter-django/pull/4293)) + +- Update sphinx to 6.2.1 ([#4292](https://github.com/cookiecutter/cookiecutter-django/pull/4292)) + +- Bump traefik from 2.9.10 to 2.10.0 ([#4290](https://github.com/cookiecutter/cookiecutter-django/pull/4290)) + +- Auto-update pre-commit hooks ([#4288](https://github.com/cookiecutter/cookiecutter-django/pull/4288)) + +## 2023.04.24 + + +### Updated + +- Auto-update pre-commit hooks ([#4286](https://github.com/cookiecutter/cookiecutter-django/pull/4286)) + +- Update sphinx to 6.2.0 ([#4285](https://github.com/cookiecutter/cookiecutter-django/pull/4285)) + +## 2023.04.19 + + +### Updated + +- Update sentry-sdk to 1.20.0 ([#4282](https://github.com/cookiecutter/cookiecutter-django/pull/4282)) + +## 2023.04.18 + + +### Documentation + +- Document how to add 3rd party packages with Docker ([#4279](https://github.com/cookiecutter/cookiecutter-django/pull/4279)) + +## 2023.04.15 + + +### Changed + +- Add username_type option ([#3958](https://github.com/cookiecutter/cookiecutter-django/pull/3958)) + +- Fix inconsistent line length and move configs to pyproject.toml ([#4276](https://github.com/cookiecutter/cookiecutter-django/pull/4276)) + +- Relax rules for linting of pull requests on this template ([#4273](https://github.com/cookiecutter/cookiecutter-django/pull/4273)) + +- Add more pre-commit hooks ([#4266](https://github.com/cookiecutter/cookiecutter-django/pull/4266)) + +- Upgrade Python to version 3.11 (Faster CPython) ([#4256](https://github.com/cookiecutter/cookiecutter-django/pull/4256)) + +### Updated + +- Update drf-spectacular to 0.26.2 ([#4277](https://github.com/cookiecutter/cookiecutter-django/pull/4277)) + +- Update pytest to 7.3.1 ([#4272](https://github.com/cookiecutter/cookiecutter-django/pull/4272)) + +## 2023.04.13 + +### Updated +- Update tox to 4.4.12 ([#4271](https://github.com/cookiecutter/cookiecutter-django/pull/4271)) + +## 2023.04.10 + +### Updated +- Update pytest-sugar to 0.9.7 ([#4269](https://github.com/cookiecutter/cookiecutter-django/pull/4269)) +- Update pytest to 7.3.0 ([#4268](https://github.com/cookiecutter/cookiecutter-django/pull/4268)) + +## 2023.04.07 + +### Updated +- Upgrade traefik to 2.9.10 ([#4267](https://github.com/cookiecutter/cookiecutter-django/pull/4267)) + +## 2023.04.05 + +### Changed +- Update indent for nginx config file ([#4260](https://github.com/cookiecutter/cookiecutter-django/pull/4260)) +### Updated +- Update tox to 4.4.11 ([#4262](https://github.com/cookiecutter/cookiecutter-django/pull/4262)) +- Update django to 4.1.8 ([#4258](https://github.com/cookiecutter/cookiecutter-django/pull/4258)) +- Update pre-commit to 3.2.2 ([#4259](https://github.com/cookiecutter/cookiecutter-django/pull/4259)) + +## 2023.04.04 + +### Changed +- Upgrade to Django 4.1 ([#4028](https://github.com/cookiecutter/cookiecutter-django/pull/4028)) +- Remove deprecated security setting ([#4247](https://github.com/cookiecutter/cookiecutter-django/pull/4247)) +### Fixed +- Replace `runserver_plus` with `runserver` ([#4255](https://github.com/cookiecutter/cookiecutter-django/pull/4255)) +- Fix traefik rule priority for media router ([#4244](https://github.com/cookiecutter/cookiecutter-django/pull/4244)) +### Updated +- Update sentry-sdk to 1.19.0 ([#4254](https://github.com/cookiecutter/cookiecutter-django/pull/4254)) +- Update django-debug-toolbar to 4.0.0 ([#4251](https://github.com/cookiecutter/cookiecutter-django/pull/4251)) + +## 2023.04.03 + +### Changed +- fix: Syntax for ignoring specific noqa errors ([#4250](https://github.com/cookiecutter/cookiecutter-django/pull/4250)) +### Updated +- Update psycopg2-binary to 2.9.6 ([#4249](https://github.com/cookiecutter/cookiecutter-django/pull/4249)) +- Update psycopg2 to 2.9.6 ([#4248](https://github.com/cookiecutter/cookiecutter-django/pull/4248)) + +## 2023.04.01 + +### Updated +- Update pytest-instafail to 0.5.0 ([#4240](https://github.com/cookiecutter/cookiecutter-django/pull/4240)) +- Update pillow to 9.5.0 ([#4242](https://github.com/cookiecutter/cookiecutter-django/pull/4242)) +- Update django-allauth to 0.54.0 ([#4241](https://github.com/cookiecutter/cookiecutter-django/pull/4241)) + +## 2023.03.29 + +### Updated +- Update redis to 4.5.4 ([#4239](https://github.com/cookiecutter/cookiecutter-django/pull/4239)) +- Update pytz to 2023.3 ([#4238](https://github.com/cookiecutter/cookiecutter-django/pull/4238)) +- Update black to 23.3.0 ([#4236](https://github.com/cookiecutter/cookiecutter-django/pull/4236)) + +## 2023.03.27 + +### Updated +- Update watchfiles to 0.19.0 ([#4232](https://github.com/cookiecutter/cookiecutter-django/pull/4232)) + +## 2023.03.26 + +### Updated +- Update pre-commit to 3.2.1 ([#4229](https://github.com/cookiecutter/cookiecutter-django/pull/4229)) + +## 2023.03.25 + +### Updated +- Update pytz to 2023.2 ([#4228](https://github.com/cookiecutter/cookiecutter-django/pull/4228)) + +## 2023.03.23 + +### Updated +- Bump traefik from 2.9.8 to 2.9.9 ([#4225](https://github.com/cookiecutter/cookiecutter-django/pull/4225)) + +## 2023.03.22 + +### Updated +- Update redis to 4.5.3 ([#4227](https://github.com/cookiecutter/cookiecutter-django/pull/4227)) + +## 2023.03.20 + +### Updated +- Update django-allauth to 0.53.1 ([#4223](https://github.com/cookiecutter/cookiecutter-django/pull/4223)) +- Update redis to 4.5.2 ([#4222](https://github.com/cookiecutter/cookiecutter-django/pull/4222)) + +## 2023.03.18 + +### Updated +- Update drf-spectacular to 0.26.1 ([#4221](https://github.com/cookiecutter/cookiecutter-django/pull/4221)) +- Update pygithub to 1.58.1 ([#4220](https://github.com/cookiecutter/cookiecutter-django/pull/4220)) +- Update pre-commit to 3.2.0 ([#4219](https://github.com/cookiecutter/cookiecutter-django/pull/4219)) + +## 2023.03.16 + +### Changed +- Pin base Python Docker images to bugfix ([#4194](https://github.com/cookiecutter/cookiecutter-django/pull/4194)) +### Fixed +- Trim leading and trailing space in `domain_name` and `email` ([#4163](https://github.com/cookiecutter/cookiecutter-django/pull/4163)) +### Updated +- Update djangorestframework-stubs to 1.10.0 ([#4217](https://github.com/cookiecutter/cookiecutter-django/pull/4217)) +- Update django-stubs to 1.16.0 ([#4216](https://github.com/cookiecutter/cookiecutter-django/pull/4216)) +- Update coverage to 7.2.2 ([#4218](https://github.com/cookiecutter/cookiecutter-django/pull/4218)) +- Update sentry-sdk to 1.17.0 ([#4215](https://github.com/cookiecutter/cookiecutter-django/pull/4215)) +- Bump Docker python image from 3.10.9 to 3.10.10 on production Django ([#4214](https://github.com/cookiecutter/cookiecutter-django/pull/4214)) +- Bump Docker python image from 3.10.9-slim-bullseye to 3.10.10-slim-bullseye for docs ([#4213](https://github.com/cookiecutter/cookiecutter-django/pull/4213)) +- Bump Docker python image from 3.10.9-slim-bullseye to 3.10.10-slim-bullseye for local Django service ([#4212](https://github.com/cookiecutter/cookiecutter-django/pull/4212)) +- Update uvicorn to 0.21.1 ([#4211](https://github.com/cookiecutter/cookiecutter-django/pull/4211)) +- Update django-allauth to 0.53.0 ([#4210](https://github.com/cookiecutter/cookiecutter-django/pull/4210)) + +## 2023.03.14 + +### Updated +- Update django-celery-beat to 2.5.0 ([#4208](https://github.com/cookiecutter/cookiecutter-django/pull/4208)) + +## 2023.03.13 + +### Updated +- Update uvicorn to 0.21.0 ([#4203](https://github.com/cookiecutter/cookiecutter-django/pull/4203)) +- Update django-anymail to 9.1 ([#4206](https://github.com/cookiecutter/cookiecutter-django/pull/4206)) +- Update tox to 4.4.7 ([#4207](https://github.com/cookiecutter/cookiecutter-django/pull/4207)) + +## 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 +- Auto-update pre-commit hooks ([#3942](https://github.com/cookiecutter/cookiecutter-django/pull/3942)) + ## 2022.11.07 ### Updated diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 8a5fc4158..a7b21223b 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,3 +1,3 @@ ## Code of Conduct -Everyone who interacts in the Cookiecutter project's codebase, issue trackers, chat rooms, and mailing lists is expected to follow the [PyPA Code of Conduct](https://www.pypa.io/en/latest/code-of-conduct/). +Everyone who interacts in the Cookiecutter project's codebase, issue trackers, chat rooms, and mailing lists is expected to follow the [PSF Code of Conduct](https://www.python.org/psf/conduct/) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 69bce6f2e..8d2025e37 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,41 +2,81 @@ Always happy to get issues identified and pull requests! -## Getting your pull request merged in +## General considerations -1. Keep it small. The smaller the pull request, the more likely we are to accept. -2. Pull requests that fix a current issue get priority for review. +1. Keep it small. The smaller the change, the more likely we are to accept. +2. Changes that fix a current issue get priority for review. +3. Check out [GitHub guide][submit-a-pr] if you've never created a pull request before. + +## Getting started + +1. Fork the repo +2. Clone your fork +3. Create a branch for your changes + +This last step is very important, don't start developing from master, it'll cause pain if you need to send another change later. ## Testing +You'll need to run the tests using Python 3.12. We recommend using [tox](https://tox.readthedocs.io/en/latest/) to run the tests. It will automatically create a fresh virtual environment and install our test dependencies, such as [pytest-cookies](https://pypi.python.org/pypi/pytest-cookies/) and [flake8](https://pypi.python.org/pypi/flake8/). + +We'll also run the tests on GitHub actions when you send your pull request, but it's a good idea to run them locally before you send it. + ### Installation -Please install [tox](https://tox.readthedocs.io/en/latest/), which is a generic virtualenv management and test command line tool. +First, make sure that your version of Python is 3.12: -[tox](https://tox.readthedocs.io/en/latest/) is available for download from [PyPI](https://pypi.python.org/pypi) via [pip](https://pypi.python.org/pypi/pip/): +```bash +$ python --version +Python 3.12.2 +``` - $ pip install tox +Any version that starts with 3.12 will do. If you need to install it, you can get it from [python.org](https://www.python.org/downloads/). -It will automatically create a fresh virtual environment and install our test dependencies, -such as [pytest-cookies](https://pypi.python.org/pypi/pytest-cookies/) and [flake8](https://pypi.python.org/pypi/flake8/). +Then install `tox`, if not already installed: -### Run the Tests +```bash +$ python -m pip install tox +``` -Tox uses pytest under the hood, hence it supports the same syntax for selecting tests. +### Run the template's test suite -For further information please consult the [pytest usage docs](https://pytest.org/latest/usage.html#specifying-tests-selecting-tests). +To run the tests of the template using the current Python version: -To run all tests using various versions of python in virtualenvs defined in tox.ini, just run tox.: +```bash +$ tox -e py +``` - $ tox +This uses `pytest `under the hood, and you can pass options to it after a `--`. So to run a particular test: -It is possible to test with a specific version of python. To do this, the command -is: +```bash +$ tox -e py -- -k test_default_configuration +``` - $ tox -e py310 +For further information, please consult the [pytest usage docs](https://pytest.org/en/latest/how-to/usage.html#specifying-which-tests-to-run). -This will run pytest with the python3.10 interpreter, for example. +### Run the generated project tests -To run a particular test with tox for against your current Python version: +The template tests are checking that the generated project is fully rendered and that it passes `flake8`. We also have some test scripts which generate a specific project combination, install the dependencies, run the tests of the generated project, install FE dependencies and generate the docs. They will install the template dependencies, so make sure you create and activate a virtual environment first. - $ tox -e py -- -k test_default_configuration +```bash +$ python -m venv venv +$ source venv/bin/activate +``` + +These tests are slower and can be run with or without Docker: + +- Without Docker: `tests/test_bare.sh` (for bare metal) +- With Docker: `tests/test_docker.sh` + +All arguments to these scripts will be passed to the `cookiecutter` CLI, letting you set options, for example: + +```bash +$ tests/test_bare.sh use_celery=y +``` + +## Submitting a pull request + +Once you're happy with your changes and they look ok locally, push and send send [a pull request][submit-a-pr] to the main repo, which will trigger the tests on GitHub actions. If they fail, try to fix them. A maintainer should take a look at your change and give you feedback or merge it. + +[submit-a-pr]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 86ee9251c..595e9924a 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -74,10 +74,17 @@ accept and merge pull requests. sfdye + + Jelmer Draaijer + + foarsitter + + + -*Audrey is also the creator of Cookiecutter. Audrey and Daniel are on -the Cookiecutter core team.* +_Audrey is also the creator of Cookiecutter. Audrey and Daniel are on +the Cookiecutter core team._ ## Other Contributors @@ -166,6 +173,13 @@ Listed in alphabetical order. + + Adin Hodovic + + adinhodovic + + + Agam Dua @@ -180,6 +194,13 @@ Listed in alphabetical order. scaramagus + + aiden + + anyidea + + + Alberto Sanchez @@ -285,6 +306,13 @@ Listed in alphabetical order. + + Arkadiusz Michał Ryś + + arrys + + + Arnav Choudhury @@ -355,6 +383,13 @@ Listed in alphabetical order. + + Birtibu + + Birtibu + + + Bo Lopker @@ -481,6 +516,13 @@ Listed in alphabetical order. + + Christian Jauvin + + cjauvin + + + Christopher Clarke @@ -600,6 +642,13 @@ Listed in alphabetical order. + + David + + buckldav + + + David Díaz @@ -607,6 +656,13 @@ Listed in alphabetical order. DavidDiazPinto + + David Păcioianu + + DavidPacioianu + + + Davit Tovmasyan @@ -628,6 +684,13 @@ Listed in alphabetical order. jangeador + + Delphine LEMIRE + + DelphineLemire + + + Demetris Stavrou @@ -691,6 +754,13 @@ Listed in alphabetical order. dudanogueira + + duffn + + duffn + + + Dónal Adams @@ -747,6 +817,20 @@ Listed in alphabetical order. fabaff + + farwill + + farwill + + + + + Fateme Fouladkar + + FatemeFouladkar + + + Felipe Arruda @@ -768,6 +852,13 @@ Listed in alphabetical order. + + Freddy + + Hraesvelg + + + Fuzzwah @@ -810,6 +901,13 @@ Listed in alphabetical order. + + GitBib + + GitBib + + + Glenn Wiskur @@ -831,6 +929,13 @@ Listed in alphabetical order. + + GvS + + GvS666 + + + Hamish Durkin @@ -880,6 +985,13 @@ Listed in alphabetical order. + + henningbra + + henningbra + + + Henrique G. G. Pereira @@ -887,6 +999,20 @@ Listed in alphabetical order. + + hleroy + + hleroy + + + + + Hoai-Thu Vuong + + thuvh + + + Howie Zhao @@ -901,6 +1027,13 @@ Listed in alphabetical order. + + Imran Rahman + + infraredCoding + + + innicoder @@ -922,6 +1055,13 @@ Listed in alphabetical order. + + itisnotyourenv + + itisnotyourenv + + + Ivan Khomutov @@ -929,6 +1069,20 @@ Listed in alphabetical order. + + JAEGYUN JUNG + + TGoddessana + + + + + Jakub Boukal + + SukiCZ + + + Jakub Musko @@ -958,9 +1112,9 @@ Listed in alphabetical order. - Jelmer Draaijer + Jens Kaeske - foarsitter + jkaeske @@ -1020,6 +1174,13 @@ Listed in alphabetical order. + + Joseph Hanna + + sanchimenea + + + jugglinmike @@ -1153,6 +1314,13 @@ Listed in alphabetical order. + + Leifur Halldor Asgeirsson + + leifurhauks + + + Leo won @@ -1237,6 +1405,13 @@ Listed in alphabetical order. marciomazza + + Marios Frixou + + frixou89 + + + Martin Blech @@ -1251,6 +1426,13 @@ Listed in alphabetical order. + + masavini + + masavini + + + Mateusz Ostaszewski @@ -1258,6 +1440,13 @@ Listed in alphabetical order. + + Matheus Jardim Bernardes + + matheusjardimb + + + Mathijs Hoogland @@ -1300,6 +1489,13 @@ Listed in alphabetical order. + + Matthew Foster Walsh + + mfosterw + + + Matthew Sisley @@ -1335,13 +1531,6 @@ Listed in alphabetical order. - - mfosterw - - mfosterw - - - Michael Gecht @@ -1356,6 +1545,13 @@ Listed in alphabetical order. + + Michael V. Battista + + mvbattista + + mvbattista + Mike97M @@ -1370,6 +1566,13 @@ Listed in alphabetical order. + + MinWoo Sung + + SungMinWoo + + + monosans @@ -1377,6 +1580,20 @@ Listed in alphabetical order. + + Morten Kaae + + MortenKaae + + + + + Mounir + + mounirmesselmeni + + + mozillazg @@ -1391,6 +1608,13 @@ Listed in alphabetical order. + + mpsantos + + mpsantos + + + Naveen @@ -1412,6 +1636,13 @@ Listed in alphabetical order. + + Nix Siow + + nixsiow + + nixsiow + Noah H @@ -1426,6 +1657,13 @@ Listed in alphabetical order. + + Omer-5 + + Omer-5 + + + Pablo @@ -1433,6 +1671,13 @@ Listed in alphabetical order. + + Pamela Fox + + pamelafox + + pamelafox + Parbhat Puri @@ -1440,6 +1685,27 @@ Listed in alphabetical order. + + Patrick Tran + + theptrk + + + + + Patrick Zhang + + PatDuJour + + + + + Paul Wulff + + mtmpaulwulff + + + Pawan Chaurasia @@ -1489,6 +1755,20 @@ Listed in alphabetical order. + + Plurific + + paulschwenn + + + + + quroom + + quroom + + + Raony Guimarães Corrêa @@ -1524,6 +1804,13 @@ Listed in alphabetical order. + + rguptar + + rguptar + + + Richard Hajdu @@ -1531,6 +1818,13 @@ Listed in alphabetical order. + + Robin + + Kaffeetasse + + + Roman Afanaskin @@ -1559,6 +1853,13 @@ Listed in alphabetical order. + + Sadra Yahyapour + + lnxpy + + lnxpylnxpy + Sam Collins @@ -1580,6 +1881,20 @@ Listed in alphabetical order. sebastianreyese + + Shayan Karimi + + shywn-mrk + + shywn_mrk + + + Simeon Emanuilov + + s-emanuilov + + s_emanuilov + Simon Rey @@ -1636,6 +1951,13 @@ Listed in alphabetical order. + + TAKAHASHI Shuuji + + shuuji3 + + + Tames McTigue @@ -1657,6 +1979,13 @@ Listed in alphabetical order. + + Tharushan + + Tharushan + + + Thibault J. @@ -1664,6 +1993,13 @@ Listed in alphabetical order. thibault + + Thomas Booij + + ThomasBooij95 + + + Théo Segonds @@ -1671,6 +2007,13 @@ Listed in alphabetical order. + + tildebox + + tildebox + + + Tim Claessens @@ -1692,6 +2035,13 @@ Listed in alphabetical order. + + tmajerech + + tmajerech + + + Tom Atkins @@ -1734,6 +2084,13 @@ Listed in alphabetical order. egregors + + Vageeshan Mankala + + vagi8 + + + vascop @@ -1755,6 +2112,13 @@ Listed in alphabetical order. + + villancikos + + villancikos + + + Vitaly Babiy @@ -1839,6 +2203,13 @@ Listed in alphabetical order. + + zhaoruibing + + zhaoruibing + + + ### Special Thanks @@ -1846,6 +2217,6 @@ Listed in alphabetical order. The following haven't provided code directly, but have provided guidance and advice. -- Jannis Leidel -- Nate Aune -- Barry Morrison \ No newline at end of file +- Jannis Leidel +- Nate Aune +- Barry Morrison \ No newline at end of file diff --git a/README.md b/README.md index 9f2af6d37..c38eec277 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,71 @@ # Cookiecutter Django -[![Build Status](https://img.shields.io/github/workflow/status/cookiecutter/cookiecutter-django/CI/master)](https://github.com/cookiecutter/cookiecutter-django/actions?query=workflow%3ACI) +[![Build Status](https://img.shields.io/github/actions/workflow/status/cookiecutter/cookiecutter-django/ci.yml?branch=master)](https://github.com/cookiecutter/cookiecutter-django/actions/workflows/ci.yml?query=branch%3Amaster) [![Documentation Status](https://readthedocs.org/projects/cookiecutter-django/badge/?version=latest)](https://cookiecutter-django.readthedocs.io/en/latest/?badge=latest) -[![Updates](https://pyup.io/repos/github/cookiecutter/cookiecutter-django/shield.svg)](https://pyup.io/repos/github/cookiecutter/cookiecutter-django/) -[![Join our Discord](https://img.shields.io/badge/Discord-cookiecutter-5865F2?style=flat&logo=discord&logoColor=white)](https://discord.gg/uFXweDQc5a) -[![Code Helpers Badge](https://www.codetriage.com/cookiecutter/cookiecutter-django/badges/users.svg)](https://www.codetriage.com/cookiecutter/cookiecutter-django) +[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/cookiecutter/cookiecutter-django/master.svg)](https://results.pre-commit.ci/latest/github/cookiecutter/cookiecutter-django/master) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) +[![Updates](https://pyup.io/repos/github/cookiecutter/cookiecutter-django/shield.svg)](https://pyup.io/repos/github/cookiecutter/cookiecutter-django/) +[![Join our Discord](https://img.shields.io/badge/Discord-cookiecutter-5865F2?style=flat&logo=discord&logoColor=white)](https://discord.gg/rAWFUP47d2) +[![Code Helpers Badge](https://www.codetriage.com/cookiecutter/cookiecutter-django/badges/users.svg)](https://www.codetriage.com/cookiecutter/cookiecutter-django) + Powered by [Cookiecutter](https://github.com/cookiecutter/cookiecutter), Cookiecutter Django is a framework for jumpstarting production-ready Django projects quickly. -- Documentation: -- See [Troubleshooting](https://cookiecutter-django.readthedocs.io/en/latest/troubleshooting.html) for common errors and obstacles -- If you have problems with Cookiecutter Django, please open [issues](https://github.com/cookiecutter/cookiecutter-django/issues/new) don't send - emails to the maintainers. +- Documentation: +- See [Troubleshooting](https://cookiecutter-django.readthedocs.io/en/latest/troubleshooting.html) for common errors and obstacles +- If you have problems with Cookiecutter Django, please open [issues](https://github.com/cookiecutter/cookiecutter-django/issues/new) don't send + emails to the maintainers. ## Features -- For Django 4.0 -- Works with Python 3.10 -- Renders Django projects with 100% starting test coverage -- Twitter [Bootstrap](https://github.com/twbs/bootstrap) v5 -- [12-Factor](http://12factor.net/) based settings via [django-environ](https://github.com/joke2k/django-environ) -- Secure by default. We believe in SSL. -- Optimized development and production settings -- 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 -- 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 -- 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/) -- Run tests with unittest or pytest -- Customizable PostgreSQL version -- Default integration with [pre-commit](https://github.com/pre-commit/pre-commit) for identifying simple issues before submission to code review +- For Django 4.2 +- Works with Python 3.12 +- Renders Django projects with 100% starting test coverage +- Twitter [Bootstrap](https://github.com/twbs/bootstrap) v5 +- [12-Factor](https://12factor.net) based settings via [django-environ](https://github.com/joke2k/django-environ) +- Secure by default. We believe in SSL. +- Optimized development and production settings +- 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 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, 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/) +- Run tests with unittest or pytest +- Customizable PostgreSQL version +- Default integration with [pre-commit](https://github.com/pre-commit/pre-commit) for identifying simple issues before submission to code review ## Optional Integrations -*These features can be enabled during initial project setup.* +_These features can be enabled during initial project setup._ -- Serve static files from Amazon S3, Google Cloud 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 +- 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 [Mailpit](https://github.com/axllent/mailpit/) for local email testing +- Integration with [Sentry](https://sentry.io/welcome/) for error logging ## Constraints -- Only maintained 3rd party libraries are used. -- Uses PostgreSQL everywhere: 10.19 - 14.1 ([MySQL fork](https://github.com/mabdullahadeel/cookiecutter-django-mysql) also available). -- Environment variables for configuration (This won't work with Apache/mod_wsgi). +- Only maintained 3rd party libraries are used. +- Uses PostgreSQL everywhere: 12 - 16 ([MySQL fork](https://github.com/mabdullahadeel/cookiecutter-django-mysql) also available). +- Environment variables for configuration (This won't work with Apache/mod_wsgi). ## Support this Project! -This project is run by volunteers. Please support them in their efforts to maintain and improve Cookiecutter Django: +This project is an open source project run by volunteers. You can sponsor us via [OpenCollective](https://opencollective.com/cookiecutter-django) or individually via GitHub Sponsors: -- Daniel Roy Greenfeld, Project Lead ([GitHub](https://github.com/pydanny), [Patreon](https://www.patreon.com/danielroygreenfeld)): expertise in Django and AWS ELB. -- Nikita Shupeyko, Core Developer ([GitHub](https://github.com/webyneter)): expertise in Python/Django, hands-on DevOps and frontend experience. +- Daniel Roy Greenfeld, Project Lead ([GitHub](https://github.com/pydanny), [Patreon](https://www.patreon.com/danielroygreenfeld)): expertise in Django and AWS ELB. +- Fabio C. Barrionuevo, Core Developer ([GitHub](https://github.com/luzfcb)): expertise in Python/Django, hands-on DevOps and frontend experience. +- Bruno Alla, Core Developer ([GitHub](https://github.com/browniebroke)): expertise in Python/Django and DevOps. +- Nikita Shupeyko, Core Developer ([GitHub](https://github.com/webyneter)): expertise in Python/Django, hands-on DevOps and frontend experience. Projects that provide financial support to the maintainers: ------------------------------------------------------------------------- +---

@@ -116,16 +120,24 @@ Answer the prompts with your own desired [options](http://cookiecutter-django.re 4 - Apache Software License 2.0 5 - Not open source Choose from 1, 2, 3, 4, 5 [1]: 1 + Select username_type: + 1 - username + 2 - email + Choose from 1, 2 [1]: 1 timezone [UTC]: America/Los_Angeles windows [n]: n - use_pycharm [n]: y + Select an editor to use. The choices are: + 1 - None + 2 - PyCharm + 3 - VS Code + Choose from 1, 2, 3 [1]: 1 use_docker [n]: n Select postgresql_version: - 1 - 14 - 2 - 13 - 3 - 12 - 4 - 11 - 5 - 10 + 1 - 16 + 2 - 15 + 3 - 14 + 4 - 13 + 5 - 12 Choose from 1, 2, 3, 4, 5 [1]: 1 Select cloud_provider: 1 - AWS @@ -149,9 +161,10 @@ 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 + use_mailpit [n]: n use_sentry [n]: y use_whitenoise [n]: n use_heroku [n]: y @@ -181,14 +194,16 @@ Now take a look at your repo. Don't forget to carefully look at the generated RE For local development, see the following: -- [Developing locally](http://cookiecutter-django.readthedocs.io/en/latest/developing-locally.html) -- [Developing locally using docker](http://cookiecutter-django.readthedocs.io/en/latest/developing-locally-docker.html) +- [Developing locally](http://cookiecutter-django.readthedocs.io/en/latest/developing-locally.html) +- [Developing locally using docker](http://cookiecutter-django.readthedocs.io/en/latest/developing-locally-docker.html) ## Community -- Have questions? **Before you ask questions anywhere else**, please post your question on [Stack Overflow](http://stackoverflow.com/questions/tagged/cookiecutter-django) under the *cookiecutter-django* tag. We check there periodically for questions. -- If you think you found a bug or want to request a feature, please open an [issue](https://github.com/cookiecutter/cookiecutter-django/issues). -- For anything else, you can chat with us on [Discord](https://discord.gg/uFXweDQc5a). +- Have questions? **Before you ask questions anywhere else**, please post your question on [Stack Overflow](http://stackoverflow.com/questions/tagged/cookiecutter-django) under the _cookiecutter-django_ tag. We check there periodically for questions. +- If you think you found a bug or want to request a feature, please open an [issue](https://github.com/cookiecutter/cookiecutter-django/issues). +- For anything else, you can chat with us on [Discord](https://discord.gg/uFXweDQc5a). + +Contributors ## For Readers of Two Scoops of Django @@ -196,13 +211,14 @@ You may notice that some elements of this project do not exactly match what we d ## For PyUp Users -If you are using [PyUp](https://pyup.io) to keep your dependencies updated and secure, use the code *cookiecutter* during checkout to get 15% off every month. +If you are using [PyUp](https://pyup.io) to keep your dependencies updated and secure, use the code _cookiecutter_ during checkout to get 15% off every month. ## "Your Stuff" Scattered throughout the Python and HTML of this project are places marked with "your stuff". This is where third-party libraries are to be integrated with your project. ## For MySQL users + To get full MySQL support in addition to the default Postgresql, you can use this fork of the cookiecutter-django: https://github.com/mabdullahadeel/cookiecutter-django-mysql @@ -212,18 +228,18 @@ Need a stable release? You can find them at ``, that would only add the package to the container. The container is ephemeral, so that new library won't be persisted if you run another container. Instead, you should modify the Docker image: +You have to modify the relevant requirement file: base, local or production by adding: :: + + == + +To get this change picked up, you'll need to rebuild the image(s) and restart the running container: :: + + docker compose -f docker-compose.local.yml build + docker compose -f docker-compose.local.yml up + Debugging ~~~~~~~~~ @@ -153,7 +178,7 @@ If you are using the following within your code to debug: :: Then you may need to run the following for it to work as desired: :: - $ docker-compose -f local.yml run --rm --service-ports django + $ docker compose -f docker-compose.local.yml run --rm --service-ports django django-debug-toolbar @@ -173,16 +198,16 @@ The ``container_name`` from the yml file can be used to check on containers with Notice that the ``container_name`` is generated dynamically using your project slug as a prefix -Mailhog +Mailpit ~~~~~~~ -When developing locally you can go with MailHog_ for email testing provided ``use_mailhog`` was set to ``y`` on setup. To proceed, +When developing locally you can go with Mailpit_ for email testing provided ``use_mailpit`` was set to ``y`` on setup. To proceed, -#. make sure ``_local_mailhog`` container is up and running; +#. make sure ``_local_mailpit`` container is up and running; #. open up ``http://127.0.0.1:8025``. -.. _Mailhog: https://github.com/mailhog/MailHog/ +.. _Mailpit: https://github.com/axllent/mailpit/ .. _`CeleryTasks`: @@ -206,10 +231,20 @@ Prerequisites: * ``use_docker`` was set to ``y`` on project initialization; * ``use_celery`` was set to ``y`` on project initialization. -By default, it's enabled both in local and production environments (``local.yml`` and ``production.yml`` Docker Compose configs, respectively) through a ``flower`` service. For added security, ``flower`` requires its clients to provide authentication credentials specified as the corresponding environments' ``.envs/.local/.django`` and ``.envs/.production/.django`` ``CELERY_FLOWER_USER`` and ``CELERY_FLOWER_PASSWORD`` environment variables. Check out ``localhost:5555`` and see for yourself. +By default, it's enabled both in local and production environments (``docker-compose.local.yml`` and ``docker-compose.production.yml`` Docker Compose configs, respectively) through a ``flower`` service. For added security, ``flower`` requires its clients to provide authentication credentials specified as the corresponding environments' ``.envs/.local/.django`` and ``.envs/.production/.django`` ``CELERY_FLOWER_USER`` and ``CELERY_FLOWER_PASSWORD`` environment variables. Check out ``localhost:5555`` and see for yourself. .. _`Flower`: https://github.com/mher/flower +Using Webpack or Gulp +~~~~~~~~~~~~~~~~~~~~~ + +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 your 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. + +The stack comes with a dedicated node service to build the static assets, watch for changes and proxy requests to the Django app with live reloading scripts injected in the response. For everything to work smoothly, you need to access the application at the port served by the node service, which is http://localhost:3000 by default. + +.. _Sass: https://sass-lang.com/ +.. _live reloading: https://browsersync.io + Developing locally with HTTPS ----------------------------- @@ -244,7 +279,7 @@ certs Take the certificates that you generated and place them in a folder called ``certs`` in the project's root folder. Assuming that you registered your local hostname as ``my-dev-env.local``, the certificates you will put in the folder should have the names ``my-dev-env.local.crt`` and ``my-dev-env.local.key``. -local.yml +docker-compose.local.yml ~~~~~~~~~ #. Add the ``nginx-proxy`` service. :: @@ -288,7 +323,7 @@ You should allow the new hostname. :: Rebuild your ``docker`` application. :: - $ docker-compose -f local.yml up -d --build + $ docker compose -f docker-compose.local.yml up -d --build Go to your browser and type in your URL bar ``https://my-dev-env.local`` @@ -302,3 +337,26 @@ See `https with nginx`_ for more information on this configuration. Add ``certs/*`` to the ``.gitignore`` file. This allows the folder to be included in the repo but its contents to be ignored. *This configuration is for local development environments only. Do not use this for production since you might expose your local* ``rootCA-key.pem``. + +Webpack +~~~~~~~ + +If you are using Webpack: + +1. On the ``nginx-proxy`` service in ``docker-compose.local.yml``, change ``depends_on`` to ``node`` instead of ``django``. + +2. On the ``node`` service in ``docker-compose.local.yml``, add the following environment configuration: + + :: + + environment: + - VIRTUAL_HOST=my-dev-env.local + - VIRTUAL_PORT=3000 + +3. Add the following configuration to the ``devServer`` section of ``webpack/dev.config.js``: + + :: + + client: { + webSocketURL: 'auto://0.0.0.0:0/ws', // note the `:0` after `0.0.0.0` + }, diff --git a/docs/developing-locally.rst b/docs/developing-locally.rst index c9d28ff73..16247d082 100644 --- a/docs/developing-locally.rst +++ b/docs/developing-locally.rst @@ -9,7 +9,7 @@ Setting Up Development Environment Make sure to have the following on your host: -* Python 3.10 +* Python 3.12 * PostgreSQL_. * Redis_, if using Celery * Cookiecutter_ @@ -18,15 +18,14 @@ First things first. #. Create a virtualenv: :: - $ python3.10 -m venv + $ python3.12 -m venv #. Activate the virtualenv you have just created: :: $ 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:: @@ -80,10 +80,12 @@ First things first. $ python manage.py runserver 0.0.0.0:8000 -or if you're running asynchronously: :: + or if you're running asynchronously: :: $ uvicorn config.asgi:application --host 0.0.0.0 --reload --reload-include '*.html' + If you've opted for Webpack or Gulp as frontend pipeline, please see the :ref:`dedicated section ` below. + .. _PostgreSQL: https://www.postgresql.org/download/ .. _Redis: https://redis.io/download .. _CookieCutter: https://github.com/cookiecutter/cookiecutter @@ -94,42 +96,95 @@ or if you're running asynchronously: :: .. _direnv: https://direnv.net/ +Creating Your First Django App +------------------------------- + +After setting up your environment, you're ready to add your first app. This project uses the setup from "Two Scoops of Django" with a two-tier layout: + +- **Top Level Repository Root** has config files, documentation, `manage.py`, and more. +- **Second Level Django Project Root** is where your Django apps live. +- **Second Level Configuration Root** holds settings and URL configurations. + +The project layout looks something like this: :: + + / + ├── config/ + │ ├── settings/ + │ │ ├── __init__.py + │ │ ├── base.py + │ │ ├── local.py + │ │ └── production.py + │ ├── urls.py + │ └── wsgi.py + ├── / + │ ├── / + │ │ ├── migrations/ + │ │ ├── admin.py + │ │ ├── apps.py + │ │ ├── models.py + │ │ ├── tests.py + │ │ └── views.py + │ ├── __init__.py + │ └── ... + ├── requirements/ + │ ├── base.txt + │ ├── local.txt + │ └── production.txt + ├── manage.py + ├── README.md + └── ... + + +Following this structured approach, here's how to add a new app: + +#. **Create the app** using Django's ``startapp`` command, replacing ```` with your desired app name: :: + + $ python manage.py startapp + +#. **Move the app** to the Django Project Root, maintaining the project's two-tier structure: :: + + $ mv / + +#. **Edit the app's apps.py** change ``name = ''`` to ``name = '.'``. + +#. **Register the new app** by adding it to the ``LOCAL_APPS`` list in ``config/settings/base.py``, integrating it as an official component of your project. + + + Setup Email Backend ------------------- -MailHog +Mailpit ~~~~~~~ -.. note:: In order for the project to support MailHog_ it must have been bootstrapped with ``use_mailhog`` set to ``y``. +.. note:: In order for the project to support Mailpit_ it must have been bootstrapped with ``use_mailpit`` set to ``y``. -MailHog is used to receive emails during development, it is written in Go and has no external dependencies. +Mailpit is used to receive emails during development, it is written in Go and has no external dependencies. For instance, one of the packages we depend upon, ``django-allauth`` sends verification emails to new users signing up as well as to the existing ones who have not yet verified themselves. -#. `Download the latest MailHog release`_ for your OS. +#. `Download the latest Mailpit release`_ for your OS. -#. Rename the build to ``MailHog``. - -#. Copy the file to the project root. +#. Copy the binary file to the project root. #. Make it executable: :: - $ chmod +x MailHog + $ chmod +x mailpit #. Spin up another terminal window and start it there: :: - ./MailHog + ./mailpit #. Check out ``_ to see how it goes. Now you have your own mail server running locally, ready to receive whatever you send it. -.. _`Download the latest MailHog release`: https://github.com/mailhog/MailHog +.. _`Download the latest Mailpit release`: https://github.com/axllent/mailpit Console ~~~~~~~ -.. note:: If you have generated your project with ``use_mailhog`` set to ``n`` this will be a default setup. +.. note:: If you have generated your project with ``use_mailpit`` set to ``n`` this will be a default setup. Alternatively, deliver emails over console via ``EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'``. @@ -141,23 +196,42 @@ 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 ---------------------------------- +.. _bare-metal-webpack-gulp: -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. +Using Webpack or Gulp +--------------------- -#. Make sure that `Node.js`_ v16 is installed on your machine. +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 your 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`_ v18 is installed on your machine. #. In the project root, install the JS dependencies with:: $ npm install @@ -166,9 +240,12 @@ If you've opted for Gulp as front-end pipeline, the project comes configured wit $ npm run dev - The app will now run with live reloading enabled, applying front-end changes dynamically. + This will start 2 processes in parallel: the static assets build loop on one side, and the Django server on the other. + +#. Access your application at the address of the ``node`` service in order to see your correct styles. This is http://localhost:3000 by default. + + .. note:: Do NOT access the application using the Django port (8000 by default), as it will result in broken styles and 404s when accessing static assets. -.. note:: The task will start 2 processes in parallel: the static assets build loop on one side, and the Django server on the other. You do NOT need to run Django as your would normally with ``manage.py runserver``. .. _Node.js: http://nodejs.org/download/ .. _Sass: https://sass-lang.com/ diff --git a/docs/docker-postgres-backups.rst b/docs/docker-postgres-backups.rst index 875d737eb..d214ee4e8 100644 --- a/docs/docker-postgres-backups.rst +++ b/docs/docker-postgres-backups.rst @@ -1,14 +1,14 @@ PostgreSQL Backups with Docker ============================== -.. note:: For brevity it is assumed that you will be running the below commands against local environment, however, this is by no means mandatory so feel free to switch to ``production.yml`` when needed. +.. note:: For brevity it is assumed that you will be running the below commands against local environment, however, this is by no means mandatory so feel free to switch to ``docker-compose.production.yml`` when needed. Prerequisites ------------- #. the project was generated with ``use_docker`` set to ``y``; -#. the stack is up and running: ``docker-compose -f local.yml up -d postgres``. +#. the stack is up and running: ``docker compose -f docker-compose.local.yml up -d postgres``. Creating a Backup @@ -16,7 +16,7 @@ Creating a Backup To create a backup, run:: - $ docker-compose -f local.yml exec postgres backup + $ docker compose -f docker-compose.local.yml exec postgres backup Assuming your project's database is named ``my_project`` here is what you will see: :: @@ -31,7 +31,7 @@ Viewing the Existing Backups To list existing backups, :: - $ docker-compose -f local.yml exec postgres backups + $ docker compose -f docker-compose.local.yml exec postgres backups These are the sample contents of ``/backups``: :: @@ -55,9 +55,9 @@ With a single backup file copied to ``.`` that would be :: $ docker cp 9c5c3f055843:/backups/backup_2018_03_13T09_05_07.sql.gz . -You can also get the container ID using ``docker-compose -f local.yml ps -q postgres`` so if you want to automate your backups, you don't have to check the container ID manually every time. Here is the full command :: +You can also get the container ID using ``docker compose -f docker-compose.local.yml ps -q postgres`` so if you want to automate your backups, you don't have to check the container ID manually every time. Here is the full command :: - $ docker cp $(docker-compose -f local.yml ps -q postgres):/backups ./backups + $ docker cp $(docker compose -f docker-compose.local.yml ps -q postgres):/backups ./backups .. _`command`: https://docs.docker.com/engine/reference/commandline/cp/ @@ -66,7 +66,7 @@ Restoring from the Existing Backup To restore from one of the backups you have already got (take the ``backup_2018_03_13T09_05_07.sql.gz`` for example), :: - $ docker-compose -f local.yml exec postgres restore backup_2018_03_13T09_05_07.sql.gz + $ docker compose -f docker-compose.local.yml exec postgres restore backup_2018_03_13T09_05_07.sql.gz You will see something like :: @@ -92,7 +92,36 @@ You will see something like :: Backup to Amazon S3 ---------------------------------- + For uploading your backups to Amazon S3 you can use the aws cli container. There is an upload command for uploading the postgres /backups directory recursively and there is a download command for downloading a specific backup. The default S3 environment variables are used. :: - $ docker-compose -f production.yml run --rm awscli upload - $ docker-compose -f production.yml run --rm awscli download backup_2018_03_13T09_05_07.sql.gz + $ docker compose -f docker-compose.production.yml run --rm awscli upload + $ docker compose -f docker-compose.production.yml run --rm awscli download backup_2018_03_13T09_05_07.sql.gz + +Remove Backup +---------------------------------- + +To remove backup you can use the ``rmbackup`` command. This will remove the backup from the ``/backups`` directory. :: + + $ docker compose -f docker-compose.local.yml exec postgres rmbackup backup_2018_03_13T09_05_07.sql.gz + + +Upgrading PostgreSQL +---------------------------------- + +Upgrading PostgreSQL in your project requires a series of carefully executed steps. Start by halting all containers, excluding the postgres container. Following this, create a backup and proceed to remove the outdated data volume. :: + + $ docker compose -f docker-compose.local.yml down + $ docker compose -f docker-compose.local.yml up -d postgres + $ docker compose -f docker-compose.local.yml run --rm postgres backup + $ docker compose -f docker-compose.local.yml down + $ docker volume rm my_project_postgres_data + +.. note:: Neglecting to remove the old data volume may lead to issues, such as the new postgres container failing to start with errors like ``FATAL: database files are incompatible with server``, and ``could not translate host name "postgres" to address: Name or service not known``. + +To complete the upgrade, update the PostgreSQL version in the corresponding Dockerfile (e.g. ``compose/production/postgres/Dockerfile``) and build a new version of PostgreSQL. :: + + $ docker compose -f docker-compose.local.yml build postgres + $ docker compose -f docker-compose.local.yml up -d postgres + $ docker compose -f docker-compose.local.yml run --rm postgres restore backup_2018_03_13T09_05_07.sql.gz + $ docker compose -f docker-compose.local.yml up -d diff --git a/docs/document.rst b/docs/document.rst index 974c66c69..61cb692d3 100644 --- a/docs/document.rst +++ b/docs/document.rst @@ -11,7 +11,7 @@ After you have set up to `develop locally`_, run the following command from the If you set up your project to `develop locally with docker`_, run the following command: :: - $ docker-compose -f local.yml up docs + $ docker compose -f docker-compose.docs.yml up Navigate to port 9000 on your host to see the documentation. This will be opened automatically at `localhost`_ for local, non-docker development. diff --git a/docs/faq.rst b/docs/faq.rst index 52a99467c..9f0b52a7d 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -22,6 +22,6 @@ TODO Why doesn't this follow the layout from Two Scoops of Django? ------------------------------------------------------------- -You may notice that some elements of this project do not exactly match what we describe in chapter 3 of `Two Scoops of Django 1.11`_. The reason for that is this project, amongst other things, serves as a test bed for trying out new ideas and concepts. Sometimes they work, sometimes they don't, but the end result is that it won't necessarily match precisely what is described in the book I co-authored. +You may notice that some elements of this project do not exactly match what we describe in chapter 3 of `Two Scoops of Django 3.x`_. The reason for that is this project, amongst other things, serves as a test bed for trying out new ideas and concepts. Sometimes they work, sometimes they don't, but the end result is that it won't necessarily match precisely what is described in the book I co-authored. -.. _Two Scoops of Django 1.11: https://www.feldroy.com/collections/django/products/two-scoops-of-django-1-11 +.. _Two Scoops of Django 3.x: https://www.feldroy.com/books/two-scoops-of-django-3-x diff --git a/docs/generate-project-block.rst b/docs/generate-project-block.rst new file mode 100644 index 000000000..fca3fe1a8 --- /dev/null +++ b/docs/generate-project-block.rst @@ -0,0 +1,6 @@ +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..70daa1852 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -27,6 +27,8 @@ Contents websocket faq troubleshooting + contributing + maintainer-guide Indices and tables ------------------ diff --git a/docs/linters.rst b/docs/linters.rst index a4f60cc8d..1fc44f30b 100644 --- a/docs/linters.rst +++ b/docs/linters.rst @@ -4,40 +4,30 @@ Linters .. index:: linters -flake8 +ruff ------ -To run flake8: :: +Ruff is a Python linter and code formatter, written in Rust. +It is a aggregation of flake8, pylint, pyupgrade and many more. - $ flake8 +Ruff comes with a linter (``ruff check``) and a formatter (``ruff format``). +The linter is a wrapper around flake8, pylint, and other linters, +and the formatter is a wrapper around black, isort, and other formatters. -The config for flake8 is located in setup.cfg. It specifies: +To run ruff without modifying your files: :: -* Set max line length to 120 chars -* Exclude ``.tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules`` + $ ruff format --diff . + $ ruff check . -pylint ------- +Ruff is capable of fixing most of the problems it encounters. +Be sure you commit first before running `ruff` so you can restore to a savepoint (and amend afterwards to prevent a double commit. : :: -To run pylint: :: + $ ruff format . + $ ruff check --fix . + # be careful with the --unsafe-fixes option, it can break your code + $ ruff check --fix --unsafe-fixes . - $ pylint - -The config for pylint is located in .pylintrc. It specifies: - -* Use the pylint_django plugin. If using Celery, also use pylint_celery. -* Set max line length to 120 chars -* Disable linting messages for missing docstring and invalid name -* max-parents=13 - -pycodestyle ------------ - -This is included in flake8's checks, but you can also run it separately to see a more detailed report: :: - - $ pycodestyle - -The config for pycodestyle is located in setup.cfg. It specifies: - -* Set max line length to 120 chars -* Exclude ``.tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules`` +The config for ruff is located in pyproject.toml. +On of the most important option is `tool.ruff.lint.select`. +`select` determines which linters are run. In example, `DJ `_ refers to flake8-django. +For a full list of available linters, see `https://docs.astral.sh/ruff/rules/ `_ diff --git a/docs/maintainer-guide.md b/docs/maintainer-guide.md new file mode 100644 index 000000000..9baac688a --- /dev/null +++ b/docs/maintainer-guide.md @@ -0,0 +1,104 @@ +# Maintainer guide + +This document is intended for maintainers of the template. + +## Automated updates + +We use 2 separate services to keep our dependencies up-to-date: + +- Dependabot, which manages updates of Python deps of the template, GitHub actions, npm packages and Docker images. +- PyUp, which manages the Python deps for the generated project. + +We don't use Dependabot for the generated project deps because our requirements files are templated, and Dependabot fails to parse them. PyUp is -AFAIK- the only service out there that supports having Jinja tags in the requirements file. + +Updates for the template should be labelled as `project infrastructure` while the ones about the generated project should be labelled as `update`. This is use to work in conjunction with our changelog script (see later). + +## Automation scripts + +We have a few workflows which have been automated over time. They usually run using GitHub actions and might need a few small manual actions to work nicely. Some have a few limitations which we should document here. + +### CI + +`ci.yml` + +The CI workflow tries to cover 2 main aspects of the template: + +- Check all combinations to make sure that valid files are generated with no major linting issues. Issues which are fixed by an auto-formatter after generation aren't considered major, and only aim for best effort. This is under the `test` job. +- Run more in-depth tests on a few combinations, by installing dependencies, running type checker and the test suite of the generated project. We try to cover docker (`docker` job) and non-docker (`bare` job) setups. + +We also run the deployment checks, but we don't do much more beyond that for testing the production setup. + +### Django issue checker + +`django-issue-checker.yml` + +This workflow runs daily, on schedule, and checks if there is a new major version of Django (not in the pure SemVer sense) released that we are not running, and list our dependencies compatibility. + +For example, at time of writing, we use Django 4.2, but the latest version of Django is 5.0, so the workflow created a ["Django 5.0" issue](https://github.com/cookiecutter/cookiecutter-django/issues/4724) in GitHub, with a compatibility table and keeps it up to date every day. + +#### Limitations + +Here are a few current and past limitations of the script + +- When a new dependency is added to the template, the script fails to update an existing issue +- Not sure what happens when a deps is removed +- ~~Unable to parse classifiers without minor version~~ +- ~~Creates an issue even if we are on the latest version~~ + +### Issue manager + +`issue-manager.yml` + +A workflow that uses [Sebastian Ramirez' issue-manager](https://github.com/tiangolo/issue-manager) to help us automate issue management. The tag line from the repo explains it well: + +> Automatically close issues or Pull Requests that have a label, after a custom delay, if no one replies back. + +It runs on a schedule as well as when some actions are taken on issues and pull requests. + +We wait 10 days before closing issues, and we have a few customised reasons, which are configured in the workflow itself. The config should be fairly self-explanatory. + +### Pre-commit auto-update + +`pre-commit-autoupdate.yml` + +Run daily, to do `pre-commit autoupdate` on the template as well as the generated project, and opens a pull request with the changes. + +#### Limitations + +- The PR is open as GitHub action which means that CI does NOT run. The documentation for create-pull-request action [explains why](https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs). +- Some hooks are also installed as local dependencies (via `requirements/local.txt`), but these are updated separately via PyUP. + +### Update changelog + +`update-changelog.yml` + +Run daily at 2AM to update our changelog and create a GitHub release. This runs a custom script which: + +- List all pull requests merged the day before +- The release name is calendar based, so `YYYY.MM.DD` +- For each PR: + - Get the PR title to summarize the change + - Look at the PR labels to classify it in a section of the release notes: + - anything labelled `project infrastructure` is excluded + - label `update` goes in section "Updated" + - label `bug` goes in section "Fixed" + - label `docs` goes in section "Documentation" + - Default to section "Changed" + +With that in mind, when merging changes, it's a good idea to set the labels and rename the PR title to give a good summary of the change, in the context of the changelog. + +#### Limitations + +- Dependabot updates for npm & Docker have a verbose title, try to rename them to be more readable: `Bump webpack-dev-server from 4.15.1 to 5.0.2 in /{{cookiecutter.project_slug}}` -> `Bump webpack-dev-server to 5.0.2` +- ~~Dependencies updates for the template repo (tox, cookiecutter, etc...) don't need to appear in changelog, and need to be labelled as `project infrastructure` manually. By default, they come from PyUp labelled as `update`.~~ + +### Update contributors + +`update-contributors.yml` + +Runs on each push to master branch. List the 5 most recently merged pull requests and extract their author. If any of the authors is a new one, updates the `.github/contributors.json`, regenerate the `CONTRIBUTORS.md` from it, and push back the changes to master. + +#### Limitations + +- If you merge a pull request from a new contributor, and merge another one right after, the push to master will fail as the remote will be out of date. +- If you merge more than 5 pull requests in a row like this, the new contributor might fail to be added. diff --git a/docs/project-generation-options.rst b/docs/project-generation-options.rst index 2d8103cf2..23c8e9178 100644 --- a/docs/project-generation-options.rst +++ b/docs/project-generation-options.rst @@ -24,6 +24,13 @@ author_name: email: The email address you want to identify yourself in the project. +username_type: + The type of username you want to use in the project. This can be either + ``username`` or ``email``. If you choose ``username``, the ``email`` field + will be included. If you choose ``email``, the ``username`` field will be + excluded. It is best practice to always include an email field, so there is + no option for having just the ``username`` field. + domain_name: The domain name you plan to use for your project once it goes live. Note that it can be safely changed later on whenever you need to. @@ -46,29 +53,34 @@ timezone: windows: Indicates whether the project should be configured for development on Windows. -use_pycharm: - Indicates whether the project should be configured for development with PyCharm_. +editor: + Select an editor to use. The choices are: + + 1. None + 2. PyCharm_ + 3. `VS Code`_ use_docker: - Indicates whether the project should be configured to use Docker_ and `Docker Compose`_. + Indicates whether the project should be configured to use Docker_, `Docker Compose`_ and `devcontainer`_. postgresql_version: Select a PostgreSQL_ version to use. The choices are: - 1. 14 - 2. 13 - 3. 12 - 4. 11 - 5. 10 + 1. 16 + 2. 15 + 3. 14 + 4. 13 + 5. 12 cloud_provider: Select a cloud provider for static & media files. The choices are: 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 @@ -94,13 +106,16 @@ 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_. -use_mailhog: - Indicates whether the project should be configured to use MailHog_. +use_mailpit: + Indicates whether the project should be configured to use Mailpit_. use_sentry: Indicates whether the project should be configured to use Sentry_. @@ -119,6 +134,7 @@ ci_tool: 2. `Travis CI`_ 3. `Gitlab CI`_ 4. `Github Actions`_ + 5. `Drone CI`_ keep_local_envs_in_vcs: Indicates whether the project's ``.envs/.local/`` should be kept in VCS @@ -137,16 +153,20 @@ debug: .. _Apache Software License 2.0: http://www.apache.org/licenses/LICENSE-2.0 .. _PyCharm: https://www.jetbrains.com/pycharm/ +.. _VS Code: https://github.com/microsoft/vscode .. _Docker: https://github.com/docker/docker .. _Docker Compose: https://docs.docker.com/compose/ +.. _devcontainer: https://containers.dev/ .. _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 @@ -164,7 +184,7 @@ debug: .. _Celery: https://github.com/celery/celery -.. _MailHog: https://github.com/mailhog/MailHog +.. _Mailpit: https://github.com/axllent/mailpit .. _Sentry: https://github.com/getsentry/sentry @@ -176,4 +196,6 @@ debug: .. _GitLab CI: https://docs.gitlab.com/ee/ci/ +.. _Drone CI: https://docs.drone.io/pipeline/overview/ + .. _Github Actions: https://docs.github.com/en/actions diff --git a/docs/requirements.txt b/docs/requirements.txt index 2cc8302a2..26cb8da1b 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,3 @@ -sphinx==5.3.0 -sphinx-rtd-theme==1.1.1 +sphinx==7.3.7 +sphinx-rtd-theme==2.0.0 +myst-parser==3.0.1 diff --git a/docs/settings.rst b/docs/settings.rst index b8c6d448e..0880bce95 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -22,7 +22,6 @@ DATABASE_URL DATABASES auto w/ Dock DJANGO_ADMIN_URL n/a 'admin/' raises error DJANGO_DEBUG DEBUG True False DJANGO_SECRET_KEY SECRET_KEY auto-generated raises error -DJANGO_SECURE_BROWSER_XSS_FILTER SECURE_BROWSER_XSS_FILTER n/a True DJANGO_SECURE_SSL_REDIRECT SECURE_SSL_REDIRECT n/a True DJANGO_SECURE_CONTENT_TYPE_NOSNIFF SECURE_CONTENT_TYPE_NOSNIFF n/a True DJANGO_SECURE_FRAME_DENY SECURE_FRAME_DENY n/a True @@ -49,6 +48,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 @@ -79,3 +81,6 @@ Other Environment Settings DJANGO_ACCOUNT_ALLOW_REGISTRATION (=True) Allow enable or disable user registration through `django-allauth` without disabling other characteristics like authentication and account management. (Django Setting: ACCOUNT_ALLOW_REGISTRATION) + +DJANGO_ADMIN_FORCE_ALLAUTH (=False) + Force the `admin` sign in process to go through the `django-allauth` workflow. diff --git a/docs/testing.rst b/docs/testing.rst index dd6fcb48f..58a05770a 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -19,7 +19,7 @@ You will get a readout of the `users` app that has already been set up with test If you set up your project to `develop locally with docker`_, run the following command: :: - $ docker-compose -f local.yml run --rm django pytest + $ docker compose -f docker-compose.local.yml run --rm django pytest Targeting particular apps for testing in ``docker`` follows a similar pattern as previously shown above. @@ -28,17 +28,22 @@ 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: :: - $ docker-compose -f local.yml run --rm django coverage report + $ coverage report + +If you're running the project locally with Docker, use these commands instead: :: + + $ docker compose -f docker-compose.local.yml run --rm django coverage run -m pytest + $ docker compose -f docker-compose.local.yml run --rm django coverage report .. note:: At the root of the project folder, you will find the `pytest.ini` file. You can use this to customize_ the ``pytest`` to your liking. - There is also the `.coveragerc`. This is the configuration file for the ``coverage`` tool. You can find out more about `configuring`_ ``coverage``. + The configuration for ``coverage`` can be found in ``pyproject.toml``. You can find out more about `configuring`_ ``coverage``. .. seealso:: @@ -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..847f0a701 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. @@ -24,13 +24,13 @@ Examples of logs:: If you recreate the project multiple times with the same name, Docker would preserve the volumes for the postgres container between projects. Here is what happens: #. You generate the project the first time. The .env postgres file is populated with the random password -#. You run the docker-compose and the containers are created. The postgres container creates the database based on the .env file credentials +#. You run the docker compose and the containers are created. The postgres container creates the database based on the .env file credentials #. You "regenerate" the project with the same name, so the postgres .env file is populated with a new random password -#. You run docker-compose. Since the names of the containers are the same, docker will try to start them (not create them from scratch i.e. it won't execute the Dockerfile to recreate the database). When this happens, it tries to start the database based on the new credentials which do not match the ones that the database was created with, and you get the error message above. +#. You run docker compose. Since the names of the containers are the same, docker will try to start them (not create them from scratch i.e. it won't execute the Dockerfile to recreate the database). When this happens, it tries to start the database based on the new credentials which do not match the ones that the database was created with, and you get the error message above. To fix this, you can either: -- Clear your project-related Docker cache with ``docker-compose -f local.yml down --volumes --rmi all``. +- Clear your project-related Docker cache with ``docker compose -f docker-compose.local.yml down --volumes --rmi all``. - Use the Docker volume sub-commands to find volumes (`ls`_) and remove them (`rm`_). - Use the `prune`_ command to clear system-wide (use with care!). @@ -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 6927f3804..050c18cd1 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -8,6 +8,7 @@ NOTE: TODO: restrict Cookiecutter Django project initialization to Python 3.x environments only """ + from __future__ import print_function import json @@ -33,6 +34,24 @@ SUCCESS = "\x1b[1;32m [SUCCESS]: " DEBUG_VALUE = "debug" +def remove_custom_user_manager_files(): + os.remove( + os.path.join( + "{{cookiecutter.project_slug}}", + "users", + "managers.py", + ) + ) + os.remove( + os.path.join( + "{{cookiecutter.project_slug}}", + "users", + "tests", + "test_managers.py", + ) + ) + + def remove_pycharm_files(): idea_dir_path = ".idea" if os.path.exists(idea_dir_path): @@ -44,12 +63,17 @@ def remove_pycharm_files(): def remove_docker_files(): + shutil.rmtree(".devcontainer") shutil.rmtree("compose") - file_names = ["local.yml", "production.yml", ".dockerignore"] + file_names = [ + "docker-compose.local.yml", + "docker-compose.production.yml", + ".dockerignore", + ] for file_name in file_names: os.remove(file_name) - if "{{ cookiecutter.use_pycharm }}".lower() == "y": + if "{{ cookiecutter.editor }}" == "PyCharm": file_names = ["docker_compose_up_django.xml", "docker_compose_up_docs.xml"] for file_name in file_names: os.remove(os.path.join(".idea", "runConfigurations", file_name)) @@ -62,29 +86,37 @@ def remove_utility_files(): def remove_heroku_files(): file_names = ["Procfile", "runtime.txt", "requirements.txt"] for file_name in file_names: - if ( - file_name == "requirements.txt" - and "{{ cookiecutter.ci_tool }}".lower() == "travis" - ): + if file_name == "requirements.txt" and "{{ cookiecutter.ci_tool }}".lower() == "travis": # don't remove the file if we are using travisci but not using heroku continue os.remove(file_name) - remove_heroku_build_hooks() - - -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(): @@ -93,13 +125,105 @@ 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-concat", + "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_prettier_pre_commit(): + with open(".pre-commit-config.yaml", "r") as fd: + content = fd.readlines() + + removing = False + new_lines = [] + for line in content: + if removing and "- repo:" in line: + removing = False + if "mirrors-prettier" in line: + removing = True + if not removing: + new_lines.append(line) + + with open(".pre-commit-config.yaml", "w") as fd: + fd.writelines(new_lines) + + def remove_celery_files(): file_names = [ os.path.join("config", "celery_app.py"), os.path.join("{{ cookiecutter.project_slug }}", "users", "tasks.py"), - os.path.join( - "{{ cookiecutter.project_slug }}", "users", "tests", "test_tasks.py" - ), + os.path.join("{{ cookiecutter.project_slug }}", "users", "tests", "test_tasks.py"), ] for file_name in file_names: os.remove(file_name) @@ -126,9 +250,11 @@ def remove_dotgithub_folder(): shutil.rmtree(".github") -def generate_random_string( - length, using_digits=False, using_ascii_letters=False, using_punctuation=False -): +def remove_dotdrone_file(): + os.remove(".drone.yml") + + +def generate_random_string(length, using_digits=False, using_ascii_letters=False, using_punctuation=False): """ Example: opting out for 50 symbol-long, [a-z][A-Z][0-9] string @@ -222,9 +348,7 @@ def set_postgres_password(file_path, value=None): def set_celery_flower_user(file_path, value): - celery_flower_user = set_flag( - file_path, "!!!SET CELERY_FLOWER_USER!!!", value=value - ) + celery_flower_user = set_flag(file_path, "!!!SET CELERY_FLOWER_USER!!!", value=value) return celery_flower_user @@ -256,22 +380,14 @@ def set_flags_in_envs(postgres_user, celery_flower_user, debug=False): set_django_admin_url(production_django_envs_path) set_postgres_user(local_postgres_envs_path, value=postgres_user) - set_postgres_password( - local_postgres_envs_path, value=DEBUG_VALUE if debug else None - ) + set_postgres_password(local_postgres_envs_path, value=DEBUG_VALUE if debug else None) set_postgres_user(production_postgres_envs_path, value=postgres_user) - set_postgres_password( - production_postgres_envs_path, value=DEBUG_VALUE if debug else None - ) + set_postgres_password(production_postgres_envs_path, value=DEBUG_VALUE if debug else None) set_celery_flower_user(local_django_envs_path, value=celery_flower_user) - set_celery_flower_password( - local_django_envs_path, value=DEBUG_VALUE if debug else None - ) + set_celery_flower_password(local_django_envs_path, value=DEBUG_VALUE if debug else None) set_celery_flower_user(production_django_envs_path, value=celery_flower_user) - set_celery_flower_password( - production_django_envs_path, value=DEBUG_VALUE if debug else None - ) + set_celery_flower_password(production_django_envs_path, value=DEBUG_VALUE if debug else None) def set_flags_in_settings_files(): @@ -282,6 +398,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(): @@ -300,25 +417,9 @@ def remove_aws_dockerfile(): def remove_drf_starter_files(): os.remove(os.path.join("config", "api_router.py")) shutil.rmtree(os.path.join("{{cookiecutter.project_slug}}", "users", "api")) - os.remove( - os.path.join( - "{{cookiecutter.project_slug}}", "users", "tests", "test_drf_urls.py" - ) - ) - os.remove( - os.path.join( - "{{cookiecutter.project_slug}}", "users", "tests", "test_drf_views.py" - ) - ) - os.remove( - os.path.join( - "{{cookiecutter.project_slug}}", "users", "tests", "test_swagger.py" - ) - ) - - -def remove_storages_module(): - os.remove(os.path.join("{{cookiecutter.project_slug}}", "utils", "storages.py")) + os.remove(os.path.join("{{cookiecutter.project_slug}}", "users", "tests", "test_drf_urls.py")) + os.remove(os.path.join("{{cookiecutter.project_slug}}", "users", "tests", "test_drf_views.py")) + os.remove(os.path.join("{{cookiecutter.project_slug}}", "users", "tests", "test_swagger.py")) def handle_licenses(): @@ -365,7 +466,10 @@ def main(): handle_licenses() - if "{{ cookiecutter.use_pycharm }}".lower() == "n": + if "{{ cookiecutter.username_type }}" == "username": + remove_custom_user_manager_files() + + if "{{ cookiecutter.editor }}" != "PyCharm": remove_pycharm_files() if "{{ cookiecutter.use_docker }}".lower() == "y": @@ -373,26 +477,18 @@ def main(): else: remove_docker_files() - if ( - "{{ cookiecutter.use_docker }}".lower() == "y" - and "{{ cookiecutter.cloud_provider}}" != "AWS" - ): + if "{{ cookiecutter.use_docker }}".lower() == "y" and "{{ cookiecutter.cloud_provider}}" != "AWS": remove_aws_dockerfile() if "{{ cookiecutter.use_heroku }}".lower() == "n": remove_heroku_files() - elif "{{ cookiecutter.frontend_pipeline }}" != "Django Compressor": - remove_heroku_build_hooks() - if ( - "{{ cookiecutter.use_docker }}".lower() == "n" - and "{{ cookiecutter.use_heroku }}".lower() == "n" - ): + if "{{ cookiecutter.use_docker }}".lower() == "n" and "{{ cookiecutter.use_heroku }}".lower() == "n": if "{{ cookiecutter.keep_local_envs_in_vcs }}".lower() == "y": print( INFO + ".env(s) are only utilized when Docker Compose and/or " - "Heroku support is enabled so keeping them does not " - "make sense given your current setup." + TERMINATOR + "Heroku support is enabled so keeping them does not make sense " + "given your current setup." + TERMINATOR ) remove_envs_and_associated_files() else: @@ -401,18 +497,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() + remove_prettier_pre_commit() 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() if "{{ cookiecutter.use_celery }}".lower() == "n": remove_celery_files() @@ -428,6 +532,9 @@ def main(): if "{{ cookiecutter.ci_tool }}" != "Github": remove_dotgithub_folder() + if "{{ cookiecutter.ci_tool }}" != "Drone": + remove_dotdrone_file() + if "{{ cookiecutter.use_drf }}".lower() == "n": remove_drf_starter_files() diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py index 2845f012c..e58fd3541 100644 --- a/hooks/pre_gen_project.py +++ b/hooks/pre_gen_project.py @@ -7,6 +7,7 @@ NOTE: TODO: restrict Cookiecutter Django project initialization to Python 3.x environments only """ + from __future__ import print_function import sys @@ -17,26 +18,28 @@ INFO = "\x1b[1;33m [INFO]: " HINT = "\x1b[3;33m" SUCCESS = "\x1b[1;32m [SUCCESS]: " +# The content of this string is evaluated by Jinja, and plays an important role. +# It updates the cookiecutter context to trim leading and trailing spaces +# from domain/email values +""" +{{ cookiecutter.update({ "domain_name": cookiecutter.domain_name | trim }) }} +{{ cookiecutter.update({ "email": cookiecutter.email | trim }) }} +""" + project_slug = "{{ cookiecutter.project_slug }}" if hasattr(project_slug, "isidentifier"): - assert ( - project_slug.isidentifier() - ), "'{}' project slug is not a valid Python identifier.".format(project_slug) + assert project_slug.isidentifier(), "'{}' project slug is not a valid Python identifier.".format(project_slug) -assert ( - project_slug == project_slug.lower() -), "'{}' project slug should be all lowercase".format(project_slug) +assert project_slug == project_slug.lower(), "'{}' project slug should be all lowercase".format(project_slug) -assert ( - "\\" not in "{{ cookiecutter.author_name }}" -), "Don't include backslashes in author name." +assert "\\" not in "{{ cookiecutter.author_name }}", "Don't include backslashes in author name." if "{{ cookiecutter.use_docker }}".lower() == "n": python_major_version = sys.version_info[0] if python_major_version == 2: print( WARNING + "You're running cookiecutter under Python 2, but the generated " - "project requires Python 3.10+. Do you want to proceed (y/n)? " + TERMINATOR + "project requires Python 3.12+. Do you want to proceed (y/n)? " + TERMINATOR ) yes_options, no_options = frozenset(["y"]), frozenset(["n"]) while True: @@ -51,35 +54,16 @@ if "{{ cookiecutter.use_docker }}".lower() == "n": print( HINT + "Please respond with {} or {}: ".format( - ", ".join( - ["'{}'".format(o) for o in yes_options if not o == ""] - ), - ", ".join( - ["'{}'".format(o) for o in no_options if not o == ""] - ), + ", ".join(["'{}'".format(o) for o in yes_options if not o == ""]), + ", ".join(["'{}'".format(o) for o in no_options if not o == ""]), ) + TERMINATOR ) -if ( - "{{ cookiecutter.use_whitenoise }}".lower() == "n" - and "{{ cookiecutter.cloud_provider }}" == "None" -): - print( - "You should either use Whitenoise or select a " - "Cloud Provider to serve static files" - ) +if "{{ cookiecutter.use_whitenoise }}".lower() == "n" and "{{ cookiecutter.cloud_provider }}" == "None": + print("You should either use Whitenoise or select a " "Cloud Provider to serve static files") 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" -): - print( - "You should either use AWS or select a different " - "Mail Service for sending emails." - ) +if "{{ cookiecutter.mail_service }}" == "Amazon SES" and "{{ cookiecutter.cloud_provider }}" != "AWS": + print("You should either use AWS or select a different " "Mail Service for sending emails.") sys.exit(1) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..6e68762f7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,50 @@ +# ==== pytest ==== +[tool.pytest.ini_options] +addopts = "-v --tb=short" +norecursedirs = [ + ".tox", + ".git", + "*/migrations/*", + "*/static/*", + "docs", + "venv", + "*/{{cookiecutter.project_slug}}/*", +] + + +# ==== black ==== +[tool.black] +line-length = 119 +target-version = ['py312'] + + +# ==== isort ==== +[tool.isort] +profile = "black" +line_length = 119 +known_first_party = [ + "tests", + "scripts", + "hooks", +] + + +# ==== djLint ==== +[tool.djlint] +blank_line_after_tag = "load,extends" +close_void_tags = true +format_css = true +format_js = true +# TODO: remove T002 when fixed https://github.com/Riverside-Healthcare/djLint/issues/687 +ignore = "H006,H030,H031,T002,T028" +ignore_blocks = "raw" +include = "H017,H035" +indent = 2 +max_line_length = 119 +profile = "jinja" + +[tool.djlint.css] +indent_size = 2 + +[tool.djlint.js] +indent_size = 2 diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 52506f47d..000000000 --- a/pytest.ini +++ /dev/null @@ -1,3 +0,0 @@ -[pytest] -addopts = -v --tb=short -norecursedirs = .tox .git */migrations/* */static/* docs venv */{{cookiecutter.project_slug}}/* diff --git a/requirements.txt b/requirements.txt index ea9671876..47d06222f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,26 +1,26 @@ -cookiecutter==2.1.1 -sh==1.14.3; sys_platform != "win32" +cookiecutter==2.6.0 +sh==2.0.6; 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 +ruff==0.4.4 +django-upgrade==1.17.0 +djlint==1.34.1 +pre-commit==3.7.1 # Testing # ------------------------------------------------------------------------------ -tox==3.27.0 -pytest==7.2.0 -pytest-cookies==0.6.1 -pytest-instafail==0.4.2 -pyyaml==6.0 +tox==4.15.0 +pytest==8.2.0 +pytest-xdist==3.6.1 +pytest-cookies==0.7.0 +pytest-instafail==0.5.0 +pyyaml==6.0.1 # Scripting # ------------------------------------------------------------------------------ -PyGithub==1.57 -gitpython==3.1.29 -jinja2==3.1.2 -requests==2.28.1 +PyGithub==2.3.0 +gitpython==3.1.43 +jinja2==3.1.4 +requests==2.31.0 diff --git a/scripts/create_django_issue.py b/scripts/create_django_issue.py index 5809f393d..2e59f18b0 100644 --- a/scripts/create_django_issue.py +++ b/scripts/create_django_issue.py @@ -6,6 +6,7 @@ patches, only comparing major and minor version numbers. This script handles when there are multiple Django versions that need to keep up to date. """ + from __future__ import annotations import os @@ -141,9 +142,7 @@ class GitHubManager: self.requirements_files = ["base", "local", "production"] # Format: # requirement file name: {package name: (master_version, package_info)} - self.requirements: dict[str, dict[str, tuple[str, dict]]] = { - x: {} for x in self.requirements_files - } + self.requirements: dict[str, dict[str, tuple[str, dict]]] = {x: {} for x in self.requirements_files} def setup(self) -> None: self.load_requirements() @@ -177,26 +176,19 @@ class GitHubManager: "is": "issue", "in": "title", } - issues = list( - self.github.search_issues( - "[Django Update]", "created", "desc", **qualifiers - ) - ) + issues = list(self.github.search_issues("[Django Update]", "created", "desc", **qualifiers)) print(f"Found {len(issues)} issues matching search") for issue in issues: matches = re.match(r"\[Update Django] Django (\d+.\d+)$", issue.title) if not matches: continue issue_version = DjVersion.parse(matches.group(1)) - if self.base_dj_version > issue_version: - issue.edit(state="closed") - print(f"Closed issue {issue.title} (ID: [{issue.id}]({issue.url}))") + if self.base_dj_version >= issue_version: + self.close_issue(issue) else: self.existing_issues[issue_version] = issue - def get_compatibility( - self, package_name: str, package_info: dict, needed_dj_version: DjVersion - ): + def get_compatibility(self, package_name: str, package_info: dict, needed_dj_version: DjVersion): """ Verify compatibility via setup.py classifiers. If Django is not in the classifiers, then default compatibility is n/a and OK is ✅. @@ -209,9 +201,7 @@ class GitHubManager: # updated packages, or known releases that will happen but haven't yet if issue := self.existing_issues.get(needed_dj_version): if index := issue.body.find(package_name): - name, _current, prev_compat, ok = ( - s.strip() for s in issue.body[index:].split("|", 4)[:4] - ) + name, _current, prev_compat, ok = (s.strip() for s in issue.body[index:].split("|", 4)[:4]) if ok in ("✅", "❓", "🕒"): return prev_compat, ok @@ -223,7 +213,7 @@ class GitHubManager: for classifier in package_info["info"]["classifiers"]: # Usually in the form of "Framework :: Django :: 3.2" tokens = classifier.split(" ") - if len(tokens) >= 5 and tokens[2].lower() == "django": + if len(tokens) >= 5 and tokens[2].lower() == "django" and "." in tokens[4]: version = DjVersion.parse(tokens[4]) if len(version) == 2: supported_dj_versions.append(version) @@ -248,9 +238,7 @@ class GitHubManager: ] def _get_md_home_page_url(self, package_info: dict): - urls = [ - package_info["info"].get(url_key) for url_key in self.HOME_PAGE_URL_KEYS - ] + urls = [package_info["info"].get(url_key) for url_key in self.HOME_PAGE_URL_KEYS] try: return f"[{{}}]({next(item for item in urls if item)})" except StopIteration: @@ -259,13 +247,9 @@ class GitHubManager: def generate_markdown(self, needed_dj_version: DjVersion): requirements = f"{needed_dj_version} requirements tables\n\n" for _file in self.requirements_files: - requirements += _TABLE_HEADER.format_map( - {"file": _file, "dj_version": needed_dj_version} - ) + requirements += _TABLE_HEADER.format_map({"file": _file, "dj_version": needed_dj_version}) for package_name, (version, info) in self.requirements[_file].items(): - compat_version, icon = self.get_compatibility( - package_name, info, needed_dj_version - ) + compat_version, icon = self.get_compatibility(package_name, info, needed_dj_version) requirements += ( f"| {self._get_md_home_page_url(info).format(package_name)} " f"| {version.strip()} " @@ -282,11 +266,14 @@ class GitHubManager: issue.edit(body=description) else: print(f"Creating new issue for Django {needed_dj_version}") - issue = self.repo.create_issue( - f"[Update Django] Django {needed_dj_version}", description - ) + issue = self.repo.create_issue(f"[Update Django] Django {needed_dj_version}", description) issue.add_to_labels(f"django{needed_dj_version}") + @staticmethod + def close_issue(issue: Issue): + issue.edit(state="closed") + print(f"Closed issue {issue.title} (ID: [{issue.id}]({issue.url}))") + def generate(self): for version in self.needed_dj_versions: print(f"Handling GitHub issue for Django {version}") @@ -297,21 +284,22 @@ class GitHubManager: def main(django_max_version=None) -> None: # Check if there are any djs - current_dj, latest_djs = get_all_latest_django_versions( - django_max_version=django_max_version - ) - if not latest_djs: - sys.exit(0) + current_dj, latest_djs = get_all_latest_django_versions(django_max_version=django_max_version) + + # Run the setup, which might close old issues manager = GitHubManager(current_dj, latest_djs) manager.setup() + + if not latest_djs: + print("No new Django versions to update. Exiting...") + sys.exit(0) + manager.generate() if __name__ == "__main__": if GITHUB_REPO is None: - raise RuntimeError( - "No github repo, please set the environment variable GITHUB_REPOSITORY" - ) + raise RuntimeError("No github repo, please set the environment variable GITHUB_REPOSITORY") max_version = None last_arg = sys.argv[-1] if CURRENT_FILE.name not in last_arg: diff --git a/scripts/update_changelog.py b/scripts/update_changelog.py index b50d25066..5f3ad5ec3 100644 --- a/scripts/update_changelog.py +++ b/scripts/update_changelog.py @@ -32,6 +32,9 @@ def main() -> None: # Group pull requests by type of change grouped_pulls = group_pulls_by_change_type(merged_pulls) + if not any(grouped_pulls.values()): + print("Pull requests merged aren't worth a changelog mention.") + return # Generate portion of markdown release_changes_summary = generate_md(grouped_pulls) @@ -82,14 +85,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) @@ -148,11 +157,7 @@ def update_git_repo(paths: list[Path], release: str) -> None: if __name__ == "__main__": if GITHUB_REPO is None: - raise RuntimeError( - "No github repo, please set the environment variable GITHUB_REPOSITORY" - ) + raise RuntimeError("No github repo, please set the environment variable GITHUB_REPOSITORY") if GIT_BRANCH is None: - raise RuntimeError( - "No git branch set, please set the GITHUB_REF_NAME environment variable" - ) + raise RuntimeError("No git branch set, please set the GITHUB_REF_NAME environment variable") main() diff --git a/scripts/update_contributors.py b/scripts/update_contributors.py index 76ccf60ad..7f7b48d76 100644 --- a/scripts/update_contributors.py +++ b/scripts/update_contributors.py @@ -40,19 +40,13 @@ def iter_recent_authors(): """ Fetch users who opened recently merged pull requests. - Use Github API to fetch recent authors rather than - git CLI to work with Github usernames. + Use GitHub API to fetch recent authors rather than + git CLI to work with GitHub usernames. """ repo = Github(login_or_token=GITHUB_TOKEN, per_page=5).get_repo(GITHUB_REPO) - recent_pulls = repo.get_pulls( - state="closed", sort="updated", direction="desc" - ).get_page(0) + recent_pulls = repo.get_pulls(state="closed", sort="updated", direction="desc").get_page(0) for pull in recent_pulls: - if ( - pull.merged - and pull.user.type == "User" - and pull.user.login not in BOT_LOGINS - ): + if pull.merged and pull.user.type == "User" and pull.user.login not in BOT_LOGINS: yield pull.user @@ -96,9 +90,7 @@ def write_md_file(contributors): core_contributors = [c for c in contributors if c.get("is_core", False)] other_contributors = (c for c in contributors if not c.get("is_core", False)) other_contributors = sorted(other_contributors, key=lambda c: c["name"].lower()) - content = template.render( - core_contributors=core_contributors, other_contributors=other_contributors - ) + content = template.render(core_contributors=core_contributors, other_contributors=other_contributors) file_path = ROOT / "CONTRIBUTORS.md" file_path.write_text(content) @@ -106,7 +98,5 @@ def write_md_file(contributors): if __name__ == "__main__": if GITHUB_REPO is None: - raise RuntimeError( - "No github repo, please set the environment variable GITHUB_REPOSITORY" - ) + raise RuntimeError("No github repo, please set the environment variable GITHUB_REPOSITORY") main() diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index dd8f1ef3c..000000000 --- a/setup.cfg +++ /dev/null @@ -1,7 +0,0 @@ -[flake8] -exclude = docs -max-line-length = 88 - -[isort] -profile = black -known_first_party = tests,scripts,hooks diff --git a/setup.py b/setup.py index 8100edc12..6fac0c577 100644 --- a/setup.py +++ b/setup.py @@ -5,18 +5,15 @@ except ImportError: from distutils.core import setup # We use calendar versioning -version = "2022.11.07" +version = "2024.05.11" -with open("README.rst") as readme_file: +with open("README.md") as readme_file: long_description = readme_file.read() setup( name="cookiecutter-django", version=version, - description=( - "A Cookiecutter template for creating production-ready " - "Django projects quickly." - ), + description=("A Cookiecutter template for creating production-ready " "Django projects quickly."), long_description=long_description, author="Daniel Roy Greenfeld", author_email="pydanny@gmail.com", @@ -27,13 +24,13 @@ setup( classifiers=[ "Development Status :: 4 - Beta", "Environment :: Console", - "Framework :: Django :: 4.0", + "Framework :: Django :: 4.2", "Intended Audience :: Developers", "Natural Language :: English", "License :: OSI Approved :: BSD License", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Software Development", ], diff --git a/tests/test_bare.sh b/tests/test_bare.sh index 05da9328b..5dc175ebd 100755 --- a/tests/test_bare.sh +++ b/tests/test_bare.sh @@ -20,25 +20,17 @@ sudo utility/install_os_dependencies.sh install # Install Python deps pip install -r requirements/local.txt -# Lint by running pre-commit on all files -# Needs a git repo to find the project root -git init -git add . -pre-commit run --show-diff-on-failure -a - # run the project's tests 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 f56fa2bb8..c8a580e4d 100755 --- a/tests/test_cookiecutter_generation.py +++ b/tests/test_cookiecutter_generation.py @@ -1,3 +1,4 @@ +import glob import os import re import sys @@ -20,6 +21,12 @@ if sys.platform.startswith("win"): elif sys.platform.startswith("darwin") and os.getenv("CI"): pytest.skip("skipping slow macOS tests on CI", allow_module_level=True) +# Run auto-fixable styles checks - skipped on CI by default. These can be fixed +# automatically by running pre-commit after generation however they are tedious +# to fix in the template, so we don't insist too much in fixing them. +AUTOFIXABLE_STYLES = os.getenv("AUTOFIXABLE_STYLES") == "1" +auto_fixable = pytest.mark.skipif(not AUTOFIXABLE_STYLES, reason="auto-fixable") + @pytest.fixture def context(): @@ -48,21 +55,26 @@ def generate_license_file_titles(): SUPPORTED_COMBINATIONS = [ *[{"open_source_license": x} for x in generate_license_file_titles()], + {"username_type": "username"}, + {"username_type": "email"}, {"windows": "y"}, {"windows": "n"}, - {"use_pycharm": "y"}, - {"use_pycharm": "n"}, + {"editor": "None"}, + {"editor": "PyCharm"}, + {"editor": "VS Code"}, {"use_docker": "y"}, {"use_docker": "n"}, + {"postgresql_version": "16"}, + {"postgresql_version": "15"}, {"postgresql_version": "14"}, {"postgresql_version": "13"}, {"postgresql_version": "12"}, - {"postgresql_version": "11"}, - {"postgresql_version": "10"}, {"cloud_provider": "AWS", "use_whitenoise": "y"}, {"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"}, @@ -89,7 +101,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"}, @@ -97,10 +118,11 @@ SUPPORTED_COMBINATIONS = [ {"frontend_pipeline": "None"}, {"frontend_pipeline": "Django Compressor"}, {"frontend_pipeline": "Gulp"}, + {"frontend_pipeline": "Webpack"}, {"use_celery": "y"}, {"use_celery": "n"}, - {"use_mailhog": "y"}, - {"use_mailhog": "n"}, + {"use_mailpit": "y"}, + {"use_mailpit": "n"}, {"use_sentry": "y"}, {"use_sentry": "n"}, {"use_whitenoise": "y"}, @@ -111,6 +133,7 @@ SUPPORTED_COMBINATIONS = [ {"ci_tool": "Travis"}, {"ci_tool": "Gitlab"}, {"ci_tool": "Github"}, + {"ci_tool": "Drone"}, {"keep_local_envs_in_vcs": "y"}, {"keep_local_envs_in_vcs": "n"}, {"debug": "y"}, @@ -120,6 +143,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"}, ] @@ -129,13 +153,9 @@ 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 file_path in files - ] + return [os.path.join(dirpath, file_path) for dirpath, subdirs, files in os.walk(base_dir) for file_path in files] def check_paths(paths): @@ -166,27 +186,78 @@ def test_project_generation(cookies, context, context_override): @pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id) -def test_flake8_passes(cookies, context_override): - """Generated project should pass flake8.""" +def test_ruff_check_passes(cookies, context_override): + """Generated project should pass ruff check.""" result = cookies.bake(extra_context=context_override) try: - sh.flake8(_cwd=str(result.project_path)) + sh.ruff("check", ".", _cwd=str(result.project_path)) + except sh.ErrorReturnCode as e: + pytest.fail(e.stdout.decode()) + + +@auto_fixable +@pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id) +def test_ruff_format_passes(cookies, context_override): + """Check whether generated project passes ruff format.""" + result = cookies.bake(extra_context=context_override) + + try: + sh.ruff( + "format", + ".", + _cwd=str(result.project_path), + ) + except sh.ErrorReturnCode as e: + pytest.fail(e.stdout.decode()) + + +@auto_fixable +@pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id) +def test_isort_passes(cookies, context_override): + """Check whether generated project passes isort style.""" + result = cookies.bake(extra_context=context_override) + + try: + sh.isort(_cwd=str(result.project_path)) + except sh.ErrorReturnCode as e: + pytest.fail(e.stdout.decode()) + + +@auto_fixable +@pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id) +def test_django_upgrade_passes(cookies, context_override): + """Check whether generated project passes django-upgrade.""" + result = cookies.bake(extra_context=context_override) + + python_files = [ + file_path.removeprefix(f"{result.project_path}/") + for file_path in glob.glob(str(result.project_path / "**" / "*.py"), recursive=True) + ] + try: + sh.django_upgrade( + "--target-version", + "4.2", + *python_files, + _cwd=str(result.project_path), + ) except sh.ErrorReturnCode as e: pytest.fail(e.stdout.decode()) @pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id) -def test_black_passes(cookies, context_override): - """Generated project should pass black.""" +def test_djlint_lint_passes(cookies, context_override): + """Check whether generated project passes djLint --lint.""" result = cookies.bake(extra_context=context_override) + autofixable_rules = "H014,T001" + # TODO: remove T002 when fixed https://github.com/Riverside-Healthcare/djLint/issues/687 + ignored_rules = "H006,H030,H031,T002" try: - sh.black( - "--check", - "--diff", - "--exclude", - "migrations", + sh.djlint( + "--lint", + "--ignore", + f"{autofixable_rules},{ignored_rules}", ".", _cwd=str(result.project_path), ) @@ -194,11 +265,23 @@ def test_black_passes(cookies, context_override): pytest.fail(e.stdout.decode()) +@auto_fixable +@pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id) +def test_djlint_check_passes(cookies, context_override): + """Check whether generated project passes djLint --check.""" + result = cookies.bake(extra_context=context_override) + + try: + sh.djlint("--check", ".", _cwd=str(result.project_path)) + except sh.ErrorReturnCode as e: + pytest.fail(e.stdout.decode()) + + @pytest.mark.parametrize( ["use_docker", "expected_test_script"], [ ("n", "pytest"), - ("y", "docker-compose -f local.yml run django pytest"), + ("y", "docker compose -f docker-compose.local.yml run django pytest"), ], ) def test_travis_invokes_pytest(cookies, context, use_docker, expected_test_script): @@ -213,7 +296,7 @@ def test_travis_invokes_pytest(cookies, context, use_docker, expected_test_scrip with open(f"{result.project_path}/.travis.yml") as travis_yml: try: yml = yaml.safe_load(travis_yml)["jobs"]["include"] - assert yml[0]["script"] == ["flake8"] + assert yml[0]["script"] == ["ruff check ."] assert yml[1]["script"] == [expected_test_script] except yaml.YAMLError as e: pytest.fail(str(e)) @@ -223,12 +306,10 @@ def test_travis_invokes_pytest(cookies, context, use_docker, expected_test_scrip ["use_docker", "expected_test_script"], [ ("n", "pytest"), - ("y", "docker-compose -f local.yml run django pytest"), + ("y", "docker compose -f docker-compose.local.yml run django pytest"), ], ) -def test_gitlab_invokes_flake8_and_pytest( - cookies, context, use_docker, expected_test_script -): +def test_gitlab_invokes_precommit_and_pytest(cookies, context, use_docker, expected_test_script): context.update({"ci_tool": "Gitlab", "use_docker": use_docker}) result = cookies.bake(extra_context=context) @@ -240,7 +321,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) @@ -250,12 +333,10 @@ def test_gitlab_invokes_flake8_and_pytest( ["use_docker", "expected_test_script"], [ ("n", "pytest"), - ("y", "docker-compose -f local.yml run django pytest"), + ("y", "docker compose -f docker-compose.local.yml run django pytest"), ], ) -def test_github_invokes_linter_and_pytest( - cookies, context, use_docker, expected_test_script -): +def test_github_invokes_linter_and_pytest(cookies, context, use_docker, expected_test_script): context.update({"ci_tool": "Github", "use_docker": use_docker}) result = cookies.bake(extra_context=context) @@ -304,17 +385,37 @@ def test_error_if_incompatible(cookies, context, invalid_context): @pytest.mark.parametrize( - ["use_pycharm", "pycharm_docs_exist"], + ["editor", "pycharm_docs_exist"], [ - ("n", False), - ("y", True), + ("None", False), + ("PyCharm", True), + ("VS Code", False), ], ) -def test_pycharm_docs_removed(cookies, context, use_pycharm, pycharm_docs_exist): - """.""" - context.update({"use_pycharm": use_pycharm}) +def test_pycharm_docs_removed(cookies, context, editor, pycharm_docs_exist): + context.update({"editor": editor}) result = cookies.bake(extra_context=context) with open(f"{result.project_path}/docs/index.rst") as f: has_pycharm_docs = "pycharm/configuration" in f.read() assert has_pycharm_docs is pycharm_docs_exist + + +def test_trim_domain_email(cookies, context): + """Check that leading and trailing spaces are trimmed in domain and email.""" + context.update( + { + "use_docker": "y", + "domain_name": " example.com ", + "email": " me@example.com ", + } + ) + result = cookies.bake(extra_context=context) + + assert result.exit_code == 0 + + prod_django_env = result.project_path / ".envs" / ".production" / ".django" + assert "DJANGO_ALLOWED_HOSTS=.example.com" in prod_django_env.read_text() + + base_settings = result.project_path / "config" / "settings" / "base.py" + assert '"me@example.com"' in base_settings.read_text() diff --git a/tests/test_docker.sh b/tests/test_docker.sh index b3663bd2c..473eede04 100755 --- a/tests/test_docker.sh +++ b/tests/test_docker.sh @@ -14,30 +14,39 @@ cd .cache/docker cookiecutter ../../ --no-input --overwrite-if-exists use_docker=y "$@" cd my_awesome_project -# Lint by running pre-commit on all files -# Needs a git repo to find the project root -# We don't have git inside Docker, so run it outside -git init -git add . -pre-commit run --show-diff-on-failure -a - # make sure all images build -docker-compose -f local.yml build +docker compose -f docker-compose.local.yml build # run the project's type checks -docker-compose -f local.yml run django mypy my_awesome_project +docker compose -f docker-compose.local.yml run django mypy my_awesome_project # run the project's tests -docker-compose -f local.yml run django pytest +docker compose -f docker-compose.local.yml run django pytest # return non-zero status code if there are migrations that have not been created -docker-compose -f local.yml run django python manage.py makemigrations --dry-run --check || { echo "ERROR: there were changes in the models, but migration listed above have not been created and are not saved in version control"; exit 1; } +docker compose -f docker-compose.local.yml run django python manage.py makemigrations --dry-run --check || { echo "ERROR: there were changes in the models, but migration listed above have not been created and are not saved in version control"; exit 1; } # Test support for translations -docker-compose -f local.yml run django python manage.py makemessages --all +docker compose -f docker-compose.local.yml run django python manage.py makemessages --all # Make sure the check doesn't raise any warnings -docker-compose -f local.yml run django python manage.py check --fail-level WARNING +docker compose -f docker-compose.local.yml run \ + -e DJANGO_SECRET_KEY="$(openssl rand -base64 64)" \ + -e REDIS_URL=redis://redis:6379/0 \ + -e CELERY_BROKER_URL=redis://redis:6379/0 \ + -e DJANGO_AWS_ACCESS_KEY_ID=x \ + -e DJANGO_AWS_SECRET_ACCESS_KEY=x \ + -e DJANGO_AWS_STORAGE_BUCKET_NAME=x \ + -e DJANGO_ADMIN_URL=x \ + -e MAILGUN_API_KEY=x \ + -e MAILGUN_DOMAIN=x \ + django python manage.py check --settings=config.settings.production --deploy --database default --fail-level WARNING # Generate the HTML for the documentation -docker-compose -f local.yml run docs make html +docker compose -f docker-compose.docs.yml run docs make html + +# Run npm build script if package.json is present +if [ -f "package.json" ] +then + docker compose -f docker-compose.local.yml run node npm run build +fi diff --git a/tests/test_hooks.py b/tests/test_hooks.py index 7ca752722..2ccac84b2 100644 --- a/tests/test_hooks.py +++ b/tests/test_hooks.py @@ -1,4 +1,5 @@ """Unit tests for the hooks""" + import os from pathlib import Path @@ -22,7 +23,5 @@ def test_append_to_gitignore_file(working_directory): gitignore_file.write_text("node_modules/\n") append_to_gitignore_file(".envs/*") linesep = os.linesep.encode() - assert ( - gitignore_file.read_bytes() == b"node_modules/" + linesep + b".envs/*" + linesep - ) + assert gitignore_file.read_bytes() == b"node_modules/" + linesep + b".envs/*" + linesep assert gitignore_file.read_text() == "node_modules/\n.envs/*\n" diff --git a/tox.ini b/tox.ini index 0400e4f91..3b7a95088 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,11 @@ [tox] skipsdist = true -envlist = py310,black-template +envlist = py312,black-template [testenv] deps = -rrequirements.txt -commands = pytest {posargs:./tests} +passenv = AUTOFIXABLE_STYLES +commands = pytest -n auto {posargs:./tests} [testenv:black-template] deps = black diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/__init__.py b/{{cookiecutter.project_slug}}/.devcontainer/bash_history similarity index 100% rename from {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/__init__.py rename to {{cookiecutter.project_slug}}/.devcontainer/bash_history diff --git a/{{cookiecutter.project_slug}}/.devcontainer/bashrc.override.sh b/{{cookiecutter.project_slug}}/.devcontainer/bashrc.override.sh new file mode 100644 index 000000000..bedddf64b --- /dev/null +++ b/{{cookiecutter.project_slug}}/.devcontainer/bashrc.override.sh @@ -0,0 +1,20 @@ + +# +# .bashrc.override.sh +# + +# persistent bash history +HISTFILE=~/.bash_history +PROMPT_COMMAND="history -a; $PROMPT_COMMAND" + +# set some django env vars +source /entrypoint + +# restore default shell options +set +o errexit +set +o pipefail +set +o nounset + +# start ssh-agent +# https://code.visualstudio.com/docs/remote/troubleshooting +eval "$(ssh-agent -s)" diff --git a/{{cookiecutter.project_slug}}/.devcontainer/devcontainer.json b/{{cookiecutter.project_slug}}/.devcontainer/devcontainer.json new file mode 100644 index 000000000..5604b8a85 --- /dev/null +++ b/{{cookiecutter.project_slug}}/.devcontainer/devcontainer.json @@ -0,0 +1,70 @@ +// For format details, see https://containers.dev/implementors/json_reference/ +{ + "name": "{{cookiecutter.project_slug}}_dev", + "dockerComposeFile": [ + "../docker-compose.local.yml" + ], + "init": true, + "mounts": [ + { + "source": "./.devcontainer/bash_history", + "target": "/home/dev-user/.bash_history", + "type": "bind" + }, + { + "source": "~/.ssh", + "target": "/home/dev-user/.ssh", + "type": "bind" + } + ], + // Tells devcontainer.json supporting services / tools whether they should run + // /bin/sh -c "while sleep 1000; do :; done" when starting the container instead of the container’s default command + "overrideCommand": false, + "service": "django", + // "remoteEnv": {"PATH": "/home/dev-user/.local/bin:${containerEnv:PATH}"}, + "remoteUser": "dev-user", + "workspaceFolder": "/app", + // Set *default* container specific settings.json values on container create. + "customizations": { + {%- if cookiecutter.editor == "VS Code" %} + "vscode": { + "settings": { + "editor.formatOnSave": true, + "[python]": { + "analysis.autoImportCompletions": true, + "analysis.typeCheckingMode": "basic", + "defaultInterpreterPath": "/usr/local/bin/python", + "editor.codeActionsOnSave": { + "source.organizeImports": "always" + }, + "editor.defaultFormatter": "charliermarsh.ruff", + "languageServer": "Pylance", + "linting.enabled": true, + "linting.mypyEnabled": true, + "linting.mypyPath": "/usr/local/bin/mypy", + } + }, + // https://code.visualstudio.com/docs/remote/devcontainerjson-reference#_vs-code-specific-properties + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "davidanson.vscode-markdownlint", + "mrmlnc.vscode-duplicate", + "visualstudioexptteam.vscodeintellicode", + "visualstudioexptteam.intellicode-api-usage-examples", + // python + "ms-python.python", + "ms-python.vscode-pylance", + "charliermarsh.ruff", + // django + "batisteo.vscode-django" + ] + } + {%- endif %} + }, + // Uncomment the next line if you want start specific services in your Docker Compose config. + // "runServices": [], + // Uncomment the next line if you want to keep your containers running after VS Code shuts down. + // "shutdownAction": "none", + // Uncomment the next line to run commands after the container is created. + "postCreateCommand": "cat .devcontainer/bashrc.override.sh >> ~/.bashrc" +} diff --git a/{{cookiecutter.project_slug}}/.dockerignore b/{{cookiecutter.project_slug}}/.dockerignore index 5518e60af..a602416cd 100644 --- a/{{cookiecutter.project_slug}}/.dockerignore +++ b/{{cookiecutter.project_slug}}/.dockerignore @@ -8,3 +8,5 @@ .readthedocs.yml .travis.yml venv +.git +.envs/ diff --git a/{{cookiecutter.project_slug}}/.drone.yml b/{{cookiecutter.project_slug}}/.drone.yml new file mode 100644 index 000000000..d6c13e62b --- /dev/null +++ b/{{cookiecutter.project_slug}}/.drone.yml @@ -0,0 +1,49 @@ +kind: pipeline +name: default + +environment: + POSTGRES_USER: '{{ cookiecutter.project_slug }}' + POSTGRES_PASSWORD: '' + POSTGRES_DB: 'test_{{ cookiecutter.project_slug }}' + POSTGRES_HOST_AUTH_METHOD: trust + {%- if cookiecutter.use_celery == 'y' %} + CELERY_BROKER_URL: 'redis://redis:6379/0' + {%- endif %} + +steps: +- name: lint + pull: if-not-exists + image: python:3.12 + environment: + PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit + volumes: + - name: pre-commit cache + path: ${PRE_COMMIT_HOME} + commands: + - export PRE_COMMIT_HOME=$CI_PROJECT_DIR/.cache/pre-commit + - pip install -q pre-commit + - pre-commit run --show-diff-on-failure --color=always --all-files + +- name: test + pull: if-not-exists + {%- if cookiecutter.use_docker == 'y' %} + image: docker:25.0 + environment: + DATABASE_URL: pgsql://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres/$POSTGRES_DB + commands: + - docker-compose -f docker-compose.local.yml build + - docker-compose -f docker-compose.docs.yml build + - docker-compose -f docker-compose.local.yml run --rm django python manage.py migrate + - docker-compose -f docker-compose.local.yml up -d + - docker-compose -f docker-compose.local.yml run django pytest + {%- else %} + image: python:3.12 + commands: + - pip install -r requirements/local.txt + - pytest + {%- endif%} + +volumes: +- name: pre-commit cache + host: + path: /tmp/drone/cache/pre-commit diff --git a/{{cookiecutter.project_slug}}/.editorconfig b/{{cookiecutter.project_slug}}/.editorconfig index 6a9a5c45d..c0ce34260 100644 --- a/{{cookiecutter.project_slug}}/.editorconfig +++ b/{{cookiecutter.project_slug}}/.editorconfig @@ -22,6 +22,6 @@ trim_trailing_whitespace = false [Makefile] indent_style = tab -[nginx.conf] +[default.conf] indent_style = space indent_size = 2 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/dependabot.yml b/{{cookiecutter.project_slug}}/.github/dependabot.yml index 420a63cdc..be52c68d5 100644 --- a/{{cookiecutter.project_slug}}/.github/dependabot.yml +++ b/{{cookiecutter.project_slug}}/.github/dependabot.yml @@ -4,11 +4,11 @@ version: 2 updates: # Update GitHub actions in workflows - - package-ecosystem: "github-actions" - directory: "/" - # Check for updates to GitHub Actions every weekday + - package-ecosystem: 'github-actions' + directory: '/' + # Every weekday schedule: - interval: "daily" + interval: 'daily' {%- if cookiecutter.use_docker == 'y' %} @@ -16,80 +16,92 @@ updates: # 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" + - package-ecosystem: 'docker' # Look for a `Dockerfile` in the `compose/local/django` directory - directory: "compose/local/django/" - # Check for updates to GitHub Actions every weekday + directory: 'compose/local/django/' + # Every weekday schedule: - interval: "daily" + interval: 'daily' + # Ignore minor version updates (3.10 -> 3.11) but update patch versions + ignore: + - dependency-name: '*' + update-types: + - 'version-update:semver-major' + - 'version-update:semver-minor' - # Enable version updates for Docker - - package-ecosystem: "docker" + - package-ecosystem: 'docker' # Look for a `Dockerfile` in the `compose/local/docs` directory - directory: "compose/local/docs/" - # Check for updates to GitHub Actions every weekday + directory: 'compose/local/docs/' + # Every weekday schedule: - interval: "daily" + interval: 'daily' + # Ignore minor version updates (3.10 -> 3.11) but update patch versions + ignore: + - dependency-name: '*' + update-types: + - 'version-update:semver-major' + - 'version-update:semver-minor' - # Enable version updates for Docker - - package-ecosystem: "docker" + - package-ecosystem: 'docker' # Look for a `Dockerfile` in the `compose/local/node` directory - directory: "compose/local/node/" - # Check for updates to GitHub Actions every weekday + directory: 'compose/local/node/' + # Every weekday schedule: - interval: "daily" + interval: 'daily' - # Enable version updates for Docker - - package-ecosystem: "docker" + - package-ecosystem: 'docker' # Look for a `Dockerfile` in the `compose/production/aws` directory - directory: "compose/production/aws/" - # Check for updates to GitHub Actions every weekday + directory: 'compose/production/aws/' + # Every weekday schedule: - interval: "daily" + interval: 'daily' - # Enable version updates for Docker - - package-ecosystem: "docker" + - package-ecosystem: 'docker' # Look for a `Dockerfile` in the `compose/production/django` directory - directory: "compose/production/django/" - # Check for updates to GitHub Actions every weekday + directory: 'compose/production/django/' + # Every weekday schedule: - interval: "daily" + interval: 'daily' + # Ignore minor version updates (3.10 -> 3.11) but update patch versions + ignore: + - dependency-name: '*' + update-types: + - 'version-update:semver-major' + - 'version-update:semver-minor' - # Enable version updates for Docker - - package-ecosystem: "docker" + - package-ecosystem: 'docker' # Look for a `Dockerfile` in the `compose/production/postgres` directory - directory: "compose/production/postgres/" - # Check for updates to GitHub Actions every weekday + directory: 'compose/production/postgres/' + # Every weekday schedule: - interval: "daily" + interval: 'daily' - # Enable version updates for Docker - - package-ecosystem: "docker" + - package-ecosystem: 'docker' # Look for a `Dockerfile` in the `compose/production/traefik` directory - directory: "compose/production/traefik/" - # Check for updates to GitHub Actions every weekday + directory: 'compose/production/traefik/' + # Every weekday schedule: - interval: "daily" + interval: 'daily' {%- endif %} # Enable version updates for Python/Pip - Production - - package-ecosystem: "pip" + - package-ecosystem: 'pip' # Look for a `requirements.txt` in the `root` directory # also 'setup.cfg', 'runtime.txt' and 'requirements/*.txt' - directory: "/" - # Check for updates to GitHub Actions every weekday + directory: '/' + # Every weekday schedule: - interval: "daily" + interval: 'daily' {%- if cookiecutter.frontend_pipeline == 'Gulp' %} # Enable version updates for javascript/npm - - package-ecosystem: "npm" - # Look for a `packages.json' in the `root` directory - directory: "/" - # Check for updates to GitHub Actions every weekday + - package-ecosystem: 'npm' + # Look for a `packages.json` in the `root` directory + directory: '/' + # Every weekday schedule: - interval: "daily" + interval: 'daily' {%- endif %} diff --git a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml index 0790187bd..5cb9ead4f 100644 --- a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +++ b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml @@ -7,12 +7,12 @@ env: on: pull_request: - branches: [ "master", "main" ] - paths-ignore: [ "docs/**" ] + branches: ['master', 'main'] + paths-ignore: ['docs/**'] push: - branches: [ "master", "main" ] - paths-ignore: [ "docs/**" ] + branches: ['master', 'main'] + paths-ignore: ['docs/**'] concurrency: group: {% raw %}${{ github.head_ref || github.run_id }}{% endraw %} @@ -22,23 +22,21 @@ jobs: linter: runs-on: ubuntu-latest steps: - - name: Checkout Code Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: - python-version: "3.10" - cache: pip - cache-dependency-path: | - requirements/base.txt - requirements/local.txt + python-version: '3.12' + {%- 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.1 - # With no caching at all the entire ci process takes 4m 30s to complete! + # With no caching at all the entire ci process takes 3m to complete! pytest: runs-on: ubuntu-latest {%- if cookiecutter.use_docker == 'n' %} @@ -51,7 +49,7 @@ jobs: - 6379:6379 {%- endif %} postgres: - image: postgres:12 + image: postgres:{{ cookiecutter.postgresql_version }} ports: - 5432:5432 env: @@ -59,35 +57,37 @@ jobs: env: {%- if cookiecutter.use_celery == 'y' %} - CELERY_BROKER_URL: "redis://localhost:6379/0" + CELERY_BROKER_URL: 'redis://localhost:6379/0' {%- endif %} # postgres://user:password@host:port/database - DATABASE_URL: "postgres://postgres:postgres@localhost:5432/postgres" + DATABASE_URL: 'postgres://postgres:postgres@localhost:5432/postgres' {%- endif %} steps: - - name: Checkout Code Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 {%- if cookiecutter.use_docker == 'y' %} - name: Build the Stack - run: docker-compose -f local.yml build + run: docker compose -f docker-compose.local.yml build django + + - name: Build the docs + run: docker compose -f docker-compose.docs.yml build docs - name: Run DB Migrations - run: docker-compose -f local.yml run --rm django python manage.py migrate + run: docker compose -f docker-compose.local.yml run --rm django python manage.py migrate - name: Run Django Tests - run: docker-compose -f local.yml run django pytest + run: docker compose -f docker-compose.local.yml run django pytest - name: Tear down the Stack - run: docker-compose -f local.yml down + run: docker compose -f docker-compose.local.yml down {%- else %} - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: - python-version: "3.10" + python-version: '3.12' cache: pip cache-dependency-path: | requirements/base.txt @@ -99,5 +99,5 @@ jobs: pip install -r requirements/local.txt - name: Test with pytest - run: pytest + run: pytest {%- endif %} diff --git a/{{cookiecutter.project_slug}}/.gitignore b/{{cookiecutter.project_slug}}/.gitignore index ede26ef72..0bb322186 100644 --- a/{{cookiecutter.project_slug}}/.gitignore +++ b/{{cookiecutter.project_slug}}/.gitignore @@ -161,11 +161,10 @@ typings/ !.vscode/extensions.json *.code-workspace -# Local History for Visual Studio Code -.history/ +# Local History for devcontainer +.devcontainer/bash_history - -{% if cookiecutter.use_pycharm == 'y' -%} +{% if cookiecutter.editor == 'PyCharm' -%} # Provided default Pycharm Run/Debug Configurations should be tracked by git # In case of local modifications made by Pycharm, use update-index command # for each changed file, like this: @@ -326,9 +325,12 @@ 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 +{%- if cookiecutter.use_mailpit == 'y' and cookiecutter.use_docker == 'n' %} +mailpit {%- endif %} {{ cookiecutter.project_slug }}/media/ @@ -343,4 +345,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..41eea0db4 100644 --- a/{{cookiecutter.project_slug}}/.gitlab-ci.yml +++ b/{{cookiecutter.project_slug}}/.gitlab-ci.yml @@ -7,46 +7,49 @@ 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.12 + 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' -%} - image: docker/compose:1.29.2 + {%- if cookiecutter.use_docker == 'y' %} + image: docker:25.0 tags: - docker services: - docker:dind before_script: - - docker-compose -f local.yml build + - docker compose -f docker-compose.local.yml build + - docker compose -f docker-compose.docs.yml build # Ensure celerybeat does not crash due to non-existent tables - - docker-compose -f local.yml run --rm django python manage.py migrate - - docker-compose -f local.yml up -d + - docker compose -f docker-compose.local.yml run --rm django python manage.py migrate + - docker compose -f docker-compose.local.yml up -d script: - - docker-compose -f local.yml run django pytest - {%- else -%} - image: python:3.10 + - docker compose -f docker-compose.local.yml run django pytest + {%- else %} + image: python:3.12 tags: - python services: - 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 @@

Forbidden (403)

- -

{% if exception %}{{ exception }}{% else %}You're not allowed to access this page.{% endif %}

+

Forbidden (403)

+

+ {% if exception %} + {{ exception }} + {% else %} + You're not allowed to access this page. + {% endif %} +

{% endblock content %} {%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/403_csrf.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/403_csrf.html new file mode 100644 index 000000000..d90b33f9b --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/403_csrf.html @@ -0,0 +1,14 @@ +{% raw %}{% extends "base.html" %} + +{% block title %}Forbidden (403){% endblock title %} +{% block content %} +

Forbidden (403)

+

+ {% if exception %} + {{ exception }} + {% else %} + You're not allowed to access this page. + {% endif %} +

+{% endblock content %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/404.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/404.html index d98241858..621596412 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/404.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/404.html @@ -1,10 +1,14 @@ {% raw %}{% extends "base.html" %} -{% block title %}Page not found{% endblock %} - +{% block title %}Page not found{% endblock title %} {% block content %} -

Page not found

- -

{% if exception %}{{ exception }}{% else %}This is not the page you were looking for.{% endif %}

+

Page not found

+

+ {% if exception %} + {{ exception }} + {% else %} + This is not the page you were looking for. + {% endif %} +

{% endblock content %} {%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/500.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/500.html index 481bb2d0b..890320164 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/500.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/500.html @@ -1,12 +1,11 @@ {% raw %}{% extends "base.html" %} -{% block title %}Server Error{% endblock %} - +{% block title %}Server Error{% endblock title %} {% block content %} -

Ooops!!! 500

- -

Looks like something went wrong!

- -

We track these errors automatically, but if the problem persists feel free to contact us. In the meantime, try refreshing.

+

Ooops!!! 500

+

Looks like something went wrong!

+

+ We track these errors automatically, but if the problem persists feel free to contact us. In the meantime, try refreshing. +

{% endblock content %} {%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/account_inactive.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/account_inactive.html deleted file mode 100644 index ab910820e..000000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/account_inactive.html +++ /dev/null @@ -1,12 +0,0 @@ -{% raw %}{% extends "account/base.html" %} - -{% load i18n %} - -{% block head_title %}{% translate "Account Inactive" %}{% endblock %} - -{% block inner %} -

{% translate "Account Inactive" %}

- -

{% translate "This account is inactive." %}

-{% endblock %} -{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/base.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/base.html deleted file mode 100644 index 03c86724b..000000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/base.html +++ /dev/null @@ -1,11 +0,0 @@ -{% raw %}{% extends "base.html" %} -{% block title %}{% block head_title %}{% endblock head_title %}{% endblock title %} - -{% block content %} -
-
- {% block inner %}{% endblock %} -
-
-{% endblock %} -{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/base_manage_password.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/base_manage_password.html new file mode 100644 index 000000000..515f5244a --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/base_manage_password.html @@ -0,0 +1,11 @@ +{% raw %}{% extends "account/base_manage.html" %} + +{% block main %} +
+
+ {% block content %} + {% endblock content %} +
+
+{% endblock main %}{% endraw %} + diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email.html deleted file mode 100644 index 1faa2b9fd..000000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email.html +++ /dev/null @@ -1,79 +0,0 @@ -{% raw %} -{% extends "account/base.html" %} - -{% load i18n %} -{% load crispy_forms_tags %} - -{% block head_title %}{% translate "Account" %}{% endblock %} - -{% block inner %} -

{% translate "E-mail Addresses" %}

- -{% if user.emailaddress_set.all %} -

{% translate 'The following e-mail addresses are associated with your account:' %}

- - - -{% else %} -

{% translate 'Warning:'%} {% translate "You currently do not have any e-mail address set up. You should really add an e-mail address so you can receive notifications, reset your password, etc." %}

- -{% endif %} - - -

{% translate "Add E-mail Address" %}

- -
- {% csrf_token %} - {{ form|crispy }} - -
- -{% endblock %} - - -{% block inline_javascript %} -{{ block.super }} - -{% endblock %} -{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email_confirm.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email_confirm.html deleted file mode 100644 index 5e4924c83..000000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email_confirm.html +++ /dev/null @@ -1,32 +0,0 @@ -{% raw %}{% extends "account/base.html" %} - -{% load i18n %} -{% load account %} - -{% block head_title %}{% translate "Confirm E-mail Address" %}{% endblock %} - - -{% block inner %} -

{% translate "Confirm E-mail Address" %}

- -{% if confirmation %} - -{% user_display confirmation.email_address.user as user_display %} - -

{% blocktranslate with confirmation.email_address.email as email %}Please confirm that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktranslate %}

- -
-{% csrf_token %} - -
- -{% else %} - -{% url 'account_email' as email_url %} - -

{% blocktranslate %}This e-mail confirmation link expired or is invalid. Please issue a new e-mail confirmation request.{% endblocktranslate %}

- -{% endif %} - -{% endblock %} -{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/login.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/login.html deleted file mode 100644 index 25a292eda..000000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/login.html +++ /dev/null @@ -1,60 +0,0 @@ -{% raw %}{% extends "account/base.html" %} - -{% load i18n %} -{% load account socialaccount %} -{% load crispy_forms_tags %} - -{% block head_title %}{% translate "Sign In" %}{% endblock %} - -{% block inner %} - -

{% translate "Sign In" %}

- -{% get_providers as socialaccount_providers %} - -{% if socialaccount_providers %} -

- {% translate "Please sign in with one of your existing third party accounts:" %} - {% if ACCOUNT_ALLOW_REGISTRATION %} - {% blocktranslate trimmed %} - Or, sign up - for a {{ site_name }} account and sign in below: - {% endblocktranslate %} - {% endif %} -

- -
- -
    - {% include "socialaccount/snippets/provider_list.html" with process="login" %} -
- - - -
- - {% include "socialaccount/snippets/login_extra.html" %} - -{% else %} - {% if ACCOUNT_ALLOW_REGISTRATION %} -

- {% blocktranslate trimmed %} - If you have not created an account yet, then please - sign up first. - {% endblocktranslate %} -

- {% endif %} -{% endif %} - - - -{% endblock %} -{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/logout.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/logout.html deleted file mode 100644 index 5edc60478..000000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/logout.html +++ /dev/null @@ -1,20 +0,0 @@ -{% raw %}{% extends "account/base.html" %} - -{% load i18n %} - -{% block head_title %}{% translate "Sign Out" %}{% endblock %} - -{% block inner %} -

{% translate "Sign Out" %}

- -

{% translate 'Are you sure you want to sign out?' %}

- -
- {% csrf_token %} - {% if redirect_field_value %} - - {% endif %} - -
-{% endblock %} -{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_change.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_change.html deleted file mode 100644 index b8dd7ac53..000000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_change.html +++ /dev/null @@ -1,17 +0,0 @@ -{% raw %}{% extends "account/base.html" %} - -{% load i18n %} -{% load crispy_forms_tags %} - -{% block head_title %}{% translate "Change Password" %}{% endblock %} - -{% block inner %} -

{% translate "Change Password" %}

- -
- {% csrf_token %} - {{ form|crispy }} - -
-{% endblock %} -{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset.html deleted file mode 100644 index f424b2111..000000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset.html +++ /dev/null @@ -1,26 +0,0 @@ -{% raw %}{% extends "account/base.html" %} - -{% load i18n %} -{% load account %} -{% load crispy_forms_tags %} - -{% block head_title %}{% translate "Password Reset" %}{% endblock %} - -{% block inner %} - -

{% translate "Password Reset" %}

- {% if user.is_authenticated %} - {% include "account/snippets/already_logged_in.html" %} - {% endif %} - -

{% translate "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}

- -
- {% csrf_token %} - {{ form|crispy }} - -
- -

{% blocktranslate %}Please contact us if you have any trouble resetting your password.{% endblocktranslate %}

-{% endblock %} -{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_done.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_done.html deleted file mode 100644 index 76d07eb21..000000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_done.html +++ /dev/null @@ -1,17 +0,0 @@ -{% raw %}{% extends "account/base.html" %} - -{% load i18n %} -{% load account %} - -{% block head_title %}{% translate "Password Reset" %}{% endblock %} - -{% block inner %} -

{% translate "Password Reset" %}

- - {% if user.is_authenticated %} - {% include "account/snippets/already_logged_in.html" %} - {% endif %} - -

{% blocktranslate %}We have sent you an e-mail. Please contact us if you do not receive it within a few minutes.{% endblocktranslate %}

-{% endblock %} -{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key.html deleted file mode 100644 index ce5d72a6d..000000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key.html +++ /dev/null @@ -1,25 +0,0 @@ -{% raw %}{% extends "account/base.html" %} - -{% load i18n %} -{% load crispy_forms_tags %} -{% block head_title %}{% translate "Change Password" %}{% endblock %} - -{% block inner %} -

{% if token_fail %}{% translate "Bad Token" %}{% else %}{% translate "Change Password" %}{% endif %}

- - {% if token_fail %} - {% url 'account_reset_password' as passwd_reset_url %} -

{% blocktranslate %}The password reset link was invalid, possibly because it has already been used. Please request a new password reset.{% endblocktranslate %}

- {% else %} - {% if form %} -
- {% csrf_token %} - {{ form|crispy }} - -
- {% else %} -

{% translate 'Your password is now changed.' %}

- {% endif %} - {% endif %} -{% endblock %} -{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key_done.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key_done.html deleted file mode 100644 index 34123fd53..000000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key_done.html +++ /dev/null @@ -1,10 +0,0 @@ -{% raw %}{% extends "account/base.html" %} - -{% load i18n %} -{% block head_title %}{% translate "Change Password" %}{% endblock %} - -{% block inner %} -

{% translate "Change Password" %}

-

{% translate 'Your password is now changed.' %}

-{% endblock %} -{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_set.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_set.html deleted file mode 100644 index 812410fc0..000000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_set.html +++ /dev/null @@ -1,17 +0,0 @@ -{% raw %}{% extends "account/base.html" %} - -{% load i18n %} -{% load crispy_forms_tags %} - -{% block head_title %}{% translate "Set Password" %}{% endblock %} - -{% block inner %} -

{% translate "Set Password" %}

- -
- {% csrf_token %} - {{ form|crispy }} - -
-{% endblock %} -{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup.html deleted file mode 100644 index 8c1c11aca..000000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup.html +++ /dev/null @@ -1,23 +0,0 @@ -{% raw %}{% extends "account/base.html" %} - -{% load i18n %} -{% load crispy_forms_tags %} - -{% block head_title %}{% translate "Signup" %}{% endblock %} - -{% block inner %} -

{% translate "Sign Up" %}

- -

{% blocktranslate %}Already have an account? Then please sign in.{% endblocktranslate %}

- - - -{% endblock %} -{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup_closed.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup_closed.html deleted file mode 100644 index c2e64d14f..000000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup_closed.html +++ /dev/null @@ -1,12 +0,0 @@ -{% raw %}{% extends "account/base.html" %} - -{% load i18n %} - -{% block head_title %}{% translate "Sign Up Closed" %}{% endblock %} - -{% block inner %} -

{% translate "Sign Up Closed" %}

- -

{% translate "We are sorry, but the sign up is currently closed." %}

-{% endblock %} -{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verification_sent.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verification_sent.html deleted file mode 100644 index be8f1cef9..000000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verification_sent.html +++ /dev/null @@ -1,13 +0,0 @@ -{% raw %}{% extends "account/base.html" %} - -{% load i18n %} - -{% block head_title %}{% translate "Verify Your E-mail Address" %}{% endblock %} - -{% block inner %} -

{% translate "Verify Your E-mail Address" %}

- -

{% blocktranslate %}We have sent an e-mail to you for verification. Follow the link provided to finalize the signup process. Please contact us if you do not receive it within a few minutes.{% endblocktranslate %}

- -{% endblock %} -{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verified_email_required.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verified_email_required.html deleted file mode 100644 index 2148a1804..000000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verified_email_required.html +++ /dev/null @@ -1,22 +0,0 @@ -{% raw %}{% extends "account/base.html" %} - -{% load i18n %} - -{% block head_title %}{% translate "Verify Your E-mail Address" %}{% endblock %} - -{% block inner %} -

{% translate "Verify Your E-mail Address" %}

- -{% url 'account_email' as email_url %} - -

{% blocktranslate %}This part of the site requires us to verify that -you are who you claim to be. For this purpose, we require that you -verify ownership of your e-mail address. {% endblocktranslate %}

- -

{% blocktranslate %}We have sent an e-mail to you for -verification. Please click on the link inside this e-mail. Please -contact us if you do not receive it within a few minutes.{% endblocktranslate %}

- -

{% blocktranslate %}Note: you can still change your e-mail address.{% endblocktranslate %}

-{% endblock %} -{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/elements/alert.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/elements/alert.html new file mode 100644 index 000000000..090c2cbf6 --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/elements/alert.html @@ -0,0 +1,7 @@ +{% raw %}{% load i18n %} +{% load allauth %} + +
+ {% slot message %} +{% endslot %} +
{% endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/elements/badge.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/elements/badge.html new file mode 100644 index 000000000..7093939da --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/elements/badge.html @@ -0,0 +1,6 @@ +{% raw %}{% load allauth %} + + + {% slot %} +{% endslot %} +{% endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/elements/button.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/elements/button.html new file mode 100644 index 000000000..1c0ae4b99 --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/elements/button.html @@ -0,0 +1,20 @@ +{% raw %}{% load allauth %} + +{% comment %} djlint:off {% endcomment %} +<{% if attrs.href %}a href="{{ attrs.href }}"{% else %}button{% endif %} + {% if attrs.form %}form="{{ attrs.form }}"{% endif %} + {% if attrs.id %}id="{{ attrs.id }}"{% endif %} + {% if attrs.name %}name="{{ attrs.name }}"{% endif %} + {% if attrs.type %}type="{{ attrs.type }}"{% endif %} + class="btn +{% if 'success' in attrs.tags %}btn-success +{% elif 'warning' in attrs.tags %}btn-warning +{% elif 'secondary' in attrs.tags %}btn-secondary +{% elif 'danger' in attrs.tags %}btn-danger +{% elif 'primary' in attrs.tags %}btn-primary +{% else %}btn-primary +{% endif %}" +> + {% slot %} + {% endslot %} + {% endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/elements/field.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/elements/field.html new file mode 100644 index 000000000..8585f7a4d --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/elements/field.html @@ -0,0 +1,66 @@ +{% raw %}{% load allauth %} +{% load crispy_forms_tags %} + +{% if attrs.type == "textarea" %} +
+
+ +
+ +
+{% elif attrs.type == "radio" %} +
+
+
+ + +
+
+
+{% else %} +
+ +
+
+ +
+{% endif %} +{% if slots.help_text %} +
{% slot help_text %}{% endslot %}
+{% endif %}{% endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/elements/fields.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/elements/fields.html new file mode 100644 index 000000000..6a2f3c2cb --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/elements/fields.html @@ -0,0 +1,3 @@ +{% raw %}{% load crispy_forms_tags %} + +{{ attrs.form|crispy }}{% endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/elements/panel.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/elements/panel.html new file mode 100644 index 000000000..ce179b587 --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/elements/panel.html @@ -0,0 +1,19 @@ +{% raw %}{% load allauth %} + +
+
+
+

+ {% slot title %} + {% endslot %} +

+ {% slot body %} + {% endslot %} + {% if slots.actions %} +
    + {% for action in slots.actions %}
  • {{ action }}
  • {% endfor %} +
+ {% endif %} +
+
+
{% endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/elements/table.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/elements/table.html new file mode 100644 index 000000000..7ceb9234d --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/elements/table.html @@ -0,0 +1,6 @@ +{% raw %}{% load allauth %} + + + {% slot %} +{% endslot %} +
{% endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/layouts/entrance.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/layouts/entrance.html new file mode 100644 index 000000000..91cbaba3f --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/layouts/entrance.html @@ -0,0 +1,29 @@ +{% raw %}{% extends "base.html" %} +{% load i18n %} +{% block bodyclass %}bg-light{% endblock bodyclass %} + +{% block css %}{{ block.super }}{% endblock css %} +{% block title %} + {% block head_title %} + {% trans "Sign In" %} + {% endblock head_title %} +{% endblock title %} +{% block body %} +
+
+ {% if messages %} + {% for message in messages %} +
+ {{ message }} + +
+ {% endfor %} + {% endif %} + {% block content %} + {% endblock content %} +
+
+{% endblock body %}{% endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/layouts/manage.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/layouts/manage.html new file mode 100644 index 000000000..b7ff36ba2 --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/allauth/layouts/manage.html @@ -0,0 +1,6 @@ +{% raw %}{% extends "base.html" %} + +{% block main %} + {% block content %} + {% endblock content %} +{% endblock main %}{% endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html index 58aca7208..ec873c801 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html @@ -1,140 +1,199 @@ -{% 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 %} - - - {% block title %}{% endraw %}{{ cookiecutter.project_name }}{% raw %}{% endblock title %} - - - - - - - {% block css %} - {%- endraw %} - {%- if cookiecutter.frontend_pipeline != 'Gulp' %} + + + + {% block title %} + {% endraw %}{{ cookiecutter.project_name }}{% raw %} + {% endblock title %} + + + + + + {% block css %} + {%- endraw %} + {%- if cookiecutter.frontend_pipeline in ['None', 'Django Compressor'] %} {%- raw %} - - {%- endraw %} - {%- endif %} - {%- raw %} - - - - {%- endraw %}{% if cookiecutter.frontend_pipeline == 'None' %}{% raw %} - - {%- endraw %}{% elif cookiecutter.frontend_pipeline == 'Django Compressor' %}{% raw %} - {% compress css %} - - {% endcompress %} - {%- endraw %}{% elif cookiecutter.frontend_pipeline == 'Gulp' %}{% raw %} - - {%- endraw %}{% endif %}{% raw %} - {% endblock %} - + +{%- endraw %} +{% if cookiecutter.frontend_pipeline == 'None' %} + {% raw %} + +{%- endraw %} +{% elif cookiecutter.frontend_pipeline == 'Django Compressor' %} +{% raw %} +{% compress css %} + +{% endcompress %} +{%- endraw %} +{% elif cookiecutter.frontend_pipeline == 'Gulp' %} +{% raw %} + +{%- endraw %} +{% elif cookiecutter.frontend_pipeline == "Webpack" %} +{% raw %} +{% render_bundle 'project' 'css' %} +{%- endraw %} +{% endif %} +{% raw %} +{% endblock css %} + - {# Placed at the top of the document so pages load faster with defer #} - {% block javascript %} - {%- endraw %}{% if cookiecutter.frontend_pipeline == 'Gulp' %}{% raw %} - - - {%- endraw %}{% else %}{% raw %} - - - - {%- endraw %}{% endif %}{% raw %} - - - {%- endraw %}{% if cookiecutter.frontend_pipeline == 'None' %}{% raw %} - - {%- endraw %}{% elif cookiecutter.frontend_pipeline == 'Django Compressor' %}{% raw %} - {% compress js %} - - {% endcompress %} - {%- endraw %}{% elif cookiecutter.frontend_pipeline == 'Gulp' %}{% raw %} - - {%- endraw %}{% endif %}{% raw %} - - {% endblock javascript %} - - - - - -
- - -
- -
- - {% if messages %} - {% for message in messages %} -
- {{ message }} - -
- {% endfor %} - {% endif %} - +
+ + +
+ {% if messages %} + {% for message in messages %} +
+ {{ message }} + +
+ {% endfor %} + {% endif %} + {% block main %} {% block content %}

Use this document as a way to quick start any new project.

{% endblock content %} + {% endblock main %} -
- - {% block modal %}{% endblock modal %} - - {% block inline_javascript %} + + {% endblock body %} + + {% block modal %} + {% endblock modal %} + {% block inline_javascript %} {% comment %} Script tags with only code, no src (defer by default). To run with a "defer" so that you run inline code: {% endcomment %} - {% endblock inline_javascript %} - + {% endblock inline_javascript %} + {%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/about.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/about.html index 8968a3d4f..3d301eead 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/about.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/about.html @@ -1 +1,3 @@ -{% raw %}{% extends "base.html" %}{% endraw %} +{% raw %}{% extends "base.html" %} + +{% endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/home.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/home.html index 8968a3d4f..3d301eead 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/home.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/home.html @@ -1 +1,3 @@ -{% raw %}{% extends "base.html" %}{% endraw %} +{% raw %}{% extends "base.html" %} + +{% endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_detail.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_detail.html index eed39ca3a..2dc7bfdc7 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_detail.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_detail.html @@ -1,35 +1,54 @@ {% raw %}{% extends "base.html" %} + {% load static %} -{% block title %}User: {{ object.username }}{% endblock %} - +{% block title %} + User: {% endraw %} + {% if cookiecutter.username_type == "email" %} + {% raw %}{{ object.name }}{% endraw %} + {% else %} + {% raw %}{{ object.username }}{% endraw %} + {% endif %} + {% raw %} +{% endblock title %} {% block content %} -
- -
-
- -

{{ object.username }}

+
+
+
+

+ {% endraw %} + {% if cookiecutter.username_type == "email" %} + {% raw %}{{ object.name }}{% endraw %} + {% else %} + {% raw %}{{ object.username }}{% endraw %} + {% endif %} +

+ {%- if cookiecutter.username_type == "username" %} + {%- raw %} {% if object.name %}

{{ object.name }}

{% endif %} + {%- endraw %} + {%- endif %}
- -{% if object == request.user %} - -
- -
- My Info - E-Mail - -
- -
- -{% endif %} - + {%- raw %} + {% if object == request.user %} + +
+
+ My Info + E-Mail + MFA + +
+
+ + {% endif %}
{% endblock content %} {%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_form.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_form.html index 53e14a530..bd1299a6d 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_form.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_form.html @@ -1,18 +1,36 @@ {% raw %}{% extends "base.html" %} + {% load crispy_forms_tags %} -{% block title %}{{ user.username }}{% endblock %} - +{% block title %} +{% endraw %} +{% if cookiecutter.username_type == "email" %} + {% raw %}{{ user.name }}{% endraw %} +{% else %} + {% raw %}{{ user.username }}{% endraw %} +{% endif %} +{% raw %} +{% endblock title %} {% block content %} -

{{ user.username }}

-
- {% csrf_token %} - {{ form|crispy }} -
-
- -
+

+ {% endraw %} + {% if cookiecutter.username_type == "email" %} + {% raw %}{{ user.name }}{% endraw %} + {% else %} + {% raw %}{{ user.username }}{% endraw %} + {% endif %} + {% raw %} +

+ + {% csrf_token %} + {{ form|crispy }} +
+
+
- -{% endblock %} +
+ +{% endblock content %} {%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py index 0d206fae4..484f686ad 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py @@ -1,16 +1,48 @@ -from typing import Any +from __future__ import annotations + +import typing from allauth.account.adapter import DefaultAccountAdapter from allauth.socialaccount.adapter import DefaultSocialAccountAdapter from django.conf import settings -from django.http import HttpRequest + +if typing.TYPE_CHECKING: + from allauth.socialaccount.models import SocialLogin + from django.http import HttpRequest + + from {{cookiecutter.project_slug}}.users.models import User class AccountAdapter(DefaultAccountAdapter): - def is_open_for_signup(self, request: HttpRequest): + def is_open_for_signup(self, request: HttpRequest) -> bool: return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True) class SocialAccountAdapter(DefaultSocialAccountAdapter): - def is_open_for_signup(self, request: HttpRequest, sociallogin: Any): + def is_open_for_signup( + self, + request: HttpRequest, + sociallogin: SocialLogin, + ) -> bool: return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True) + + def populate_user( + self, + request: HttpRequest, + sociallogin: SocialLogin, + data: dict[str, typing.Any], + ) -> User: + """ + Populates user information from social provider info. + + See: https://docs.allauth.org/en/latest/socialaccount/advanced.html#creating-and-populating-user-instances + """ + user = super().populate_user(request, sociallogin, data) + if not user.name: + if name := data.get("name"): + user.name = name + elif first_name := data.get("first_name"): + user.name = first_name + if last_name := data.get("last_name"): + user.name += f" {last_name}" + return user diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py index 6675f483a..70f829256 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py @@ -1,21 +1,31 @@ +from django.conf import settings from django.contrib import admin from django.contrib.auth import admin as auth_admin -from django.contrib.auth import get_user_model +from django.contrib.auth.decorators import login_required from django.utils.translation import gettext_lazy as _ -from {{ cookiecutter.project_slug }}.users.forms import UserAdminChangeForm, UserAdminCreationForm +from .forms import UserAdminChangeForm +from .forms import UserAdminCreationForm +from .models import User -User = get_user_model() +if settings.DJANGO_ADMIN_FORCE_ALLAUTH: + # Force the `admin` sign in process to go through the `django-allauth` workflow: + # https://docs.allauth.org/en/latest/common/admin.html#admin + admin.site.login = login_required(admin.site.login) # type: ignore[method-assign] @admin.register(User) class UserAdmin(auth_admin.UserAdmin): - form = UserAdminChangeForm add_form = UserAdminCreationForm fieldsets = ( + {%- if cookiecutter.username_type == "email" %} + (None, {"fields": ("email", "password")}), + (_("Personal info"), {"fields": ("name",)}), + {%- else %} (None, {"fields": ("username", "password")}), (_("Personal info"), {"fields": ("name", "email")}), + {%- endif %} ( _("Permissions"), { @@ -30,5 +40,17 @@ class UserAdmin(auth_admin.UserAdmin): ), (_("Important dates"), {"fields": ("last_login", "date_joined")}), ) - list_display = ["username", "name", "is_superuser"] + list_display = ["{{cookiecutter.username_type}}", "name", "is_superuser"] search_fields = ["name"] + {%- if cookiecutter.username_type == "email" %} + ordering = ["id"] + add_fieldsets = ( + ( + None, + { + "classes": ("wide",), + "fields": ("email", "password1", "password2"), + }, + ), + ) + {%- endif %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/__init__.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py index b5ccabba1..51e0859f3 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py @@ -1,14 +1,21 @@ -from django.contrib.auth import get_user_model from rest_framework import serializers -User = get_user_model() +from {{ cookiecutter.project_slug }}.users.models import User -class UserSerializer(serializers.ModelSerializer): +class UserSerializer(serializers.ModelSerializer[User]): class Meta: model = User + {%- if cookiecutter.username_type == "email" %} + fields = ["name", "url"] + + extra_kwargs = { + "url": {"view_name": "api:user-detail", "lookup_field": "pk"}, + } + {%- else %} fields = ["username", "name", "url"] extra_kwargs = { - "url": {"view_name": "api:user-detail", "lookup_field": "username"} + "url": {"view_name": "api:user-detail", "lookup_field": "username"}, } + {%- endif %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/views.py index 98bb04e7b..7a521cdfe 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/views.py @@ -1,19 +1,24 @@ -from django.contrib.auth import get_user_model from rest_framework import status from rest_framework.decorators import action -from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, UpdateModelMixin +from rest_framework.mixins import ListModelMixin +from rest_framework.mixins import RetrieveModelMixin +from rest_framework.mixins import UpdateModelMixin from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet -from .serializers import UserSerializer +from {{ cookiecutter.project_slug }}.users.models import User -User = get_user_model() +from .serializers import UserSerializer class UserViewSet(RetrieveModelMixin, ListModelMixin, UpdateModelMixin, GenericViewSet): serializer_class = UserSerializer queryset = User.objects.all() + {%- if cookiecutter.username_type == "email" %} + lookup_field = "pk" + {%- else %} lookup_field = "username" + {%- endif %} def get_queryset(self, *args, **kwargs): assert isinstance(self.request.user.id, int) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py index 2241e5eb5..5c3d4fe08 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py @@ -1,3 +1,5 @@ +import contextlib + from django.apps import AppConfig from django.utils.translation import gettext_lazy as _ @@ -7,7 +9,5 @@ class UsersConfig(AppConfig): verbose_name = _("Users") def ready(self): - try: - import {{ cookiecutter.project_slug }}.users.signals # noqa F401 - except ImportError: - pass + with contextlib.suppress(ImportError): + import {{ cookiecutter.project_slug }}.users.signals # noqa: F401 diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py index 6e1dd9d32..830fca60d 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py @@ -1,15 +1,20 @@ from allauth.account.forms import SignupForm from allauth.socialaccount.forms import SignupForm as SocialSignupForm from django.contrib.auth import forms as admin_forms -from django.contrib.auth import get_user_model +{%- if cookiecutter.username_type == "email" %} +from django.forms import EmailField +{%- endif %} from django.utils.translation import gettext_lazy as _ -User = get_user_model() +from .models import User class UserAdminChangeForm(admin_forms.UserChangeForm): class Meta(admin_forms.UserChangeForm.Meta): model = User + {%- if cookiecutter.username_type == "email" %} + field_classes = {"email": EmailField} + {%- endif %} class UserAdminCreationForm(admin_forms.UserCreationForm): @@ -20,10 +25,17 @@ class UserAdminCreationForm(admin_forms.UserCreationForm): class Meta(admin_forms.UserCreationForm.Meta): model = User - + {%- if cookiecutter.username_type == "email" %} + fields = ("email",) + field_classes = {"email": EmailField} error_messages = { - "username": {"unique": _("This username has already been taken.")} + "email": {"unique": _("This email has already been taken.")}, } + {%- else %} + error_messages = { + "username": {"unique": _("This username has already been taken.")}, + } + {%- endif %} class UserSignupForm(SignupForm): diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/managers.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/managers.py new file mode 100644 index 000000000..d8beaa48e --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/managers.py @@ -0,0 +1,42 @@ +from typing import TYPE_CHECKING + +from django.contrib.auth.hashers import make_password +from django.contrib.auth.models import UserManager as DjangoUserManager + +if TYPE_CHECKING: + from .models import User # noqa: F401 + + +class UserManager(DjangoUserManager["User"]): + """Custom manager for the User model.""" + + def _create_user(self, email: str, password: str | None, **extra_fields): + """ + Create and save a user with the given email and password. + """ + if not email: + msg = "The given email must be set" + raise ValueError(msg) + email = self.normalize_email(email) + user = self.model(email=email, **extra_fields) + user.password = make_password(password) + user.save(using=self._db) + return user + + def create_user(self, email: str, password: str | None = None, **extra_fields): # type: ignore[override] + extra_fields.setdefault("is_staff", False) + extra_fields.setdefault("is_superuser", False) + return self._create_user(email, password, **extra_fields) + + def create_superuser(self, email: str, password: str | None = None, **extra_fields): # type: ignore[override] + extra_fields.setdefault("is_staff", True) + extra_fields.setdefault("is_superuser", True) + + if extra_fields.get("is_staff") is not True: + msg = "Superuser must have is_staff=True." + raise ValueError(msg) + if extra_fields.get("is_superuser") is not True: + msg = "Superuser must have is_superuser=True." + raise ValueError(msg) + + return self._create_user(email, password, **extra_fields) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/0001_initial.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/0001_initial.py index acd18512f..cee6676bc 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/0001_initial.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/0001_initial.py @@ -1,7 +1,10 @@ import django.contrib.auth.models import django.contrib.auth.validators -from django.db import migrations, models import django.utils.timezone +from django.db import migrations +from django.db import models + +import {{cookiecutter.project_slug}}.users.models class Migration(migrations.Migration): @@ -29,7 +32,7 @@ class Migration(migrations.Migration): ( "last_login", models.DateTimeField( - blank=True, null=True, verbose_name="last login" + blank=True, null=True, verbose_name="last login", ), ), ( @@ -40,6 +43,7 @@ class Migration(migrations.Migration): verbose_name="superuser status", ), ), + {%- if cookiecutter.username_type == "username" -%} ( "username", models.CharField( @@ -58,9 +62,17 @@ class Migration(migrations.Migration): ( "email", models.EmailField( - blank=True, max_length=254, verbose_name="email address" + blank=True, max_length=254, verbose_name="email address", ), ), + {%- else %} + ( + "email", + models.EmailField( + unique=True, max_length=254, verbose_name="email address", + ), + ), + {%- endif %} ( "is_staff", models.BooleanField( @@ -80,13 +92,13 @@ class Migration(migrations.Migration): ( "date_joined", models.DateTimeField( - default=django.utils.timezone.now, verbose_name="date joined" + default=django.utils.timezone.now, verbose_name="date joined", ), ), ( "name", models.CharField( - blank=True, max_length=255, verbose_name="Name of User" + blank=True, max_length=255, verbose_name="Name of User", ), ), ( @@ -118,7 +130,11 @@ class Migration(migrations.Migration): "abstract": False, }, managers=[ + {%- if cookiecutter.username_type == "email" %} + ("objects", {{cookiecutter.project_slug}}.users.models.UserManager()), + {%- else %} ("objects", django.contrib.auth.models.UserManager()), + {%- endif %} ], ), ] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py index 1f6f61bc0..4a870cc28 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py @@ -1,7 +1,18 @@ +{%- if cookiecutter.username_type == "email" %} +from typing import ClassVar + +{% endif -%} from django.contrib.auth.models import AbstractUser from django.db.models import CharField +{%- if cookiecutter.username_type == "email" %} +from django.db.models import EmailField +{%- endif %} from django.urls import reverse from django.utils.translation import gettext_lazy as _ +{%- if cookiecutter.username_type == "email" %} + +from .managers import UserManager +{%- endif %} class User(AbstractUser): @@ -11,16 +22,29 @@ class User(AbstractUser): check forms.SignupForm and forms.SocialSignupForms accordingly. """ - #: First and last name do not cover name patterns around the globe + # First and last name do not cover name patterns around the globe name = CharField(_("Name of User"), blank=True, max_length=255) - first_name = None # type: ignore - last_name = None # type: ignore + first_name = None # type: ignore[assignment] + last_name = None # type: ignore[assignment] + {%- if cookiecutter.username_type == "email" %} + email = EmailField(_("email address"), unique=True) + username = None # type: ignore[assignment] - def get_absolute_url(self): - """Get url for user's detail view. + USERNAME_FIELD = "email" + REQUIRED_FIELDS = [] + + objects: ClassVar[UserManager] = UserManager() + {%- endif %} + + def get_absolute_url(self) -> str: + """Get URL for user's detail view. Returns: str: URL for user detail. """ + {%- if cookiecutter.username_type == "email" %} + return reverse("users:detail", kwargs={"pk": self.id}) + {%- else %} return reverse("users:detail", kwargs={"username": self.username}) + {%- endif %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tasks.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tasks.py index c99341c5f..ca51cd740 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tasks.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tasks.py @@ -1,11 +1,9 @@ -from django.contrib.auth import get_user_model +from celery import shared_task -from config import celery_app - -User = get_user_model() +from .models import User -@celery_app.task() +@shared_task() def get_users_count(): """A pointless Celery task to demonstrate usage.""" return User.objects.count() diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py index e30476227..136d0b1d5 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py @@ -1,19 +1,22 @@ from collections.abc import Sequence from typing import Any -from django.contrib.auth import get_user_model -from factory import Faker, post_generation +from factory import Faker +from factory import post_generation from factory.django import DjangoModelFactory +from {{ cookiecutter.project_slug }}.users.models import User + class UserFactory(DjangoModelFactory): - + {%- if cookiecutter.username_type == "username" %} username = Faker("user_name") + {%- endif %} email = Faker("email") name = Faker("name") @post_generation - def password(self, create: bool, extracted: Sequence[Any], **kwargs): + def password(self, create: bool, extracted: Sequence[Any], **kwargs): # noqa: FBT001 password = ( extracted if extracted @@ -28,6 +31,13 @@ class UserFactory(DjangoModelFactory): ) self.set_password(password) + @classmethod + def _after_postgeneration(cls, instance, create, results=None): + """Save again the instance if creating and at least one hook ran.""" + if create and results and not cls._meta.skip_postgeneration_save: + # Some post-generation hooks ran, and may have modified us. + instance.save() + class Meta: - model = get_user_model() - django_get_or_create = ["username"] + model = User + django_get_or_create = ["{{cookiecutter.username_type}}"] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py index a370784fc..f802b8ba1 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py @@ -1,4 +1,12 @@ +import contextlib +from http import HTTPStatus +from importlib import reload + +import pytest +from django.contrib import admin +from django.contrib.auth.models import AnonymousUser from django.urls import reverse +from pytest_django.asserts import assertRedirects from {{ cookiecutter.project_slug }}.users.models import User @@ -7,31 +15,63 @@ class TestUserAdmin: def test_changelist(self, admin_client): url = reverse("admin:users_user_changelist") response = admin_client.get(url) - assert response.status_code == 200 + assert response.status_code == HTTPStatus.OK def test_search(self, admin_client): url = reverse("admin:users_user_changelist") response = admin_client.get(url, data={"q": "test"}) - assert response.status_code == 200 + assert response.status_code == HTTPStatus.OK def test_add(self, admin_client): url = reverse("admin:users_user_add") response = admin_client.get(url) - assert response.status_code == 200 + assert response.status_code == HTTPStatus.OK response = admin_client.post( url, data={ + {%- if cookiecutter.username_type == "email" %} + "email": "new-admin@example.com", + {%- else %} "username": "test", + {%- endif %} "password1": "My_R@ndom-P@ssw0rd", "password2": "My_R@ndom-P@ssw0rd", }, ) - assert response.status_code == 302 + assert response.status_code == HTTPStatus.FOUND + {%- if cookiecutter.username_type == "email" %} + assert User.objects.filter(email="new-admin@example.com").exists() + {%- else %} assert User.objects.filter(username="test").exists() + {%- endif %} def test_view_user(self, admin_client): + {%- if cookiecutter.username_type == "email" %} + user = User.objects.get(email="admin@example.com") + {%- else %} user = User.objects.get(username="admin") + {%- endif %} url = reverse("admin:users_user_change", kwargs={"object_id": user.pk}) response = admin_client.get(url) - assert response.status_code == 200 + assert response.status_code == HTTPStatus.OK + + @pytest.fixture() + def _force_allauth(self, settings): + settings.DJANGO_ADMIN_FORCE_ALLAUTH = True + # Reload the admin module to apply the setting change + import {{ cookiecutter.project_slug }}.users.admin as users_admin + + with contextlib.suppress(admin.sites.AlreadyRegistered): + reload(users_admin) + + @pytest.mark.django_db() + @pytest.mark.usefixtures("_force_allauth") + def test_allauth_login(self, rf, settings): + request = rf.get("/fake-url") + request.user = AnonymousUser() + response = admin.site.login(request) + + # The `admin` login view should redirect to the `allauth` login view + target_url = reverse(settings.LOGIN_URL) + "?next=" + request.path + assertRedirects(response, target_url, fetch_redirect_response=False) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_urls.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_urls.py index 7d9c444d3..b445b611d 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_urls.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_urls.py @@ -1,14 +1,22 @@ -from django.urls import resolve, reverse +from django.urls import resolve +from django.urls import reverse from {{ cookiecutter.project_slug }}.users.models import User def test_user_detail(user: User): + {%- if cookiecutter.username_type == "email" %} + assert ( + reverse("api:user-detail", kwargs={"pk": user.pk}) == f"/api/users/{user.pk}/" + ) + assert resolve(f"/api/users/{user.pk}/").view_name == "api:user-detail" + {%- else %} assert ( reverse("api:user-detail", kwargs={"username": user.username}) == f"/api/users/{user.username}/" ) assert resolve(f"/api/users/{user.username}/").view_name == "api:user-detail" + {%- endif %} def test_user_list(): 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 4d163bf08..955ebe4eb 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_views.py @@ -1,30 +1,39 @@ -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[call-arg, arg-type, misc] assert response.data == { + {%- if cookiecutter.username_type == "email" %} + "url": f"http://testserver/api/users/{user.pk}/", + {%- else %} "username": user.username, - "name": user.name, "url": f"http://testserver/api/users/{user.username}/", + {%- endif %} + "name": user.name, } diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py index 261f88c8c..17d0d72a1 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py @@ -1,6 +1,5 @@ -""" -Module for all Form Tests. -""" +"""Module for all Form Tests.""" + from django.utils.translation import gettext_lazy as _ from {{ cookiecutter.project_slug }}.users.forms import UserAdminCreationForm @@ -24,13 +23,22 @@ class TestUserAdminCreationForm: # hence cannot be created. form = UserAdminCreationForm( { + {%- if cookiecutter.username_type == "email" %} + "email": user.email, + {%- else %} "username": user.username, + {%- endif %} "password1": user.password, "password2": user.password, - } + }, ) assert not form.is_valid() assert len(form.errors) == 1 + {%- if cookiecutter.username_type == "email" %} + assert "email" in form.errors + assert form.errors["email"][0] == _("This email has already been taken.") + {%- else %} assert "username" in form.errors assert form.errors["username"][0] == _("This username has already been taken.") + {%- endif %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_managers.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_managers.py new file mode 100644 index 000000000..e5e5f5a4b --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_managers.py @@ -0,0 +1,55 @@ +from io import StringIO + +import pytest +from django.core.management import call_command + +from {{ cookiecutter.project_slug }}.users.models import User + + +@pytest.mark.django_db() +class TestUserManager: + def test_create_user(self): + user = User.objects.create_user( + email="john@example.com", + password="something-r@nd0m!", # noqa: S106 + ) + assert user.email == "john@example.com" + assert not user.is_staff + assert not user.is_superuser + assert user.check_password("something-r@nd0m!") + assert user.username is None + + def test_create_superuser(self): + user = User.objects.create_superuser( + email="admin@example.com", + password="something-r@nd0m!", # noqa: S106 + ) + assert user.email == "admin@example.com" + assert user.is_staff + assert user.is_superuser + assert user.username is None + + def test_create_superuser_username_ignored(self): + user = User.objects.create_superuser( + email="test@example.com", + password="something-r@nd0m!", # noqa: S106 + ) + assert user.username is None + + +@pytest.mark.django_db() +def test_createsuperuser_command(): + """Ensure createsuperuser command works with our custom manager.""" + out = StringIO() + command_result = call_command( + "createsuperuser", + "--email", + "henry@example.com", + interactive=False, + stdout=out, + ) + + assert command_result is None + assert out.getvalue() == "Superuser created successfully.\n" + user = User.objects.get(email="henry@example.com") + assert not user.has_usable_password() diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py index b09bcdf0b..76d89ce3f 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py @@ -2,4 +2,8 @@ from {{ cookiecutter.project_slug }}.users.models import User def test_user_get_absolute_url(user: User): + {%- if cookiecutter.username_type == "email" %} + assert user.get_absolute_url() == f"/users/{user.pk}/" + {%- else %} assert user.get_absolute_url() == f"/users/{user.username}/" + {%- endif %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_swagger.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_swagger.py index f97658b55..3081d1f65 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_swagger.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_swagger.py @@ -1,3 +1,5 @@ +from http import HTTPStatus + import pytest from django.urls import reverse @@ -5,17 +7,17 @@ from django.urls import reverse def test_swagger_accessible_by_admin(admin_client): url = reverse("api-docs") response = admin_client.get(url) - assert response.status_code == 200 + assert response.status_code == HTTPStatus.OK -@pytest.mark.django_db +@pytest.mark.django_db() def test_swagger_ui_not_accessible_by_normal_user(client): url = reverse("api-docs") response = client.get(url) - assert response.status_code == 403 + assert response.status_code == HTTPStatus.FORBIDDEN def test_api_schema_generated_successfully(admin_client): url = reverse("api-schema") response = admin_client.get(url) - assert response.status_code == 200 + assert response.status_code == HTTPStatus.OK diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_tasks.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_tasks.py index 41d5af292..d3f610139 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_tasks.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_tasks.py @@ -9,8 +9,9 @@ pytestmark = pytest.mark.django_db def test_user_count(settings): """A basic test to execute the get_users_count Celery task.""" - UserFactory.create_batch(3) + batch_size = 3 + UserFactory.create_batch(batch_size) settings.CELERY_TASK_ALWAYS_EAGER = True task_result = get_users_count.delay() assert isinstance(task_result, EagerResult) - assert task_result.result == 3 + assert task_result.result == batch_size diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py index 7cd056db5..aaacb05a1 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py @@ -1,14 +1,20 @@ -from django.urls import resolve, reverse +from django.urls import resolve +from django.urls import reverse from {{ cookiecutter.project_slug }}.users.models import User def test_detail(user: User): + {%- if cookiecutter.username_type == "email" %} + assert reverse("users:detail", kwargs={"pk": user.pk}) == f"/users/{user.pk}/" + assert resolve(f"/users/{user.pk}/").view_name == "users:detail" + {%- else %} assert ( reverse("users:detail", kwargs={"username": user.username}) == f"/users/{user.username}/" ) assert resolve(f"/users/{user.username}/").view_name == "users:detail" + {%- endif %} def test_update(): diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py index d968d7ec9..136daa402 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py @@ -1,21 +1,23 @@ +from http import HTTPStatus + import pytest from django.conf import settings from django.contrib import messages from django.contrib.auth.models import AnonymousUser from django.contrib.messages.middleware import MessageMiddleware from django.contrib.sessions.middleware import SessionMiddleware -from django.http import HttpRequest, HttpResponseRedirect +from django.http import HttpRequest +from django.http import HttpResponseRedirect from django.test import RequestFactory from django.urls import reverse +from django.utils.translation import gettext_lazy as _ from {{ cookiecutter.project_slug }}.users.forms import UserAdminChangeForm from {{ cookiecutter.project_slug }}.users.models import User from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory -from {{ cookiecutter.project_slug }}.users.views import ( - UserRedirectView, - UserUpdateView, - user_detail_view, -) +from {{ cookiecutter.project_slug }}.users.views import UserRedirectView +from {{ cookiecutter.project_slug }}.users.views import UserUpdateView +from {{ cookiecutter.project_slug }}.users.views import user_detail_view pytestmark = pytest.mark.django_db @@ -39,7 +41,11 @@ class TestUserUpdateView: view.request = request + {%- if cookiecutter.username_type == "email" %} + assert view.get_success_url() == f"/users/{user.pk}/" + {%- else %} assert view.get_success_url() == f"/users/{user.username}/" + {%- endif %} def test_get_object(self, user: User, rf: RequestFactory): view = UserUpdateView() @@ -68,7 +74,7 @@ class TestUserUpdateView: view.form_valid(form) messages_sent = [m.message for m in messages.get_messages(request)] - assert messages_sent == ["Information successfully updated"] + assert messages_sent == [_("Information successfully updated")] class TestUserRedirectView: @@ -79,7 +85,11 @@ class TestUserRedirectView: view.request = request + {%- if cookiecutter.username_type == "email" %} + assert view.get_redirect_url() == f"/users/{user.pk}/" + {%- else %} assert view.get_redirect_url() == f"/users/{user.username}/" + {%- endif %} class TestUserDetailView: @@ -87,17 +97,25 @@ class TestUserDetailView: request = rf.get("/fake-url/") request.user = UserFactory() + {%- if cookiecutter.username_type == "email" %} + response = user_detail_view(request, pk=user.pk) + {%- else %} response = user_detail_view(request, username=user.username) + {%- endif %} - assert response.status_code == 200 + assert response.status_code == HTTPStatus.OK def test_not_authenticated(self, user: User, rf: RequestFactory): request = rf.get("/fake-url/") request.user = AnonymousUser() + {%- if cookiecutter.username_type == "email" %} + response = user_detail_view(request, pk=user.pk) + {%- else %} response = user_detail_view(request, username=user.username) + {%- endif %} login_url = reverse(settings.LOGIN_URL) assert isinstance(response, HttpResponseRedirect) - assert response.status_code == 302 + assert response.status_code == HTTPStatus.FOUND assert response.url == f"{login_url}?next=/fake-url/" diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py index 8c8c7e2ea..74d65da1e 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py @@ -1,14 +1,16 @@ from django.urls import path -from {{ cookiecutter.project_slug }}.users.views import ( - user_detail_view, - user_redirect_view, - user_update_view, -) +from .views import user_detail_view +from .views import user_redirect_view +from .views import user_update_view app_name = "users" urlpatterns = [ path("~redirect/", view=user_redirect_view, name="redirect"), path("~update/", view=user_update_view, name="update"), + {%- if cookiecutter.username_type == "email" %} + path("/", view=user_detail_view, name="detail"), + {%- else %} path("/", view=user_detail_view, name="detail"), + {%- endif %} ] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py index baa04a0d4..3f20f2686 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py @@ -1,33 +1,36 @@ -from django.contrib.auth import get_user_model from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.messages.views import SuccessMessageMixin from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from django.views.generic import DetailView, RedirectView, UpdateView +from django.views.generic import DetailView +from django.views.generic import RedirectView +from django.views.generic import UpdateView -User = get_user_model() +from {{ cookiecutter.project_slug }}.users.models import User class UserDetailView(LoginRequiredMixin, DetailView): - model = User + {%- if cookiecutter.username_type == "email" %} + slug_field = "id" + slug_url_kwarg = "id" + {%- else %} slug_field = "username" slug_url_kwarg = "username" + {%- endif %} user_detail_view = UserDetailView.as_view() class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): - model = User fields = ["name"] success_message = _("Information successfully updated") def get_success_url(self): - assert ( - self.request.user.is_authenticated - ) # for mypy to know that the user is authenticated + # for mypy to know that the user is authenticated + assert self.request.user.is_authenticated return self.request.user.get_absolute_url() def get_object(self): @@ -38,11 +41,14 @@ user_update_view = UserUpdateView.as_view() class UserRedirectView(LoginRequiredMixin, RedirectView): - permanent = False def get_redirect_url(self): + {%- if cookiecutter.username_type == "email" %} + return reverse("users:detail", kwargs={"pk": self.request.user.pk}) + {%- else %} return reverse("users:detail", kwargs={"username": self.request.user.username}) + {%- endif %} user_redirect_view = UserRedirectView.as_view() diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/storages.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/storages.py deleted file mode 100644 index b712d3239..000000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/storages.py +++ /dev/null @@ -1,25 +0,0 @@ -{% if cookiecutter.cloud_provider == 'AWS' -%} -from storages.backends.s3boto3 import S3Boto3Storage - - -class StaticRootS3Boto3Storage(S3Boto3Storage): - location = "static" - default_acl = "public-read" - - -class MediaRootS3Boto3Storage(S3Boto3Storage): - location = "media" - file_overwrite = False -{%- elif cookiecutter.cloud_provider == 'GCP' -%} -from storages.backends.gcloud import GoogleCloudStorage - - -class StaticRootGoogleCloudStorage(GoogleCloudStorage): - location = "static" - default_acl = "publicRead" - - -class MediaRootGoogleCloudStorage(GoogleCloudStorage): - location = "media" - file_overwrite = False -{%- endif %}