diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000..84b4b4411 --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +exclude = docs +max-line-length = 119 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..0e5ec12c4 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 3d4ec0488..f5d7a4df6 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", @@ -1317,5 +1318,85 @@ "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": "" } ] \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8922cd450..7642a3f2d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -18,3 +18,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..dbda77b5e 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 with: - python-version: "3.10" + python-version: "3.11" 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,11 +38,15 @@ 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 @@ -62,7 +56,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: "3.10" + python-version: "3.11" 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" - name: "${{ matrix.script.name }} Bare metal" + name: "Bare metal ${{ matrix.script.name }}" runs-on: ubuntu-latest services: redis: @@ -102,7 +100,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: "3.10" + python-version: "3.11" cache: pip cache-dependency-path: | requirements.txt @@ -112,6 +110,6 @@ jobs: run: pip install -r requirements.txt - uses: actions/setup-node@v3 with: - node-version: "16" + node-version: "18" - 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..cbfea922d 100644 --- a/.github/workflows/django-issue-checker.yml +++ b/.github/workflows/django-issue-checker.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: - python-version: "3.10" + python-version: "3.11" - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/pre-commit-autoupdate.yml b/.github/workflows/pre-commit-autoupdate.yml index 1708e8b82..75bfe0a20 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 with: - python-version: "3.10" + python-version: "3.11" - 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@v5 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..f48f297aa 100644 --- a/.github/workflows/update-changelog.yml +++ b/.github/workflows/update-changelog.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.10" + python-version: "3.11" - 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 571bdcd02..78d72c241 100644 --- a/.github/workflows/update-contributors.yml +++ b/.github/workflows/update-contributors.yml @@ -13,7 +13,7 @@ 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: @@ -22,16 +22,18 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.10" + python-version: "3.11" - 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.4 + uses: stefanzweifel/git-auto-commit-action@v4.16.0 with: commit_message: Update Contributors file_pattern: CONTRIBUTORS.md .github/contributors.json diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index df197108f..b35eae073 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,4 @@ -exclude: "{{cookiecutter.project_slug}}" +exclude: "{{cookiecutter.project_slug}}|.github/contributors.json|CHANGELOG.md|CONTRIBUTORS.md" default_stages: [commit] repos: @@ -6,22 +6,36 @@ repos: rev: v4.4.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: "v3.0.0-alpha.9-for-vscode" + hooks: + - id: prettier + args: ["--tab-width", "2"] - repo: https://github.com/asottile/pyupgrade - rev: v3.2.2 + rev: v3.7.0 hooks: - id: pyupgrade - args: [--py310-plus] + args: [--py311-plus] exclude: hooks/ - repo: https://github.com/psf/black - rev: 22.10.0 + rev: 23.3.0 hooks: - id: black - repo: https://github.com/PyCQA/isort - rev: 5.10.1 + rev: 5.12.0 hooks: - id: isort diff --git a/.readthedocs.yaml b/.readthedocs.yaml index beb30d845..4598ff77c 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.11" + # 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 ba72b2cf3..309f62a26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,857 @@ All enhancements and patches to Cookiecutter Django will be documented in this f +## 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 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..94ecbdd7d 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.11. 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.11: -[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.11.3 +``` - $ pip install tox +Any version that starts with 3.11 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: `scripts/test_bare.sh` (for bare metal) +- With Docker: `scripts/test_docker.sh` + +All arguments to these scripts will be passed to the `cookiecutter` CLI, letting you set options, for example: + +```bash +$ scripts/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 028ed7231..52072d8e2 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 @@ -292,6 +299,13 @@ Listed in alphabetical order. + + Arkadiusz Michał Ryś + + arrys + + + Arnav Choudhury @@ -362,6 +376,13 @@ Listed in alphabetical order. + + Birtibu + + Birtibu + + + Bo Lopker @@ -642,6 +663,13 @@ Listed in alphabetical order. jangeador + + Delphine LEMIRE + + DelphineLemire + + + Demetris Stavrou @@ -705,6 +733,13 @@ Listed in alphabetical order. dudanogueira + + duffn + + duffn + + + Dónal Adams @@ -901,6 +936,13 @@ Listed in alphabetical order. + + Hoai-Thu Vuong + + thuvh + + + Howie Zhao @@ -971,13 +1013,6 @@ Listed in alphabetical order. - - Jelmer Draaijer - - foarsitter - - - Jens Nilsson @@ -1279,6 +1314,13 @@ Listed in alphabetical order. + + Matheus Jardim Bernardes + + matheusjardimb + + + Mathijs Hoogland @@ -1398,6 +1440,13 @@ Listed in alphabetical order. + + Morten Kaae + + MortenKaae + + + mozillazg @@ -1412,6 +1461,13 @@ Listed in alphabetical order. + + mpsantos + + mpsantos + + + Naveen @@ -1447,6 +1503,13 @@ Listed in alphabetical order. + + Omer-5 + + Omer-5 + + + Pablo @@ -1454,6 +1517,13 @@ Listed in alphabetical order. + + Pamela Fox + + pamelafox + + pamelafox + Parbhat Puri @@ -1461,6 +1531,13 @@ Listed in alphabetical order. + + Patrick Tran + + theptrk + + + Pawan Chaurasia @@ -1545,6 +1622,13 @@ Listed in alphabetical order. + + rguptar + + rguptar + + + Richard Hajdu @@ -1552,6 +1636,13 @@ Listed in alphabetical order. + + Robin + + Kaffeetasse + + + Roman Afanaskin @@ -1657,6 +1748,13 @@ Listed in alphabetical order. + + TAKAHASHI Shuuji + + shuuji3 + + + Tames McTigue @@ -1685,6 +1783,13 @@ Listed in alphabetical order. thibault + + Thomas Booij + + ThomasBooij95 + + + Théo Segonds @@ -1692,6 +1797,13 @@ Listed in alphabetical order. + + tildebox + + tildebox + + + Tim Claessens @@ -1867,6 +1979,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..bab5aa041 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) +[![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/uFXweDQc5a) [![Code Helpers Badge](https://www.codetriage.com/cookiecutter/cookiecutter-django/badges/users.svg)](https://www.codetriage.com/cookiecutter/cookiecutter-django) -[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) Powered by [Cookiecutter](https://github.com/cookiecutter/cookiecutter), Cookiecutter Django is a framework for jumpstarting production-ready Django projects quickly. -- 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.1 +- Works with Python 3.11 +- 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 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 [MailHog](https://github.com/mailhog/MailHog) 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: 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). ## 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,6 +120,10 @@ 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 @@ -149,6 +157,7 @@ Answer the prompts with your own desired [options](http://cookiecutter-django.re 1 - None 2 - Django Compressor 3 - Gulp + 4 - Webpack Choose from 1, 2, 3, 4 [1]: 1 use_celery [n]: y use_mailhog [n]: n @@ -181,14 +190,14 @@ 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). ## For Readers of Two Scoops of Django @@ -196,13 +205,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 +222,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 local.yml build + docker-compose -f local.yml up + Debugging ~~~~~~~~~ @@ -210,6 +226,11 @@ By default, it's enabled both in local and production environments (``local.yml` .. _`Flower`: https://github.com/mher/flower +Using Webpack or Gulp +~~~~~~~~~~~~~~~~~~~~~ + +When using Webpack or Gulp as the ``frontend_pipeline`` option, you should access your application at the address of the ``node`` service in order to see your correct styles. This is http://localhost:3000 by default. When using any of the other ``frontend_pipeline`` options, you should use the address of the ``django`` service, http://localhost:8000. + Developing locally with HTTPS ----------------------------- diff --git a/docs/developing-locally.rst b/docs/developing-locally.rst index c9d28ff73..b79033aaa 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.11 * PostgreSQL_. * Redis_, if using Celery * Cookiecutter_ @@ -18,15 +18,14 @@ First things first. #. Create a virtualenv: :: - $ python3.10 -m venv + $ python3.11 -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:: @@ -141,23 +141,40 @@ In production, we have Mailgun_ configured to have your back! Celery ------ -If the project is configured to use Celery as a task scheduler then by default tasks are set to run on the main thread -when developing locally. If you have the appropriate setup on your local machine then set the following -in ``config/settings/local.py``:: +If the project is configured to use Celery as a task scheduler then, by default, tasks are set to run on the main thread when developing locally instead of getting sent to a broker. However, if you have Redis setup on your local machine, you can set the following in ``config/settings/local.py``:: CELERY_TASK_ALWAYS_EAGER = False -To run Celery locally, make sure redis-server is installed (instructions are available at https://redis.io/topics/quickstart), run the server in one terminal with `redis-server`, and then start celery in another terminal with the following command:: +Next, make sure `redis-server` is installed (per the `Getting started with Redis`_ guide) and run the server in one terminal:: - celery -A config.celery_app worker --loglevel=info + $ redis-server + +Start the Celery worker by running the following command in another terminal:: + + $ celery -A config.celery_app worker --loglevel=info + +That Celery worker should be running whenever your app is running, typically as a background process, +so that it can pick up any tasks that get queued. Learn more from the `Celery Workers Guide`_. + +The project comes with a simple task for manual testing purposes, inside `/users/tasks.py`. To queue that task locally, start the Django shell, import the task, and call `delay()` on it:: + + $ python manage.py shell + >> from .users.tasks import get_users_count + >> get_users_count.delay() + +You can also use Django admin to queue up tasks, thanks to the `django-celerybeat`_ package. + +.. _Getting started with Redis guide: https://redis.io/docs/getting-started/ +.. _Celery Workers Guide: https://docs.celeryq.dev/en/stable/userguide/workers.html +.. _django-celerybeat: https://django-celery-beat.readthedocs.io/en/latest/ Sass Compilation & Live Reloading --------------------------------- -If you've opted for Gulp as front-end pipeline, the project comes configured with `Sass`_ compilation and `live reloading`_. As you change you Sass/JS source files, the task runner will automatically rebuild the corresponding CSS and JS assets and reload them in your browser without refreshing the page. +If you've opted for Gulp or Webpack as front-end pipeline, the project comes configured with `Sass`_ compilation and `live reloading`_. As you change you Sass/JS source files, the task runner will automatically rebuild the corresponding CSS and JS assets and reload them in your browser without refreshing the page. -#. Make sure that `Node.js`_ v16 is installed on your machine. +#. Make sure that `Node.js`_ v18 is installed on your machine. #. In the project root, install the JS dependencies with:: $ npm install 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..da5186487 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -27,6 +27,7 @@ Contents websocket faq troubleshooting + contributing Indices and tables ------------------ diff --git a/docs/project-generation-options.rst b/docs/project-generation-options.rst index 2d8103cf2..a1d788173 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. @@ -66,9 +73,10 @@ cloud_provider: 1. AWS_ 2. GCP_ - 3. None + 3. Azure_ + 4. None - Note that if you choose no cloud provider, media files won't work. + If you choose no cloud provider and docker, the production stack will serve the media files via an nginx Docker service. Without Docker, the media files won't work. mail_service: Select an email service that Django-Anymail provides @@ -94,7 +102,10 @@ frontend_pipeline: 1. None 2. `Django Compressor`_ - 3. `Gulp`_: support Bootstrap recompilation with real-time variables alteration. + 3. `Gulp`_ + 4. `Webpack`_ + +Both Gulp and Webpack support Bootstrap recompilation with real-time variables alteration. use_celery: Indicates whether the project should be configured to use Celery_. @@ -144,9 +155,11 @@ debug: .. _PostgreSQL: https://www.postgresql.org/docs/ .. _Gulp: https://github.com/gulpjs/gulp +.. _Webpack: https://webpack.js.org .. _AWS: https://aws.amazon.com/s3/ .. _GCP: https://cloud.google.com/storage/ +.. _Azure: https://azure.microsoft.com/en-us/products/storage/blobs/ .. _Amazon SES: https://aws.amazon.com/ses/ .. _Mailgun: https://www.mailgun.com diff --git a/docs/requirements.txt b/docs/requirements.txt index 2cc8302a2..d06b651b3 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,3 @@ -sphinx==5.3.0 -sphinx-rtd-theme==1.1.1 +sphinx==6.2.1 +sphinx-rtd-theme==1.2.2 +myst-parser==2.0.0 diff --git a/docs/settings.rst b/docs/settings.rst index b8c6d448e..6dacb7404 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 diff --git a/docs/testing.rst b/docs/testing.rst index dd6fcb48f..bea45c6dd 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -28,10 +28,15 @@ Coverage You should build your tests to provide the highest level of **code coverage**. You can run the ``pytest`` with code ``coverage`` by typing in the following command: :: - $ docker-compose -f local.yml run --rm django coverage run -m pytest + $ coverage run -m pytest Once the tests are complete, in order to see the code coverage, run the following command: :: + $ coverage report + +If you're running the project locally with Docker, use these commands instead: :: + + $ docker-compose -f local.yml run --rm django coverage run -m pytest $ docker-compose -f local.yml run --rm django coverage report .. note:: @@ -53,4 +58,4 @@ Once the tests are complete, in order to see the code coverage, run the followin .. _develop locally with docker: ./developing-locally-docker.html .. _customize: https://docs.pytest.org/en/latest/customize.html .. _unittest: https://docs.python.org/3/library/unittest.html#module-unittest -.. _configuring: https://coverage.readthedocs.io/en/v4.5.x/config.html +.. _configuring: https://coverage.readthedocs.io/en/latest/config.html diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index ba8ab53e6..293e9b652 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -1,5 +1,5 @@ Troubleshooting -===================================== +=============== This page contains some advice about errors and problems commonly encountered during the development of Cookiecutter Django applications. @@ -38,6 +38,16 @@ To fix this, you can either: .. _rm: https://docs.docker.com/engine/reference/commandline/volume_rm/ .. _prune: https://docs.docker.com/v17.09/engine/reference/commandline/system_prune/ +Variable is not set. Defaulting to a blank string +------------------------------------------------- + +Example:: + + WARN[0000] The "DJANGO_AWS_STORAGE_BUCKET_NAME" variable is not set. Defaulting to a blank string. + WARN[0000] The "DJANGO_AWS_S3_CUSTOM_DOMAIN" variable is not set. Defaulting to a blank string. + +You have probably opted for Docker + Webpack without Whitenoise. This is a know limitation of the combination, which needs a little bit of manual intervention. See the :ref:`dedicated section about it `. + Others ------ diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 9c3d946c1..11f165b78 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -10,6 +10,7 @@ TODO: restrict Cookiecutter Django project initialization to """ from __future__ import print_function +import json import os import random import shutil @@ -44,6 +45,24 @@ def remove_gplv3_files(): os.remove(file_name) +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): @@ -73,29 +92,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(): @@ -104,13 +131,86 @@ def remove_packagejson_file(): os.remove(file_name) +def update_package_json(remove_dev_deps=None, remove_keys=None, scripts=None): + remove_dev_deps = remove_dev_deps or [] + remove_keys = remove_keys or [] + scripts = scripts or {} + with open("package.json", mode="r") as fd: + content = json.load(fd) + for package_name in remove_dev_deps: + content["devDependencies"].pop(package_name) + for key in remove_keys: + content.pop(key) + content["scripts"].update(scripts) + with open("package.json", mode="w") as fd: + json.dump(content, fd, ensure_ascii=False, indent=2) + fd.write("\n") + + +def handle_js_runner(choice, use_docker, use_async): + if choice == "Gulp": + update_package_json( + remove_dev_deps=[ + "@babel/core", + "@babel/preset-env", + "babel-loader", + "concurrently", + "css-loader", + "mini-css-extract-plugin", + "postcss-loader", + "postcss-preset-env", + "sass-loader", + "webpack", + "webpack-bundle-tracker", + "webpack-cli", + "webpack-dev-server", + "webpack-merge", + ], + remove_keys=["babel"], + scripts={ + "dev": "gulp", + "build": "gulp generate-assets", + }, + ) + remove_webpack_files() + elif choice == "Webpack": + scripts = { + "dev": "webpack serve --config webpack/dev.config.js", + "build": "webpack --config webpack/prod.config.js", + } + remove_dev_deps = [ + "browser-sync", + "cssnano", + "gulp", + "gulp-imagemin", + "gulp-plumber", + "gulp-postcss", + "gulp-rename", + "gulp-sass", + "gulp-uglify-es", + ] + if not use_docker: + dev_django_cmd = ( + "uvicorn config.asgi:application --reload" if use_async else "python manage.py runserver_plus" + ) + scripts.update( + { + "dev": "concurrently npm:dev:*", + "dev:webpack": "webpack serve --config webpack/dev.config.js", + "dev:django": dev_django_cmd, + } + ) + else: + remove_dev_deps.append("concurrently") + update_package_json(remove_dev_deps=remove_dev_deps, scripts=scripts) + remove_gulp_files() + + def remove_celery_files(): file_names = [ os.path.join("config", "celery_app.py"), 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) @@ -137,9 +237,7 @@ def remove_dotgithub_folder(): shutil.rmtree(".github") -def generate_random_string( - length, using_digits=False, using_ascii_letters=False, using_punctuation=False -): +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 @@ -233,9 +331,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 @@ -267,22 +363,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(): @@ -293,6 +381,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(): @@ -311,21 +400,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" - ) - ) + 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(): @@ -347,6 +424,9 @@ def main(): if "{{ cookiecutter.open_source_license}}" != "GPLv3": remove_gplv3_files() + if "{{ cookiecutter.username_type }}" == "username": + remove_custom_user_manager_files() + if "{{ cookiecutter.use_pycharm }}".lower() == "n": remove_pycharm_files() @@ -355,21 +435,13 @@ 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 " @@ -383,15 +455,23 @@ def main(): if "{{ cookiecutter.keep_local_envs_in_vcs }}".lower() == "y": append_to_gitignore_file("!.envs/.local/") - if "{{ cookiecutter.frontend_pipeline }}" != "Gulp": + if "{{ cookiecutter.frontend_pipeline }}" in ["None", "Django Compressor"]: remove_gulp_files() + remove_webpack_files() + remove_sass_files() remove_packagejson_file() if "{{ cookiecutter.use_docker }}".lower() == "y": remove_node_dockerfile() + else: + handle_js_runner( + "{{ cookiecutter.frontend_pipeline }}", + use_docker=("{{ cookiecutter.use_docker }}".lower() == "y"), + use_async=("{{ cookiecutter.use_async }}".lower() == "y"), + ) - if "{{ cookiecutter.cloud_provider}}" == "None": + if "{{ cookiecutter.cloud_provider }}" == "None" and "{{ cookiecutter.use_docker }}".lower() == "n": print( - WARNING + "You chose not to use a cloud provider, " + WARNING + "You chose to not use any cloud providers nor Docker, " "media files won't be served in production." + TERMINATOR ) remove_storages_module() diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py index 2845f012c..33dc2e834 100644 --- a/hooks/pre_gen_project.py +++ b/hooks/pre_gen_project.py @@ -17,26 +17,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.11+. Do you want to proceed (y/n)? " + TERMINATOR ) yes_options, no_options = frozenset(["y"]), frozenset(["n"]) while True: @@ -51,35 +53,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..2a9f00b29 --- /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 = ['py311'] + + +# ==== 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 88c8176f1..d6d0ca754 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,26 +1,28 @@ cookiecutter==2.1.1 -sh==1.14.3; sys_platform != "win32" +sh==2.0.4; 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.3 -pre-commit==2.20.0 +black==23.3.0 +isort==5.12.0 +flake8==6.0.0 +django-upgrade==1.14.0 +djlint==1.31.1 +pre-commit==3.3.3 # Testing # ------------------------------------------------------------------------------ -tox==3.27.1 -pytest==7.2.0 -pytest-cookies==0.6.1 -pytest-instafail==0.4.2 +tox==4.6.3 +pytest==7.4.0 +pytest-xdist==3.3.1 +pytest-cookies==0.7.0 +pytest-instafail==0.5.0 pyyaml==6.0 # Scripting # ------------------------------------------------------------------------------ -PyGithub==1.57 -gitpython==3.1.29 +PyGithub==1.59.0 +gitpython==3.1.31 jinja2==3.1.2 -requests==2.28.1 +requests==2.31.0 diff --git a/scripts/create_django_issue.py b/scripts/create_django_issue.py index 5809f393d..d3b3c3792 100644 --- a/scripts/create_django_issue.py +++ b/scripts/create_django_issue.py @@ -141,9 +141,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,11 +175,7 @@ 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) @@ -194,9 +188,7 @@ class GitHubManager: 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 @@ -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,9 +266,7 @@ 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}") def generate(self): @@ -297,9 +279,7 @@ 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 - ) + current_dj, latest_djs = get_all_latest_django_versions(django_max_version=django_max_version) if not latest_djs: sys.exit(0) manager = GitHubManager(current_dj, latest_djs) @@ -309,9 +289,7 @@ def main(django_max_version=None) -> 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") 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..7d43a0b57 100644 --- a/scripts/update_changelog.py +++ b/scripts/update_changelog.py @@ -82,14 +82,20 @@ def group_pulls_by_change_type( grouped_pulls = { "Changed": [], "Fixed": [], + "Documentation": [], "Updated": [], } for pull in pull_requests_list: label_names = {label.name for label in pull.labels} + if "project infrastructure" in label_names: + # Don't mention it in the changelog + continue if "update" in label_names: group_name = "Updated" elif "bug" in label_names: group_name = "Fixed" + elif "docs" in label_names: + group_name = "Documentation" else: group_name = "Changed" grouped_pulls[group_name].append(pull) @@ -148,11 +154,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..09a7082c0 100644 --- a/scripts/update_contributors.py +++ b/scripts/update_contributors.py @@ -44,15 +44,9 @@ def iter_recent_authors(): 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 6f2fde261..bf58e0239 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ except ImportError: from distutils.core import setup # We use calendar versioning -version = "2022.11.26" +version = "2023.06.26" with open("README.rst") as readme_file: long_description = readme_file.read() @@ -13,10 +13,7 @@ with open("README.rst") as readme_file: 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.1", "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.11", "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 3a757fcb7..bb91e329a 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(): @@ -36,6 +43,8 @@ def context(): SUPPORTED_COMBINATIONS = [ + {"username_type": "username"}, + {"username_type": "email"}, {"open_source_license": "MIT"}, {"open_source_license": "BSD"}, {"open_source_license": "GPLv3"}, @@ -56,6 +65,8 @@ SUPPORTED_COMBINATIONS = [ {"cloud_provider": "AWS", "use_whitenoise": "n"}, {"cloud_provider": "GCP", "use_whitenoise": "y"}, {"cloud_provider": "GCP", "use_whitenoise": "n"}, + {"cloud_provider": "Azure", "use_whitenoise": "y"}, + {"cloud_provider": "Azure", "use_whitenoise": "n"}, {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Mailgun"}, {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Mailjet"}, {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Mandrill"}, @@ -82,7 +93,16 @@ SUPPORTED_COMBINATIONS = [ {"cloud_provider": "GCP", "mail_service": "SendinBlue"}, {"cloud_provider": "GCP", "mail_service": "SparkPost"}, {"cloud_provider": "GCP", "mail_service": "Other SMTP"}, - # Note: cloud_providers GCP and None with mail_service Amazon SES is not supported + {"cloud_provider": "Azure", "mail_service": "Mailgun"}, + {"cloud_provider": "Azure", "mail_service": "Mailjet"}, + {"cloud_provider": "Azure", "mail_service": "Mandrill"}, + {"cloud_provider": "Azure", "mail_service": "Postmark"}, + {"cloud_provider": "Azure", "mail_service": "Sendgrid"}, + {"cloud_provider": "Azure", "mail_service": "SendinBlue"}, + {"cloud_provider": "Azure", "mail_service": "SparkPost"}, + {"cloud_provider": "Azure", "mail_service": "Other SMTP"}, + # Note: cloud_providers GCP, Azure, and None + # with mail_service Amazon SES is not supported {"use_async": "y"}, {"use_async": "n"}, {"use_drf": "y"}, @@ -90,6 +110,7 @@ SUPPORTED_COMBINATIONS = [ {"frontend_pipeline": "None"}, {"frontend_pipeline": "Django Compressor"}, {"frontend_pipeline": "Gulp"}, + {"frontend_pipeline": "Webpack"}, {"use_celery": "y"}, {"use_celery": "n"}, {"use_mailhog": "y"}, @@ -113,6 +134,7 @@ SUPPORTED_COMBINATIONS = [ UNSUPPORTED_COMBINATIONS = [ {"cloud_provider": "None", "use_whitenoise": "n"}, {"cloud_provider": "GCP", "mail_service": "Amazon SES"}, + {"cloud_provider": "Azure", "mail_service": "Amazon SES"}, {"cloud_provider": "None", "mail_service": "Amazon SES"}, ] @@ -122,13 +144,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): @@ -169,9 +187,10 @@ def test_flake8_passes(cookies, context_override): pytest.fail(e.stdout.decode()) +@auto_fixable @pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id) def test_black_passes(cookies, context_override): - """Generated project should pass black.""" + """Check whether generated project passes black style.""" result = cookies.bake(extra_context=context_override) try: @@ -187,6 +206,65 @@ 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_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.1", + *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_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.djlint("--lint", "--ignore", f"{autofixable_rules},{ignored_rules}", ".", _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_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"], [ @@ -219,9 +297,7 @@ def test_travis_invokes_pytest(cookies, context, use_docker, expected_test_scrip ("y", "docker-compose -f local.yml run django pytest"), ], ) -def test_gitlab_invokes_flake8_and_pytest( - 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) @@ -233,7 +309,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) @@ -246,9 +324,7 @@ def test_gitlab_invokes_flake8_and_pytest( ("y", "docker-compose -f 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,10 +380,29 @@ def test_error_if_incompatible(cookies, context, invalid_context): ], ) def test_pycharm_docs_removed(cookies, context, use_pycharm, pycharm_docs_exist): - """.""" context.update({"use_pycharm": use_pycharm}) 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..5c60d20d3 100755 --- a/tests/test_docker.sh +++ b/tests/test_docker.sh @@ -14,13 +14,6 @@ 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 @@ -41,3 +34,9 @@ docker-compose -f local.yml run django python manage.py check --fail-level WARNI # Generate the HTML for the documentation docker-compose -f local.yml run docs make html + +# Run npm build script if package.json is present +if [ -f "package.json" ] +then + docker-compose -f local.yml run node npm run build +fi diff --git a/tests/test_hooks.py b/tests/test_hooks.py index 7ca752722..6afdc400b 100644 --- a/tests/test_hooks.py +++ b/tests/test_hooks.py @@ -22,7 +22,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..903d5a53b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,11 @@ [tox] skipsdist = true -envlist = py310,black-template +envlist = py311,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}}/.dockerignore b/{{cookiecutter.project_slug}}/.dockerignore index 5518e60af..7369480e3 100644 --- a/{{cookiecutter.project_slug}}/.dockerignore +++ b/{{cookiecutter.project_slug}}/.dockerignore @@ -8,3 +8,4 @@ .readthedocs.yml .travis.yml venv +.git diff --git a/{{cookiecutter.project_slug}}/.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..ac231ee72 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,21 +22,19 @@ jobs: linter: runs-on: ubuntu-latest steps: - - name: Checkout Code Repository uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: - python-version: "3.10" - cache: pip - cache-dependency-path: | - requirements/base.txt - requirements/local.txt + python-version: '3.11' + {%- if cookiecutter.open_source_license != 'Not open source' %} + # Consider using pre-commit.ci for open source project + {%- endif %} - name: Run pre-commit - uses: pre-commit/action@v2.0.3 + uses: pre-commit/action@v3.0.0 # With no caching at all the entire ci process takes 4m 30s to complete! pytest: @@ -59,35 +57,34 @@ 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 {%- if cookiecutter.use_docker == 'y' %} - name: Build the Stack - run: docker-compose -f local.yml build + run: docker-compose -f local.yml build - name: Run DB Migrations - run: docker-compose -f local.yml run --rm django python manage.py migrate + run: docker-compose -f 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 local.yml run django pytest - name: Tear down the Stack - run: docker-compose -f local.yml down + run: docker-compose -f 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.11' cache: pip cache-dependency-path: | requirements/base.txt @@ -99,5 +96,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..19bb2bc07 100644 --- a/{{cookiecutter.project_slug}}/.gitignore +++ b/{{cookiecutter.project_slug}}/.gitignore @@ -326,6 +326,9 @@ Session.vim # Auto-generated tag files tags +# Redis dump file +dump.rdb + ### Project template {%- if cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'n' %} MailHog @@ -343,4 +346,9 @@ project.css project.min.css vendors.js *.min.js +*.min.js.map +{%- endif %} +{%- if cookiecutter.frontend_pipeline == 'Webpack' %} +{{ cookiecutter.project_slug }}/static/webpack_bundles/ +webpack-stats.json {%- endif %} diff --git a/{{cookiecutter.project_slug}}/.gitlab-ci.yml b/{{cookiecutter.project_slug}}/.gitlab-ci.yml index dbb65fb73..a312a41ae 100644 --- a/{{cookiecutter.project_slug}}/.gitlab-ci.yml +++ b/{{cookiecutter.project_slug}}/.gitlab-ci.yml @@ -7,21 +7,26 @@ variables: POSTGRES_PASSWORD: '' POSTGRES_DB: 'test_{{ cookiecutter.project_slug }}' POSTGRES_HOST_AUTH_METHOD: trust - {% if cookiecutter.use_celery == 'y' -%} + {%- if cookiecutter.use_celery == 'y' %} CELERY_BROKER_URL: 'redis://redis:6379/0' {%- endif %} -flake8: +precommit: stage: lint - image: python:3.10-alpine + image: python:3.11 + variables: + PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit + cache: + paths: + - ${PRE_COMMIT_HOME} before_script: - - pip install -q flake8 + - pip install -q pre-commit script: - - flake8 + - pre-commit run --show-diff-on-failure --color=always --all-files pytest: stage: test - {% if cookiecutter.use_docker == 'y' -%} + {%- if cookiecutter.use_docker == 'y' %} image: docker/compose:1.29.2 tags: - docker @@ -34,19 +39,16 @@ pytest: - docker-compose -f local.yml up -d script: - docker-compose -f local.yml run django pytest - {%- else -%} - image: python:3.10 + {%- else %} + image: python:3.11 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/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 index ab910820e..a9112cf09 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/account_inactive.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/account_inactive.html @@ -2,11 +2,11 @@ {% load i18n %} -{% block head_title %}{% translate "Account Inactive" %}{% endblock %} - +{% block head_title %} + {% translate "Account Inactive" %} +{% endblock head_title %} {% block inner %} -

{% translate "Account Inactive" %}

- -

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

-{% endblock %} +

{% translate "Account Inactive" %}

+

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

+{% endblock inner %} {%- 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 index 03c86724b..057618257 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/base.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/base.html @@ -1,11 +1,14 @@ {% raw %}{% extends "base.html" %} -{% block title %}{% block head_title %}{% endblock head_title %}{% endblock title %} +{% block title %} + {% block head_title %} + {% endblock head_title %} +{% endblock title %} {% block content %} -
-
- {% block inner %}{% endblock %} +
+
+ {% block inner %}{% endblock inner %} +
-
-{% endblock %} +{% endblock content %} {%- 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 index 1faa2b9fd..37770f00c 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email.html @@ -4,76 +4,77 @@ {% load i18n %} {% load crispy_forms_tags %} -{% block head_title %}{% translate "Account" %}{% endblock %} - +{% block head_title %} + {% translate "Account" %} +{% endblock head_title %} {% 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 }} - +

{% translate "E-mail Addresses" %}

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

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

+ + {% csrf_token %} +
+ {% for emailaddress in user.emailaddress_set.all %} +
+ +
+ {% endfor %} +
+ + + +
+
- -{% endblock %} - - + {% 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 inner %} {% block inline_javascript %} -{{ block.super }} - -{% endblock %} + +{% endblock inline_javascript %} {%- 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 index 5e4924c83..40ca4a47b 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email_confirm.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email_confirm.html @@ -3,30 +3,26 @@ {% load i18n %} {% load account %} -{% block head_title %}{% translate "Confirm E-mail Address" %}{% endblock %} - - +{% block head_title %} + {% translate "Confirm E-mail Address" %} +{% endblock head_title %} {% 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 %} +

{% 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 inner %} {%- 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 index 25a292eda..5737afc06 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/login.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/login.html @@ -4,57 +4,50 @@ {% load account socialaccount %} {% load crispy_forms_tags %} -{% block head_title %}{% translate "Sign In" %}{% endblock %} - +{% block head_title %} + {% translate "Sign In" %} +{% endblock head_title %} {% 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 %} +

{% translate "Sign In" %}

+ {% get_providers as socialaccount_providers %} + {% if socialaccount_providers %}

- {% blocktranslate trimmed %} - If you have not created an account yet, then please - sign up first. - {% endblocktranslate %} + {% 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 %} -{% endif %} - - - -{% endblock %} + +{% endblock inner %} {%- 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 index 5edc60478..43ae9ed38 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/logout.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/logout.html @@ -2,19 +2,20 @@ {% load i18n %} -{% block head_title %}{% translate "Sign Out" %}{% endblock %} - +{% block head_title %} + {% translate "Sign Out" %} +{% endblock head_title %} {% block inner %} -

{% translate "Sign Out" %}

- -

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

- -
- {% csrf_token %} - {% if redirect_field_value %} - - {% endif %} - -
-{% endblock %} +

{% translate "Sign Out" %}

+

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

+
+ {% csrf_token %} + {% if redirect_field_value %} + + {% endif %} + +
+{% endblock inner %} {%- 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 index b8dd7ac53..2e6110d5d 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_change.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_change.html @@ -3,15 +3,17 @@ {% load i18n %} {% load crispy_forms_tags %} -{% block head_title %}{% translate "Change Password" %}{% endblock %} - +{% block head_title %} + {% translate "Change Password" %} +{% endblock head_title %} {% block inner %} -

{% translate "Change Password" %}

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

{% translate "Change Password" %}

+
+ {% csrf_token %} + {{ form|crispy }} + +
+{% endblock inner %} {%- 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 index f424b2111..0c184269a 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset.html @@ -4,23 +4,26 @@ {% load account %} {% load crispy_forms_tags %} -{% block head_title %}{% translate "Password Reset" %}{% endblock %} - +{% block head_title %} + {% translate "Password Reset" %} +{% endblock head_title %} {% block inner %} - -

{% translate "Password Reset" %}

- {% if user.is_authenticated %} +

{% 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 %} + {% 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 inner %} {%- 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 index 76d07eb21..a596425bb 100644 --- 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 @@ -3,15 +3,16 @@ {% load i18n %} {% load account %} -{% block head_title %}{% translate "Password Reset" %}{% endblock %} - +{% block head_title %} + {% translate "Password Reset" %} +{% endblock head_title %} {% block inner %} -

{% translate "Password Reset" %}

- - {% if user.is_authenticated %} +

{% 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 %} + {% endif %} +

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

+{% endblock inner %} {%- 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 index ce5d72a6d..a958ba089 100644 --- 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 @@ -2,24 +2,36 @@ {% load i18n %} {% load crispy_forms_tags %} -{% block head_title %}{% translate "Change Password" %}{% endblock %} +{% block head_title %} + {% translate "Change Password" %} +{% endblock head_title %} {% 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 %}

+ {% translate "Bad Token" %} {% else %} - {% if form %} -
- {% csrf_token %} - {{ form|crispy }} - -
- {% else %} -

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

- {% endif %} + {% translate "Change Password" %} {% endif %} -{% endblock %} +

+ {% 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 inner %} {%- 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 index 34123fd53..ee399b404 100644 --- 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 @@ -1,10 +1,12 @@ {% raw %}{% extends "account/base.html" %} {% load i18n %} -{% block head_title %}{% translate "Change Password" %}{% endblock %} +{% block head_title %} + {% translate "Change Password" %} +{% endblock head_title %} {% block inner %} -

{% translate "Change Password" %}

-

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

-{% endblock %} +

{% translate "Change Password" %}

+

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

+{% endblock inner %} {%- 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 index 812410fc0..3efc30874 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_set.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_set.html @@ -3,15 +3,20 @@ {% load i18n %} {% load crispy_forms_tags %} -{% block head_title %}{% translate "Set Password" %}{% endblock %} - +{% block head_title %} + {% translate "Set Password" %} +{% endblock head_title %} {% block inner %} -

{% translate "Set Password" %}

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

{% translate "Set Password" %}

+
+ {% csrf_token %} + {{ form|crispy }} + +
+{% endblock inner %} {%- 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 index 8c1c11aca..54150a474 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup.html @@ -3,21 +3,26 @@ {% load i18n %} {% load crispy_forms_tags %} -{% block head_title %}{% translate "Signup" %}{% endblock %} - +{% block head_title %} + {% translate "Signup" %} +{% endblock head_title %} {% block inner %} -

{% translate "Sign Up" %}

- -

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

- - - -{% endblock %} +

{% translate "Sign Up" %}

+

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

+ +{% endblock inner %} {%- 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 index c2e64d14f..b3472ed6d 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup_closed.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup_closed.html @@ -2,11 +2,11 @@ {% load i18n %} -{% block head_title %}{% translate "Sign Up Closed" %}{% endblock %} - +{% block head_title %} + {% translate "Sign Up Closed" %} +{% endblock head_title %} {% block inner %} -

{% translate "Sign Up Closed" %}

- -

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

-{% endblock %} +

{% translate "Sign Up Closed" %}

+

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

+{% endblock inner %} {%- 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 index be8f1cef9..d71bbc41a 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verification_sent.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verification_sent.html @@ -2,12 +2,13 @@ {% load i18n %} -{% block head_title %}{% translate "Verify Your E-mail Address" %}{% endblock %} - +{% block head_title %} + {% translate "Verify Your E-mail Address" %} +{% endblock head_title %} {% 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 %} +

{% 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 inner %} {%- 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 index 2148a1804..b736581ce 100644 --- 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 @@ -2,21 +2,24 @@ {% load i18n %} -{% block head_title %}{% translate "Verify Your E-mail Address" %}{% endblock %} - +{% block head_title %} + {% translate "Verify Your E-mail Address" %} +{% endblock head_title %} {% 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 +

{% 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 +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 %} +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 inner %} {%- 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..421973e57 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html @@ -1,140 +1,194 @@ -{% 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 %} - - {% block content %} -

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

- {% endblock content %} - -
- - {% block modal %}{% endblock modal %} - - {% block inline_javascript %} +
+ + +
+ {% if messages %} + {% for message in messages %} +
+ {{ message }} + +
+ {% endfor %} + {% endif %} + {% block content %} +

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

+ {% endblock content %} +
+ + {% 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..4e632b015 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,45 @@ {% 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 }}

- {% if object.name %} -

{{ object.name }}

- {% endif %} +
+
+
+

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

+ {% if object.name %}

{{ object.name }}

{% endif %}
- -{% if object == request.user %} - -
- -
- My Info - E-Mail - -
- -
- -{% endif %} - + {% if object == request.user %} + +
+
+ My Info + E-Mail + +
+
+ + {% 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/admin.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py index 6675f483a..d81c0a3b0 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py @@ -10,12 +10,16 @@ User = get_user_model() @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 +34,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/serializers.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py index b5ccabba1..6b26367d0 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py @@ -7,8 +7,16 @@ User = get_user_model() class UserSerializer(serializers.ModelSerializer): 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..508431e4c 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/views.py @@ -13,7 +13,11 @@ User = get_user_model() 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..92e7a74ec 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py @@ -8,6 +8,6 @@ class UsersConfig(AppConfig): def ready(self): try: - import {{ cookiecutter.project_slug }}.users.signals # noqa F401 + import {{ cookiecutter.project_slug }}.users.signals # noqa: F401 except ImportError: pass diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py index 6e1dd9d32..ac5b647d3 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py @@ -2,6 +2,9 @@ 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() @@ -10,6 +13,9 @@ User = get_user_model() 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 +26,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..017ab14e7 --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/managers.py @@ -0,0 +1,34 @@ +from django.contrib.auth.hashers import make_password +from django.contrib.auth.models import UserManager as DjangoUserManager + + +class UserManager(DjangoUserManager): + """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: + raise ValueError("The given email must be set") + 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): + 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): + extra_fields.setdefault("is_staff", True) + extra_fields.setdefault("is_superuser", True) + + if extra_fields.get("is_staff") is not True: + raise ValueError("Superuser must have is_staff=True.") + if extra_fields.get("is_superuser") is not True: + raise ValueError("Superuser must have is_superuser=True.") + + 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..58a439c5d 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 @@ -3,6 +3,8 @@ import django.contrib.auth.validators from django.db import migrations, models import django.utils.timezone +import {{cookiecutter.project_slug}}.users.models + class Migration(migrations.Migration): @@ -40,6 +42,7 @@ class Migration(migrations.Migration): verbose_name="superuser status", ), ), + {%- if cookiecutter.username_type == "username" -%} ( "username", models.CharField( @@ -61,6 +64,14 @@ class Migration(migrations.Migration): 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( @@ -118,7 +129,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..1e4807510 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py @@ -1,7 +1,11 @@ from django.contrib.auth.models import AbstractUser -from django.db.models import CharField +from django.db.models import CharField{% if cookiecutter.username_type == "email" %}, EmailField{% endif %} from django.urls import reverse from django.utils.translation import gettext_lazy as _ +{%- if cookiecutter.username_type == "email" %} + +from {{ cookiecutter.project_slug }}.users.managers import UserManager +{%- endif %} class User(AbstractUser): @@ -11,16 +15,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 + {%- if cookiecutter.username_type == "email" %} + email = EmailField(_("email address"), unique=True) + username = None # type: ignore - def get_absolute_url(self): - """Get url for user's detail view. + USERNAME_FIELD = "email" + REQUIRED_FIELDS = [] + + objects = 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/tests/factories.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py index e30476227..4b86f7985 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py @@ -7,8 +7,9 @@ from factory.django import DjangoModelFactory class UserFactory(DjangoModelFactory): - + {%- if cookiecutter.username_type == "username" %} username = Faker("user_name") + {%- endif %} email = Faker("email") name = Faker("name") @@ -30,4 +31,4 @@ class UserFactory(DjangoModelFactory): class Meta: model = get_user_model() - django_get_or_create = ["username"] + 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..2991d18a9 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 @@ -22,16 +22,28 @@ class TestUserAdmin: 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 + {%- 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 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..334ab1185 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 @@ -4,11 +4,13 @@ from {{ cookiecutter.project_slug }}.users.models import User def test_user_detail(user: User): - assert ( - reverse("api:user-detail", kwargs={"username": user.username}) - == f"/api/users/{user.username}/" - ) + {%- 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..90e84dc7d 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 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..023aad056 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 @@ -24,7 +24,11 @@ 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, } @@ -32,5 +36,10 @@ class TestUserAdminCreationForm: 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..f25af4ee2 --- /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!", + ) + 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!", + ) + 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!", + ) + 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_urls.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py index 7cd056db5..a0d068890 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 @@ -4,11 +4,13 @@ from {{ cookiecutter.project_slug }}.users.models import User def test_detail(user: User): - assert ( - reverse("users:detail", kwargs={"username": user.username}) - == f"/users/{user.username}/" - ) + {%- 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..2c1027038 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 @@ -7,6 +7,7 @@ from django.contrib.sessions.middleware import SessionMiddleware from django.http import HttpRequest, 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 @@ -39,7 +40,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 +73,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 +84,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,7 +96,11 @@ 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 @@ -95,7 +108,11 @@ class TestUserDetailView: 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) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py index 8c8c7e2ea..0ffca17aa 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py @@ -10,5 +10,9 @@ 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..82498e630 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py @@ -9,25 +9,26 @@ User = get_user_model() 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 + assert self.request.user.is_authenticated # for mypy to know that the user is authenticated return self.request.user.get_absolute_url() def get_object(self): @@ -38,11 +39,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 index b712d3239..27595ad1a 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/storages.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/storages.py @@ -22,4 +22,15 @@ class StaticRootGoogleCloudStorage(GoogleCloudStorage): class MediaRootGoogleCloudStorage(GoogleCloudStorage): location = "media" file_overwrite = False +{%- elif cookiecutter.cloud_provider == 'Azure' -%} +from storages.backends.azure_storage import AzureStorage + + +class StaticRootAzureStorage(AzureStorage): + location = "static" + + +class MediaRootAzureStorage(AzureStorage): + location = "media" + file_overwrite = False {%- endif %}