Merge branch 'master' into masavini-master

This commit is contained in:
Bruno Alla 2023-04-15 14:55:04 +01:00
commit a5b14d71e9
No known key found for this signature in database
86 changed files with 1020 additions and 525 deletions

View File

@ -22,8 +22,8 @@ accept and merge pull requests.
{%- endfor %}
</table>
*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

View File

@ -12,41 +12,47 @@ labels: bug
<!-- To assist you best, please include commands that you've run, options you've selected and any relevant logs -->
* 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
```
<summary>
Logs:
<details>

View File

@ -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.

View File

@ -1,6 +1,5 @@
<!-- Thank you for helping us out: your efforts mean a great deal to the project and the community as a whole! -->
## Description
<!-- What's it you're proposing? -->

View File

@ -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 -%}

View File

@ -1377,5 +1377,15 @@
"name": "Arkadiusz Michał Ryś",
"github_login": "arrys",
"twitter_username": ""
},
{
"name": "mpsantos",
"github_login": "mpsantos",
"twitter_username": ""
},
{
"name": "Morten Kaae",
"github_login": "MortenKaae",
"twitter_username": ""
}
]

View File

@ -27,6 +27,11 @@ updates:
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"
@ -34,6 +39,11 @@ updates:
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"
@ -55,6 +65,11 @@ updates:
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"

View File

@ -2,7 +2,7 @@ name: CI
on:
push:
branches: [ "master", "main" ]
branches: ["master", "main"]
pull_request:
concurrency:
@ -25,7 +25,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
@ -38,7 +38,7 @@ jobs:
matrix:
script:
- name: Basic
args: ""
args: "ci_tool=Gitlab"
- name: Celery & DRF
args: "use_celery=y use_drf=y"
- name: Gulp
@ -56,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
@ -73,7 +73,9 @@ jobs:
- name: Gulp
args: "frontend_pipeline=Gulp"
- name: Webpack
args: "frontend_pipeline=Webpack"
args: "frontend_pipeline=Webpack use_heroku=y"
- name: Email Username
args: "username_type=email ci_tool=Github"
name: "Bare metal ${{ matrix.script.name }}"
runs-on: ubuntu-latest
@ -98,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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,7 +22,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

View File

@ -1,4 +1,4 @@
exclude: "{{cookiecutter.project_slug}}"
exclude: "{{cookiecutter.project_slug}}|.github/contributors.json|CHANGELOG.md|CONTRIBUTORS.md"
default_stages: [commit]
repos:
@ -6,17 +6,31 @@ 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.6"
hooks:
- id: prettier
args: ["--tab-width", "2"]
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.1
hooks:
- id: pyupgrade
args: [--py310-plus]
args: [--py311-plus]
exclude: hooks/
- repo: https://github.com/psf/black
rev: 23.1.0
rev: 23.3.0
hooks:
- id: black

View File

@ -3,6 +3,145 @@ All enhancements and patches to Cookiecutter Django will be documented in this f
<!-- GENERATOR_PLACEHOLDER -->
## 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

View File

@ -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/)

View File

@ -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

View File

@ -76,8 +76,8 @@ accept and merge pull requests.
</tr>
</table>
*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
@ -1426,6 +1426,13 @@ Listed in alphabetical order.
</td>
<td></td>
</tr>
<tr>
<td>Morten Kaae</td>
<td>
<a href="https://github.com/MortenKaae">MortenKaae</a>
</td>
<td></td>
</tr>
<tr>
<td>mozillazg</td>
<td>
@ -1440,6 +1447,13 @@ Listed in alphabetical order.
</td>
<td></td>
</tr>
<tr>
<td>mpsantos</td>
<td>
<a href="https://github.com/mpsantos">mpsantos</a>
</td>
<td></td>
</tr>
<tr>
<td>Naveen</td>
<td>
@ -1951,6 +1965,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

117
README.md
View File

@ -12,58 +12,58 @@
Powered by [Cookiecutter](https://github.com/cookiecutter/cookiecutter), Cookiecutter Django is a framework for jumpstarting
production-ready Django projects quickly.
- Documentation: <https://cookiecutter-django.readthedocs.io/en/latest/>
- 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: <https://cookiecutter-django.readthedocs.io/en/latest/>
- 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 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
- 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, 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
- 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:
- 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.
- 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:
------------------------------------------------------------------------
---
<p align="center">
<a href="https://www.feldroy.com/products//two-scoops-of-django-3-x"><img src="https://cdn.shopify.com/s/files/1/0304/6901/products/Two-Scoops-of-Django-3-Alpha-Cover_540x_26507b15-e489-470b-8a97-02773dd498d1_1080x.jpg"></a>
@ -118,6 +118,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
Select an editor to use. The choices are:
@ -188,14 +192,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
@ -203,13 +207,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
@ -219,18 +224,18 @@ Need a stable release? You can find them at <https://github.com/cookiecutter/coo
## Not Exactly What You Want?
This is what I want. *It might not be what you want.* Don't worry, you have options:
This is what I want. _It might not be what you want._ Don't worry, you have options:
### Fork This
If you have differences in your preferred setup, I encourage you to fork this to create your own version.
Once you have your fork working, let me know and I'll add it to a '*Similar Cookiecutter Templates*' list here.
Once you have your fork working, let me know and I'll add it to a '_Similar Cookiecutter Templates_' list here.
It's up to you whether to rename your fork.
If you do rename your fork, I encourage you to submit it to the following places:
- [cookiecutter](https://github.com/cookiecutter/cookiecutter) so it gets listed in the README as a template.
- The cookiecutter [grid](https://www.djangopackages.com/grids/g/cookiecutters/) on Django Packages.
- [cookiecutter](https://github.com/cookiecutter/cookiecutter) so it gets listed in the README as a template.
- The cookiecutter [grid](https://www.djangopackages.com/grids/g/cookiecutters/) on Django Packages.
### Submit a Pull Request
@ -239,17 +244,17 @@ experience better.
## Articles
- [Cookiecutter Django With Amazon RDS](https://haseeburrehman.com/posts/cookiecutter-django-with-amazon-rds/) - Apr, 2, 2021
- [Complete Walkthrough: Blue/Green Deployment to AWS ECS using GitHub actions](https://github.com/Andrew-Chen-Wang/cookiecutter-django-ecs-github) - June 10, 2020
- [Using cookiecutter-django with Google Cloud Storage](https://ahhda.github.io/cloud/gce/django/2019/03/12/using-django-cookiecutter-cloud-storage.html) - Mar. 12, 2019
- [cookiecutter-django with Nginx, Route 53 and ELB](https://msaizar.com/blog/cookiecutter-django-nginx-route-53-and-elb/) - Feb. 12, 2018
- [cookiecutter-django and Amazon RDS](https://msaizar.com/blog/cookiecutter-django-and-amazon-rds/) - Feb. 7, 2018
- [Using Cookiecutter to Jumpstart a Django Project on Windows with PyCharm](https://joshuahunter.com/posts/using-cookiecutter-to-jumpstart-a-django-project-on-windows-with-pycharm/) - May 19, 2017
- [Exploring with Cookiecutter](http://www.snowboardingcoder.com/django/2016/12/03/exploring-with-cookiecutter/) - Dec. 3, 2016
- [Introduction to Cookiecutter-Django](http://krzysztofzuraw.com/blog/2016/django-cookiecutter.html) - Feb. 19, 2016
- [Django and GitLab - Running Continuous Integration and tests with your FREE account](http://dezoito.github.io/2016/05/11/django-gitlab-continuous-integration-phantomjs.html) - May. 11, 2016
- [Development and Deployment of Cookiecutter-Django on Fedora](https://realpython.com/blog/python/development-and-deployment-of-cookiecutter-django-on-fedora/) - Jan. 18, 2016
- [Development and Deployment of Cookiecutter-Django via Docker](https://realpython.com/blog/python/development-and-deployment-of-cookiecutter-django-via-docker/) - Dec. 29, 2015
- [How to create a Django Application using Cookiecutter and Django 1.8](https://www.swapps.io/blog/how-to-create-a-django-application-using-cookiecutter-and-django-1-8/) - Sept. 12, 2015
- [Cookiecutter Django With Amazon RDS](https://haseeburrehman.com/posts/cookiecutter-django-with-amazon-rds/) - Apr, 2, 2021
- [Complete Walkthrough: Blue/Green Deployment to AWS ECS using GitHub actions](https://github.com/Andrew-Chen-Wang/cookiecutter-django-ecs-github) - June 10, 2020
- [Using cookiecutter-django with Google Cloud Storage](https://ahhda.github.io/cloud/gce/django/2019/03/12/using-django-cookiecutter-cloud-storage.html) - Mar. 12, 2019
- [cookiecutter-django with Nginx, Route 53 and ELB](https://msaizar.com/blog/cookiecutter-django-nginx-route-53-and-elb/) - Feb. 12, 2018
- [cookiecutter-django and Amazon RDS](https://msaizar.com/blog/cookiecutter-django-and-amazon-rds/) - Feb. 7, 2018
- [Using Cookiecutter to Jumpstart a Django Project on Windows with PyCharm](https://joshuahunter.com/posts/using-cookiecutter-to-jumpstart-a-django-project-on-windows-with-pycharm/) - May 19, 2017
- [Exploring with Cookiecutter](http://www.snowboardingcoder.com/django/2016/12/03/exploring-with-cookiecutter/) - Dec. 3, 2016
- [Introduction to Cookiecutter-Django](http://krzysztofzuraw.com/blog/2016/django-cookiecutter.html) - Feb. 19, 2016
- [Django and GitLab - Running Continuous Integration and tests with your FREE account](http://dezoito.github.io/2016/05/11/django-gitlab-continuous-integration-phantomjs.html) - May. 11, 2016
- [Development and Deployment of Cookiecutter-Django on Fedora](https://realpython.com/blog/python/development-and-deployment-of-cookiecutter-django-on-fedora/) - Jan. 18, 2016
- [Development and Deployment of Cookiecutter-Django via Docker](https://realpython.com/blog/python/development-and-deployment-of-cookiecutter-django-via-docker/) - Dec. 29, 2015
- [How to create a Django Application using Cookiecutter and Django 1.8](https://www.swapps.io/blog/how-to-create-a-django-application-using-cookiecutter-and-django-1-8/) - Sept. 12, 2015
Have a blog or online publication? Write about your cookiecutter-django tips and tricks, then send us a pull request with the link.

View File

@ -4,7 +4,7 @@
"description": "Behold My Awesome Project!",
"author_name": "Daniel Roy Greenfeld",
"domain_name": "example.com",
"email": "{{ cookiecutter.author_name.lower()|replace(' ', '-') }}@example.com",
"email": "{{ cookiecutter.author_name.lower() | trim() |replace(' ', '-') }}@{{ cookiecutter.domain_name.lower() | trim() }}",
"version": "0.1.0",
"open_source_license": [
"MIT",
@ -13,27 +13,13 @@
"Apache Software License 2.0",
"Not open source"
],
"username_type": ["username", "email"],
"timezone": "UTC",
"windows": "n",
"editor": [
"None",
"PyCharm",
"VS Code"
],
"editor": ["None", "PyCharm", "VS Code"],
"use_docker": "n",
"postgresql_version": [
"14",
"13",
"12",
"11",
"10"
],
"cloud_provider": [
"AWS",
"GCP",
"Azure",
"None"
],
"postgresql_version": ["14", "13", "12", "11", "10"],
"cloud_provider": ["AWS", "GCP", "Azure", "None"],
"mail_service": [
"Mailgun",
"Amazon SES",
@ -47,23 +33,13 @@
],
"use_async": "n",
"use_drf": "n",
"frontend_pipeline": [
"None",
"Django Compressor",
"Gulp",
"Webpack"
],
"frontend_pipeline": ["None", "Django Compressor", "Gulp", "Webpack"],
"use_celery": "n",
"use_mailhog": "n",
"use_sentry": "n",
"use_whitenoise": "n",
"use_heroku": "n",
"ci_tool": [
"None",
"Travis",
"Gitlab",
"Github"
],
"ci_tool": ["None", "Travis", "Gitlab", "Github"],
"keep_local_envs_in_vcs": "y",
"debug": "n"
}
}

View File

@ -37,6 +37,7 @@ Make sure your project is fully committed and pushed up to Bitbucket or Github o
mkvirtualenv --python=/usr/bin/python3.10 my-project-name
pip install -r requirements/production.txt # may take a few minutes
.. note:: We're creating the virtualenv using Python 3.10 (``--python=/usr/bin/python3.10```), although Cookiecutter Django generates a project for Python 3.11. This is because, at time of writing, PythonAnywhere only supports Python 3.10. It shouldn't be a problem, but if is, you may try changing the Python version to 3.11 and see if it works. If it does, please let us know, or even better, submit a pull request to update this section.
Setting environment variables in the console
--------------------------------------------

View File

@ -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,7 +18,7 @@ First things first.
#. Create a virtualenv: ::
$ python3.10 -m venv <virtual env path>
$ python3.11 -m venv <virtual env path>
#. Activate the virtualenv you have just created: ::

View File

@ -4,4 +4,3 @@ Generate a new cookiecutter-django project: ::
For more information refer to
:ref:`Project Generation Options <template-options>`.

View File

@ -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.

View File

@ -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

View File

@ -45,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):
@ -193,7 +211,7 @@ def handle_js_runner(choice, use_docker, use_async):
dev_django_cmd = (
"uvicorn config.asgi:application --reload"
if use_async
else "python manage.py runserver_plus"
else "python manage.py runserver"
)
scripts.update(
{
@ -452,6 +470,9 @@ def main():
if "{{ cookiecutter.open_source_license}}" != "GPLv3":
remove_gplv3_files()
if "{{ cookiecutter.username_type }}" == "username":
remove_custom_user_manager_files()
if "{{ cookiecutter.editor }}".lower() != "PyCharm":
remove_pycharm_files()
@ -461,7 +482,6 @@ def main():
else:
remove_docker_files()
if (
"{{ cookiecutter.use_docker }}".lower() == "y"
and "{{ cookiecutter.cloud_provider}}" != "AWS"

View File

@ -17,6 +17,14 @@ 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 (
@ -36,7 +44,7 @@ if "{{ cookiecutter.use_docker }}".lower() == "n":
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:

View File

@ -1,26 +1,26 @@
cookiecutter==2.1.1
sh==2.0.2; sys_platform != "win32"
sh==2.0.3; sys_platform != "win32"
binaryornot==0.4.4
# Code quality
# ------------------------------------------------------------------------------
black==23.1.0
black==23.3.0
isort==5.12.0
flake8==6.0.0
flake8-isort==6.0.0
pre-commit==3.1.1
pre-commit==3.2.2
# Testing
# ------------------------------------------------------------------------------
tox==4.4.6
pytest==7.2.2
pytest-cookies==0.6.1
pytest-instafail==0.4.2
tox==4.4.12
pytest==7.3.1
pytest-cookies==0.7.0
pytest-instafail==0.5.0
pyyaml==6.0
# Scripting
# ------------------------------------------------------------------------------
PyGithub==1.58.0
PyGithub==1.58.1
gitpython==3.1.31
jinja2==3.1.2
requests==2.28.2

View File

@ -5,7 +5,7 @@ except ImportError:
from distutils.core import setup
# We use calendar versioning
version = "2023.03.04"
version = "2023.04.13"
with open("README.rst") as readme_file:
long_description = readme_file.read()
@ -27,13 +27,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",
],

View File

@ -20,12 +20,6 @@ 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

View File

@ -20,6 +20,11 @@ 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
@pytest.fixture
def context():
@ -36,6 +41,8 @@ def context():
SUPPORTED_COMBINATIONS = [
{"username_type": "username"},
{"username_type": "email"},
{"open_source_license": "MIT"},
{"open_source_license": "BSD"},
{"open_source_license": "GPLv3"},
@ -183,9 +190,10 @@ def test_flake8_passes(cookies, context_override):
pytest.fail(e.stdout.decode())
@pytest.mark.skipif(not AUTOFIXABLE_STYLES, reason="Black is 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:
@ -321,10 +329,29 @@ def test_error_if_incompatible(cookies, context, invalid_context):
],
)
def test_pycharm_docs_removed(cookies, context, editor, pycharm_docs_exist):
"""."""
context.update({"editor": editor})
result = cookies.bake(extra_context=context)
with open(f"{result.project_path}/docs/index.rst") as f:
has_pycharm_docs = "pycharm/configuration" in f.read()
assert has_pycharm_docs is pycharm_docs_exist
def test_trim_domain_email(cookies, context):
"""Check that leading and trailing spaces are trimmed in domain and email."""
context.update(
{
"use_docker": "y",
"domain_name": " example.com ",
"email": " me@example.com ",
}
)
result = cookies.bake(extra_context=context)
assert result.exit_code == 0
prod_django_env = result.project_path / ".envs" / ".production" / ".django"
assert "DJANGO_ALLOWED_HOSTS=.example.com" in prod_django_env.read_text()
base_settings = result.project_path / "config" / "settings" / "base.py"
assert '"me@example.com"' in base_settings.read_text()

View File

@ -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

View File

@ -1,6 +1,6 @@
[tox]
skipsdist = true
envlist = py310,black-template
envlist = py311,black-template
[testenv]
deps = -rrequirements.txt

View File

@ -22,6 +22,6 @@ trim_trailing_whitespace = false
[Makefile]
indent_style = tab
[nginx.conf]
[default.conf]
indent_style = space
indent_size = 2

View File

@ -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 %}

View File

@ -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,14 +22,13 @@ jobs:
linter:
runs-on: ubuntu-latest
steps:
- name: Checkout Code Repository
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.10"
python-version: '3.11'
{%- if cookiecutter.open_source_license != 'Not open source' %}
# Consider using pre-commit.ci for open source project
@ -58,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@v4
with:
python-version: "3.10"
python-version: '3.11'
cache: pip
cache-dependency-path: |
requirements/base.txt
@ -98,5 +96,5 @@ jobs:
pip install -r requirements/local.txt
- name: Test with pytest
run: pytest
run: pytest
{%- endif %}

View File

@ -13,7 +13,7 @@ variables:
precommit:
stage: lint
image: python:3.10
image: python:3.11
variables:
PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
cache:
@ -40,7 +40,7 @@ pytest:
script:
- docker-compose -f local.yml run django pytest
{%- else %}
image: python:3.10
image: python:3.11
tags:
- python
services:

View File

@ -1,33 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="runserver_plus" type="Python.DjangoServer" factoryName="Django server" singleton="true">
<module name="{{ cookiecutter.project_slug }}" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
<env name="DJANGO_SETTINGS_MODULE" value="config.settings.local" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<PathMappingSettings>
<option name="pathMappings">
<list>
<mapping local-root="$PROJECT_DIR$" remote-root="/app" />
</list>
</option>
</PathMappingSettings>
<option name="launchJavascriptDebuger" value="false" />
<option name="port" value="8000" />
<option name="host" value="0.0.0.0" />
<option name="additionalOptions" value="" />
<option name="browserUrl" value="" />
<option name="runTestServer" value="false" />
<option name="runNoReload" value="false" />
<option name="useCustomRunCommand" value="true" />
<option name="customRunCommand" value="runserver_plus" />
<method />
</configuration>
</component>

View File

@ -1,4 +1,4 @@
exclude: "^docs/|/migrations/"
exclude: '^docs/|/migrations/'
default_stages: [commit]
repos:
@ -7,16 +7,31 @@ repos:
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: check-docstring-first
- id: detect-private-key
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.0.0-alpha.6
hooks:
- id: prettier
args: ['--tab-width', '2', '--single-quote']
exclude: {{cookiecutter.project_slug}}/templates/
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.1
hooks:
- id: pyupgrade
args: [--py310-plus]
args: [--py311-plus]
- repo: https://github.com/psf/black
rev: 23.1.0
rev: 23.3.0
hooks:
- id: black
@ -29,7 +44,7 @@ repos:
rev: 6.0.0
hooks:
- id: flake8
args: ["--config=setup.cfg"]
args: ['--config=setup.cfg']
additional_dependencies: [flake8-isort]
# sets up .pre-commit-ci.yaml to ensure pre-commit dependencies stay up to date

View File

@ -8,7 +8,7 @@ version: 2
build:
os: ubuntu-22.04
tools:
python: "3.10"
python: '3.11'
# Build documentation in the docs/ directory with Sphinx
sphinx:

View File

@ -2,7 +2,7 @@ dist: focal
language: python
python:
- "3.10"
- "3.11"
services:
- {% if cookiecutter.use_docker == 'y' %}docker{% else %}postgresql{% endif %}
@ -37,7 +37,7 @@ jobs:
- sudo apt-get install -qq libsqlite3-dev libxml2 libxml2-dev libssl-dev libbz2-dev wget curl llvm
language: python
python:
- "3.10"
- "3.11"
install:
- pip install -r requirements/local.txt
script:

View File

@ -18,11 +18,11 @@ Moved to [settings](http://cookiecutter-django.readthedocs.io/en/latest/settings
### Setting Up Your Users
- To create a **normal user account**, just go to Sign Up and fill out the form. Once you submit it, you'll see a "Verify Your E-mail Address" page. Go to your console to see a simulated email verification message. Copy the link into your browser. Now the user's email should be verified and ready to go.
- To create a **normal user account**, just go to Sign Up and fill out the form. Once you submit it, you'll see a "Verify Your E-mail Address" page. Go to your console to see a simulated email verification message. Copy the link into your browser. Now the user's email should be verified and ready to go.
- To create a **superuser account**, use this command:
- To create a **superuser account**, use this command:
$ python manage.py createsuperuser
$ python manage.py createsuperuser
For convenience, you can keep your normal user logged in on Chrome and your superuser logged in on Firefox (or similar), so that you can see how the site behaves for both kinds of users.
@ -56,23 +56,23 @@ This app comes with Celery.
To run a celery worker:
``` bash
```bash
cd {{cookiecutter.project_slug}}
celery -A config.celery_app worker -l info
```
Please note: For Celery's import magic to work, it is important *where* the celery commands are run. If you are in the same folder with *manage.py*, you should be right.
Please note: For Celery's import magic to work, it is important _where_ the celery commands are run. If you are in the same folder with _manage.py_, you should be right.
To run [periodic tasks](https://docs.celeryq.dev/en/stable/userguide/periodic-tasks.html), you'll need to start the celery beat scheduler service. You can start it as a standalone process:
``` bash
```bash
cd {{cookiecutter.project_slug}}
celery -A config.celery_app beat
```
or you can embed the beat service inside a worker with the `-B` option (not recommended for production use):
``` bash
```bash
cd {{cookiecutter.project_slug}}
celery -A config.celery_app worker -B -l info
```

View File

@ -1,7 +1,5 @@
ARG PYTHON_VERSION=3.10-slim-bullseye
# define an alias for the specfic python version used in this file.
FROM python:${PYTHON_VERSION} as python
FROM python:3.11.3-slim-bullseye as python
# Python build stage
FROM python as python-build-stage

View File

@ -9,5 +9,5 @@ python manage.py migrate
{%- if cookiecutter.use_async == 'y' %}
exec uvicorn config.asgi:application --host 0.0.0.0 --reload --reload-include '*.html'
{%- else %}
exec python manage.py runserver_plus 0.0.0.0:8000
exec python manage.py runserver 0.0.0.0:8000
{%- endif %}

View File

@ -1,7 +1,5 @@
ARG PYTHON_VERSION=3.10-slim-bullseye
# define an alias for the specfic python version used in this file.
FROM python:${PYTHON_VERSION} as python
FROM python:3.11.3-slim-bullseye as python
# Python build stage

View File

@ -1,5 +1,3 @@
ARG PYTHON_VERSION=3.10-slim-bullseye
{% if cookiecutter.frontend_pipeline in ['Gulp', 'Webpack'] -%}
FROM node:16-bullseye-slim as client-builder
@ -26,9 +24,8 @@ ENV DJANGO_AZURE_ACCOUNT_NAME=${DJANGO_AZURE_ACCOUNT_NAME}
RUN npm run build
{%- endif %}
# define an alias for the specfic python version used in this file.
FROM python:${PYTHON_VERSION} as python
FROM python:3.11.3-slim-bullseye as python
# Python build stage
FROM python as python-build-stage

View File

@ -1,7 +1,7 @@
server {
listen 80;
server_name localhost;
location /media/ {
alias /usr/share/nginx/media/;
}
listen 80;
server_name localhost;
location /media/ {
alias /usr/share/nginx/media/;
}
}

View File

@ -1,4 +1,4 @@
FROM traefik:2.9.8
FROM traefik:2.9.10
RUN mkdir -p /etc/traefik/acme \
&& touch /etc/traefik/acme/acme.json \
&& chmod 600 /etc/traefik/acme/acme.json

View File

@ -4,7 +4,7 @@ log:
entryPoints:
web:
# http
address: ":80"
address: ':80'
http:
# https://docs.traefik.io/routing/entrypoints/#entrypoint
redirections:
@ -13,18 +13,18 @@ entryPoints:
web-secure:
# https
address: ":443"
address: ':443'
{%- if cookiecutter.use_celery == 'y' %}
flower:
address: ":5555"
address: ':5555'
{%- endif %}
certificatesResolvers:
letsencrypt:
# https://docs.traefik.io/master/https/acme/#lets-encrypt
acme:
email: "{{ cookiecutter.email }}"
email: '{{ cookiecutter.email }}'
storage: /etc/traefik/acme/acme.json
# https://docs.traefik.io/master/https/acme/#httpchallenge
httpChallenge:
@ -34,9 +34,9 @@ http:
routers:
web-secure-router:
{%- if cookiecutter.domain_name.count('.') == 1 %}
rule: "Host(`{{ cookiecutter.domain_name }}`) || Host(`www.{{ cookiecutter.domain_name }}`)"
rule: 'Host(`{{ cookiecutter.domain_name }}`) || Host(`www.{{ cookiecutter.domain_name }}`)'
{%- else %}
rule: "Host(`{{ cookiecutter.domain_name }}`)"
rule: 'Host(`{{ cookiecutter.domain_name }}`)'
{%- endif %}
entryPoints:
- web-secure
@ -49,7 +49,7 @@ http:
{%- if cookiecutter.use_celery == 'y' %}
flower-secure-router:
rule: "Host(`{{ cookiecutter.domain_name }}`)"
rule: 'Host(`{{ cookiecutter.domain_name }}`)'
entryPoints:
- flower
service: flower
@ -60,7 +60,11 @@ http:
{%- if cookiecutter.cloud_provider == 'None' %}
web-media-router:
rule: "Host(`{{ cookiecutter.domain_name }}`) && PathPrefix(`/media/`)"
{%- if cookiecutter.domain_name.count('.') == 1 %}
rule: '(Host(`{{ cookiecutter.domain_name }}`) || Host(`www.{{ cookiecutter.domain_name }}`)) && PathPrefix(`/media/`)'
{%- else %}
rule: 'Host(`{{ cookiecutter.domain_name }}`) && PathPrefix(`/media/`)'
{%- endif %}
entryPoints:
- web-secure
middlewares:
@ -75,7 +79,7 @@ http:
# https://docs.traefik.io/master/middlewares/headers/#hostsproxyheaders
# https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
headers:
hostsProxyHeaders: ["X-CSRFToken"]
hostsProxyHeaders: ['X-CSRFToken']
services:
django:

View File

@ -225,8 +225,6 @@ FIXTURE_DIRS = (str(APPS_DIR / "fixtures"),)
SESSION_COOKIE_HTTPONLY = True
# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-httponly
CSRF_COOKIE_HTTPONLY = True
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-browser-xss-filter
SECURE_BROWSER_XSS_FILTER = True
# https://docs.djangoproject.com/en/dev/ref/settings/#x-frame-options
X_FRAME_OPTIONS = "DENY"
@ -314,9 +312,15 @@ CELERY_TASK_SEND_SENT_EVENT = True
# ------------------------------------------------------------------------------
ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True)
# https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_AUTHENTICATION_METHOD = "username"
ACCOUNT_AUTHENTICATION_METHOD = "{{cookiecutter.username_type}}"
# https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_EMAIL_REQUIRED = True
{%- if cookiecutter.username_type == "email" %}
# https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_USERNAME_REQUIRED = False
# https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
{%- endif %}
# https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
# https://django-allauth.readthedocs.io/en/latest/configuration.html

View File

@ -47,15 +47,15 @@ EMAIL_BACKEND = env(
# WhiteNoise
# ------------------------------------------------------------------------------
# http://whitenoise.evans.io/en/latest/django.html#using-whitenoise-in-development
INSTALLED_APPS = ["whitenoise.runserver_nostatic"] + INSTALLED_APPS # noqa F405
INSTALLED_APPS = ["whitenoise.runserver_nostatic"] + INSTALLED_APPS # noqa: F405
{% endif %}
# django-debug-toolbar
# ------------------------------------------------------------------------------
# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#prerequisites
INSTALLED_APPS += ["debug_toolbar"] # noqa F405
INSTALLED_APPS += ["debug_toolbar"] # noqa: F405
# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#middleware
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] # noqa F405
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] # noqa: F405
# https://django-debug-toolbar.readthedocs.io/en/latest/configuration.html#debug-toolbar-config
DEBUG_TOOLBAR_CONFIG = {
"DISABLE_PANELS": ["debug_toolbar.panels.redirects.RedirectsPanel"],
@ -82,7 +82,7 @@ if env("USE_DOCKER") == "yes":
# django-extensions
# ------------------------------------------------------------------------------
# https://django-extensions.readthedocs.io/en/latest/installation_instructions.html#configuration
INSTALLED_APPS += ["django_extensions"] # noqa F405
INSTALLED_APPS += ["django_extensions"] # noqa: F405
{% if cookiecutter.use_celery == 'y' -%}
# Celery
@ -98,7 +98,7 @@ CELERY_TASK_EAGER_PROPAGATES = True
{%- if cookiecutter.frontend_pipeline == 'Webpack' %}
# django-webpack-loader
# ------------------------------------------------------------------------------
WEBPACK_LOADER["DEFAULT"]["CACHE"] = not DEBUG # noqa F405
WEBPACK_LOADER["DEFAULT"]["CACHE"] = not DEBUG # noqa: F405
{%- endif %}
# Your stuff...

View File

@ -2,8 +2,10 @@
import logging
import sentry_sdk
{%- if cookiecutter.use_celery == 'y' %}
from sentry_sdk.integrations.celery import CeleryIntegration
{%- endif %}
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.logging import LoggingIntegration
@ -22,7 +24,7 @@ ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["{{ cookiecutter.domai
# DATABASES
# ------------------------------------------------------------------------------
DATABASES["default"]["CONN_MAX_AGE"] = env.int("CONN_MAX_AGE", default=60) # noqa F405
DATABASES["default"]["CONN_MAX_AGE"] = env.int("CONN_MAX_AGE", default=60) # noqa: F405
# CACHES
# ------------------------------------------------------------------------------
@ -68,7 +70,7 @@ SECURE_CONTENT_TYPE_NOSNIFF = env.bool(
# STORAGES
# ------------------------------------------------------------------------------
# https://django-storages.readthedocs.io/en/latest/#installation
INSTALLED_APPS += ["storages"] # noqa F405
INSTALLED_APPS += ["storages"] # noqa: F405
{%- endif -%}
{% if cookiecutter.cloud_provider == 'AWS' %}
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
@ -159,7 +161,7 @@ ADMIN_URL = env("DJANGO_ADMIN_URL")
# Anymail
# ------------------------------------------------------------------------------
# https://anymail.readthedocs.io/en/stable/installation/#installing-anymail
INSTALLED_APPS += ["anymail"] # noqa F405
INSTALLED_APPS += ["anymail"] # noqa: F405
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
# https://anymail.readthedocs.io/en/stable/installation/#anymail-settings-reference
{%- if cookiecutter.mail_service == 'Mailgun' %}
@ -241,7 +243,7 @@ COMPRESS_STORAGE = "compressor.storage.GzipCompressorFileStorage"
COMPRESS_STORAGE = STATICFILES_STORAGE
{%- endif %}
# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_URL
COMPRESS_URL = STATIC_URL{% if cookiecutter.use_whitenoise == 'y' or cookiecutter.cloud_provider == 'None' %} # noqa F405{% endif %}
COMPRESS_URL = STATIC_URL{% if cookiecutter.use_whitenoise == 'y' or cookiecutter.cloud_provider == 'None' %} # noqa: F405{% endif %}
{%- if cookiecutter.use_whitenoise == 'y' %}
# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_OFFLINE
COMPRESS_OFFLINE = True # Offline compression is required when using Whitenoise
@ -259,7 +261,7 @@ COMPRESS_FILTERS = {
# Collectfast
# ------------------------------------------------------------------------------
# https://github.com/antonagestam/collectfast#installation
INSTALLED_APPS = ["collectfast"] + INSTALLED_APPS # noqa F405
INSTALLED_APPS = ["collectfast"] + INSTALLED_APPS # noqa: F405
{% endif %}
# LOGGING
# ------------------------------------------------------------------------------
@ -373,7 +375,7 @@ sentry_sdk.init(
# django-rest-framework
# -------------------------------------------------------------------------------
# Tools that generate code samples can use SERVERS to point to the correct domain
SPECTACULAR_SETTINGS["SERVERS"] = [ # noqa F405
SPECTACULAR_SETTINGS["SERVERS"] = [ # noqa: F405
{"url": "https://{{ cookiecutter.domain_name }}", "description": "Production server"}
]

View File

@ -27,12 +27,12 @@ EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
# DEBUGGING FOR TEMPLATES
# ------------------------------------------------------------------------------
TEMPLATES[0]["OPTIONS"]["debug"] = True # type: ignore # noqa F405
TEMPLATES[0]["OPTIONS"]["debug"] = True # type: ignore # noqa: F405
{%- if cookiecutter.frontend_pipeline == 'Webpack' %}
# django-webpack-loader
# ------------------------------------------------------------------------------
WEBPACK_LOADER["DEFAULT"][ # noqa F405
WEBPACK_LOADER["DEFAULT"][ # noqa: F405
"LOADER_CLASS"
] = "webpack_loader.loader.FakeWebpackLoader"

View File

@ -3,29 +3,29 @@
////////////////////////////////
// Gulp and package
const { src, dest, parallel, series, watch } = require('gulp')
const pjson = require('./package.json')
const { src, dest, parallel, series, watch } = require('gulp');
const pjson = require('./package.json');
// Plugins
const autoprefixer = require('autoprefixer')
const browserSync = require('browser-sync').create()
const concat = require('gulp-concat')
const autoprefixer = require('autoprefixer');
const browserSync = require('browser-sync').create();
const concat = require('gulp-concat');
const tildeImporter = require('node-sass-tilde-importer');
const cssnano = require ('cssnano')
const imagemin = require('gulp-imagemin')
const pixrem = require('pixrem')
const plumber = require('gulp-plumber')
const postcss = require('gulp-postcss')
const reload = browserSync.reload
const rename = require('gulp-rename')
const sass = require('gulp-sass')(require('sass'))
const spawn = require('child_process').spawn
const uglify = require('gulp-uglify-es').default
const cssnano = require('cssnano');
const imagemin = require('gulp-imagemin');
const pixrem = require('pixrem');
const plumber = require('gulp-plumber');
const postcss = require('gulp-postcss');
const reload = browserSync.reload;
const rename = require('gulp-rename');
const sass = require('gulp-sass')(require('sass'));
const spawn = require('child_process').spawn;
const uglify = require('gulp-uglify-es').default;
// Relative paths function
function pathsConfig(appName) {
this.app = `./${pjson.name}`
const vendorsRoot = 'node_modules'
this.app = `./${pjson.name}`;
const vendorsRoot = 'node_modules';
return {
vendorsJs: [
@ -39,10 +39,10 @@ function pathsConfig(appName) {
fonts: `${this.app}/static/fonts`,
images: `${this.app}/static/images`,
js: `${this.app}/static/js`,
}
};
}
const paths = pathsConfig()
const paths = pathsConfig();
////////////////////////////////
// Tasks
@ -51,27 +51,27 @@ const paths = pathsConfig()
// Styles autoprefixing and minification
function styles() {
const processCss = [
autoprefixer(), // adds vendor prefixes
pixrem(), // add fallbacks for rem units
]
autoprefixer(), // adds vendor prefixes
pixrem(), // add fallbacks for rem units
];
const minifyCss = [
cssnano({ preset: 'default' }) // minify result
]
cssnano({ preset: 'default' }), // minify result
];
return src(`${paths.sass}/project.scss`)
.pipe(sass({
importer: tildeImporter,
includePaths: [
paths.sass
]
}).on('error', sass.logError))
.pipe(
sass({
importer: tildeImporter,
includePaths: [paths.sass],
}).on('error', sass.logError),
)
.pipe(plumber()) // Checks for errors
.pipe(postcss(processCss))
.pipe(dest(paths.css))
.pipe(rename({ suffix: '.min' }))
.pipe(postcss(minifyCss)) // Minifies the result
.pipe(dest(paths.css))
.pipe(dest(paths.css));
}
// Javascript minification
@ -80,7 +80,7 @@ function scripts() {
.pipe(plumber()) // Checks for errors
.pipe(uglify()) // Minifies the js
.pipe(rename({ suffix: '.min' }))
.pipe(dest(paths.js))
.pipe(dest(paths.js));
}
// Vendor Javascript minification
@ -91,97 +91,91 @@ function vendorScripts() {
.pipe(plumber()) // Checks for errors
.pipe(uglify()) // Minifies the js
.pipe(rename({ suffix: '.min' }))
.pipe(dest(paths.js, { sourcemaps: '.' }))
.pipe(dest(paths.js, { sourcemaps: '.' }));
}
// Image compression
function imgCompression() {
return src(`${paths.images}/*`)
.pipe(imagemin()) // Compresses PNG, JPEG, GIF and SVG images
.pipe(dest(paths.images))
.pipe(dest(paths.images));
}
{%- if cookiecutter.use_async == 'y' -%}
// Run django server
function asyncRunServer() {
const cmd = spawn('gunicorn', [
'config.asgi', '-k', 'uvicorn.workers.UvicornWorker', '--reload'
], {stdio: 'inherit'}
)
cmd.on('close', function(code) {
console.log('gunicorn exited with code ' + code)
const cmd = spawn(
'gunicorn',
['config.asgi', '-k', 'uvicorn.workers.UvicornWorker', '--reload'],
{stdio: 'inherit'},
);
cmd.on('close', function (code) {
console.log('gunicorn exited with code ' + code);
})
}
{%- else %}
// Run django server
function runServer(cb) {
const cmd = spawn('python', ['manage.py', 'runserver'], {stdio: 'inherit'})
cmd.on('close', function(code) {
console.log('runServer exited with code ' + code)
cb(code)
})
const cmd = spawn('python', ['manage.py', 'runserver'], { stdio: 'inherit' });
cmd.on('close', function (code) {
console.log('runServer exited with code ' + code);
cb(code);
});
}
{%- endif %}
// Browser sync server for live reload
function initBrowserSync() {
browserSync.init(
[
`${paths.css}/*.css`,
`${paths.js}/*.js`,
`${paths.templates}/*.html`
], {
[`${paths.css}/*.css`, `${paths.js}/*.js`, `${paths.templates}/*.html`],
{
{%- if cookiecutter.use_docker == 'y' %}
// https://www.browsersync.io/docs/options/#option-open
// Disable as it doesn't work from inside a container
open: false,
{%- endif %}
// https://www.browsersync.io/docs/options/#option-proxy
proxy: {
proxy: {
{%- if cookiecutter.use_docker == 'n' %}
target: '127.0.0.1:8000',
{%- else %}
target: 'django:8000',
{%- endif %}
proxyReq: [
function(proxyReq, req) {
// Assign proxy "host" header same as current request at Browsersync server
proxyReq.setHeader('Host', req.headers.host)
}
]
}
}
)
function (proxyReq, req) {
// Assign proxy 'host' header same as current request at Browsersync server
proxyReq.setHeader('Host', req.headers.host);
},
],
},
},
);
}
// Watch
function watchPaths() {
watch(`${paths.sass}/*.scss`{% if cookiecutter.windows == 'y' %}, { usePolling: true }{% endif %}, styles)
watch(`${paths.templates}/**/*.html`{% if cookiecutter.windows == 'y' %}, { usePolling: true }{% endif %}).on("change", reload)
watch([`${paths.js}/*.js`, `!${paths.js}/*.min.js`]{% if cookiecutter.windows == 'y' %}, { usePolling: true }{% endif %}, scripts).on("change", reload)
watch(`${paths.sass}/*.scss`{% if cookiecutter.windows == 'y' %}, { usePolling: true }{% endif %}, styles);
watch(`${paths.templates}/**/*.html`{% if cookiecutter.windows == 'y' %}, { usePolling: true }{% endif %}).on('change', reload);
watch([`${paths.js}/*.js`, `!${paths.js}/*.min.js`]{% if cookiecutter.windows == 'y' %}, { usePolling: true }{% endif %}, scripts).on(
'change',
reload,
);
}
// Generate all assets
const generateAssets = parallel(
styles,
scripts,
vendorScripts,
imgCompression
)
const generateAssets = parallel(styles, scripts, vendorScripts, imgCompression);
// Set up dev environment
const dev = parallel(
{%- if cookiecutter.use_docker == 'n' %}
{%- if cookiecutter.use_async == 'y' %}
asyncRunServer,
{%- else %}
runServer,
{%- endif %}
{%- endif %}
initBrowserSync,
watchPaths
)
{%- if cookiecutter.use_docker == 'n' %}
{%- if cookiecutter.use_async == 'y' %}
const dev = parallel(asyncRunServer, initBrowserSync, watchPaths);
{%- else %}
const dev = parallel(runServer, initBrowserSync, watchPaths);
{%- endif %}
{%- else %}
const dev = parallel(initBrowserSync, watchPaths);
{%- endif %}
exports.default = series(generateAssets, dev)
exports["generate-assets"] = generateAssets
exports["dev"] = dev
exports.default = series(generateAssets, dev);
exports['generate-assets'] = generateAssets;
exports['dev'] = dev;

View File

@ -25,7 +25,7 @@ services:
- ./.envs/.local/.django
- ./.envs/.local/.postgres
ports:
- "8000:8000"
- '8000:8000'
command: /start
postgres:
@ -53,7 +53,7 @@ services:
- ./config:/app/config:z
- ./{{ cookiecutter.project_slug }}:/app/{{ cookiecutter.project_slug }}:z
ports:
- "9000:9000"
- '9000:9000'
command: /start-docs
{%- if cookiecutter.use_mailhog == 'y' %}
@ -101,7 +101,7 @@ services:
image: {{ cookiecutter.project_slug }}_local_flower
container_name: {{ cookiecutter.project_slug }}_local_flower
ports:
- "5555:5555"
- '5555:5555'
command: /start-flower
{%- endif %}
@ -121,10 +121,10 @@ services:
- /app/node_modules
command: npm run dev
ports:
- "3000:3000"
- '3000:3000'
{%- if cookiecutter.frontend_pipeline == 'Gulp' %}
# Expose browsersync UI: https://www.browsersync.io/docs/options/#option-ui
- "3001:3001"
- '3001:3001'
{%- endif %}
{%- endif %}

View File

@ -60,10 +60,10 @@ services:
volumes:
- production_traefik:/etc/traefik/acme
ports:
- "0.0.0.0:80:80"
- "0.0.0.0:443:443"
- '0.0.0.0:80:80'
- '0.0.0.0:443:443'
{%- if cookiecutter.use_celery == 'y' %}
- "0.0.0.0:5555:5555"
- '0.0.0.0:5555:5555'
{%- endif %}
redis:

View File

@ -1,6 +1,6 @@
pytz==2022.7.1 # https://github.com/stub42/pytz
pytz==2023.3 # https://github.com/stub42/pytz
python-slugify==8.0.1 # https://github.com/un33k/python-slugify
Pillow==9.4.0 # https://github.com/python-pillow/Pillow
Pillow==9.5.0 # https://github.com/python-pillow/Pillow
{%- if cookiecutter.frontend_pipeline == 'Django Compressor' %}
{%- if cookiecutter.windows == 'y' and cookiecutter.use_docker == 'n' %}
rcssmin==1.1.0 --install-option="--without-c-extensions" # https://github.com/ndparker/rcssmin
@ -12,27 +12,27 @@ argon2-cffi==21.3.0 # https://github.com/hynek/argon2_cffi
{%- if cookiecutter.use_whitenoise == 'y' %}
whitenoise==6.4.0 # https://github.com/evansd/whitenoise
{%- endif %}
redis==4.5.1 # https://github.com/redis/redis-py
redis==4.5.4 # https://github.com/redis/redis-py
{%- if cookiecutter.use_docker == "y" or cookiecutter.windows == "n" %}
hiredis==2.2.2 # https://github.com/redis/hiredis-py
{%- endif %}
{%- if cookiecutter.use_celery == "y" %}
celery==5.2.7 # pyup: < 6.0 # https://github.com/celery/celery
django-celery-beat==2.4.0 # https://github.com/celery/django-celery-beat
django-celery-beat==2.5.0 # https://github.com/celery/django-celery-beat
{%- if cookiecutter.use_docker == 'y' %}
flower==1.2.0 # https://github.com/mher/flower
{%- endif %}
{%- endif %}
{%- if cookiecutter.use_async == 'y' %}
uvicorn[standard]==0.20.0 # https://github.com/encode/uvicorn
uvicorn[standard]==0.21.1 # https://github.com/encode/uvicorn
{%- endif %}
# Django
# ------------------------------------------------------------------------------
django==4.0.10 # pyup: < 4.1 # https://www.djangoproject.com/
django==4.1.8 # pyup: < 4.2 # https://www.djangoproject.com/
django-environ==0.10.0 # https://github.com/joke2k/django-environ
django-model-utils==4.3.1 # https://github.com/jazzband/django-model-utils
django-allauth==0.52.0 # https://github.com/pennersr/django-allauth
django-allauth==0.54.0 # https://github.com/pennersr/django-allauth
django-crispy-forms==2.0 # https://github.com/django-crispy-forms/django-crispy-forms
crispy-bootstrap5==0.7 # https://github.com/django-crispy-forms/crispy-bootstrap5
{%- if cookiecutter.frontend_pipeline == 'Django Compressor' %}
@ -44,7 +44,7 @@ django-redis==5.2.0 # https://github.com/jazzband/django-redis
djangorestframework==3.14.0 # https://github.com/encode/django-rest-framework
django-cors-headers==3.14.0 # https://github.com/adamchainz/django-cors-headers
# DRF-spectacular for api documentation
drf-spectacular==0.26.0 # https://github.com/tfranzel/drf-spectacular
drf-spectacular==0.26.1 # https://github.com/tfranzel/drf-spectacular
{%- endif %}
{%- if cookiecutter.frontend_pipeline == 'Webpack' %}
django-webpack-loader==1.8.1 # https://github.com/django-webpack/django-webpack-loader

View File

@ -1,24 +1,24 @@
-r base.txt
Werkzeug[watchdog]==2.2.3 # https://github.com/pallets/werkzeug
ipdb==0.13.11 # https://github.com/gotcha/ipdb
ipdb==0.13.13 # https://github.com/gotcha/ipdb
{%- if cookiecutter.use_docker == 'y' %}
psycopg2==2.9.5 # https://github.com/psycopg/psycopg2
psycopg2==2.9.6 # https://github.com/psycopg/psycopg2
{%- else %}
psycopg2-binary==2.9.5 # https://github.com/psycopg/psycopg2
psycopg2-binary==2.9.6 # https://github.com/psycopg/psycopg2
{%- endif %}
{%- if cookiecutter.use_async == 'y' or cookiecutter.use_celery == 'y' %}
watchfiles==0.18.1 # https://github.com/samuelcolvin/watchfiles
watchfiles==0.19.0 # https://github.com/samuelcolvin/watchfiles
{%- endif %}
# Testing
# ------------------------------------------------------------------------------
mypy==1.1.1 # https://github.com/python/mypy
django-stubs==1.15.0 # https://github.com/typeddjango/django-stubs
pytest==7.2.2 # https://github.com/pytest-dev/pytest
pytest-sugar==0.9.6 # https://github.com/Frozenball/pytest-sugar
django-stubs==1.16.0 # https://github.com/typeddjango/django-stubs
pytest==7.3.1 # https://github.com/pytest-dev/pytest
pytest-sugar==0.9.7 # https://github.com/Frozenball/pytest-sugar
{%- if cookiecutter.use_drf == "y" %}
djangorestframework-stubs==1.9.1 # https://github.com/typeddjango/djangorestframework-stubs
djangorestframework-stubs==1.10.0 # https://github.com/typeddjango/djangorestframework-stubs
{%- endif %}
# Documentation
@ -30,19 +30,19 @@ sphinx-autobuild==2021.3.14 # https://github.com/GaretJax/sphinx-autobuild
# ------------------------------------------------------------------------------
flake8==6.0.0 # https://github.com/PyCQA/flake8
flake8-isort==6.0.0 # https://github.com/gforcada/flake8-isort
coverage==7.2.1 # https://github.com/nedbat/coveragepy
black==23.1.0 # https://github.com/psf/black
coverage==7.2.2 # https://github.com/nedbat/coveragepy
black==23.3.0 # https://github.com/psf/black
pylint-django==2.5.3 # https://github.com/PyCQA/pylint-django
{%- if cookiecutter.use_celery == 'y' %}
pylint-celery==0.3 # https://github.com/PyCQA/pylint-celery
{%- endif %}
pre-commit==3.1.1 # https://github.com/pre-commit/pre-commit
pre-commit==3.2.2 # https://github.com/pre-commit/pre-commit
# Django
# ------------------------------------------------------------------------------
factory-boy==3.2.1 # https://github.com/FactoryBoy/factory_boy
django-debug-toolbar==3.8.1 # https://github.com/jazzband/django-debug-toolbar
django-debug-toolbar==4.0.0 # https://github.com/jazzband/django-debug-toolbar
django-extensions==3.2.1 # https://github.com/django-extensions/django-extensions
django-coverage-plugin==3.0.0 # https://github.com/nedbat/django_coverage_plugin
pytest-django==4.5.2 # https://github.com/pytest-dev/pytest-django

View File

@ -3,12 +3,12 @@
-r base.txt
gunicorn==20.1.0 # https://github.com/benoitc/gunicorn
psycopg2==2.9.5 # https://github.com/psycopg/psycopg2
psycopg2==2.9.6 # https://github.com/psycopg/psycopg2
{%- if cookiecutter.use_whitenoise == 'n' %}
Collectfast==2.2.0 # https://github.com/antonagestam/collectfast
{%- endif %}
{%- if cookiecutter.use_sentry == "y" %}
sentry-sdk==1.16.0 # https://github.com/getsentry/sentry-python
sentry-sdk==1.19.0 # https://github.com/getsentry/sentry-python
{%- endif %}
{%- if cookiecutter.use_docker == "n" and cookiecutter.windows == "y" %}
hiredis==2.2.2 # https://github.com/redis/hiredis-py
@ -24,21 +24,21 @@ django-storages[google]==1.13.2 # https://github.com/jschneier/django-storages
django-storages[azure]==1.13.2 # https://github.com/jschneier/django-storages
{%- endif %}
{%- if cookiecutter.mail_service == 'Mailgun' %}
django-anymail[mailgun]==9.0 # https://github.com/anymail/django-anymail
django-anymail[mailgun]==9.1 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'Amazon SES' %}
django-anymail[amazon_ses]==9.0 # https://github.com/anymail/django-anymail
django-anymail[amazon_ses]==9.1 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'Mailjet' %}
django-anymail[mailjet]==9.0 # https://github.com/anymail/django-anymail
django-anymail[mailjet]==9.1 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'Mandrill' %}
django-anymail[mandrill]==9.0 # https://github.com/anymail/django-anymail
django-anymail[mandrill]==9.1 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'Postmark' %}
django-anymail[postmark]==9.0 # https://github.com/anymail/django-anymail
django-anymail[postmark]==9.1 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'Sendgrid' %}
django-anymail[sendgrid]==9.0 # https://github.com/anymail/django-anymail
django-anymail[sendgrid]==9.1 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'SendinBlue' %}
django-anymail[sendinblue]==9.0 # https://github.com/anymail/django-anymail
django-anymail[sendinblue]==9.1 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'SparkPost' %}
django-anymail[sparkpost]==9.0 # https://github.com/anymail/django-anymail
django-anymail[sparkpost]==9.1 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'Other SMTP' %}
django-anymail==9.0 # https://github.com/anymail/django-anymail
django-anymail==9.1 # https://github.com/anymail/django-anymail
{%- endif %}

View File

@ -1 +1 @@
python-3.10.8
python-3.11.3

View File

@ -18,7 +18,7 @@ force_grid_wrap = 0
use_parentheses = true
[mypy]
python_version = 3.10
python_version = 3.11
check_untyped_defs = True
ignore_missing_imports = True
warn_unused_ignores = True
@ -35,6 +35,6 @@ ignore_errors = True
[coverage:run]
include = {{cookiecutter.project_slug}}/**
omit = *migrations*, *tests*
omit = */migrations/*, */tests/*
plugins =
django_coverage_plugin

View File

@ -3,20 +3,25 @@ const BundleTracker = require('webpack-bundle-tracker');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
target: "web",
target: 'web',
context: path.join(__dirname, '../'),
entry: {
'project': path.resolve(__dirname, '../{{cookiecutter.project_slug}}/static/js/project'),
'vendors': path.resolve(__dirname, '../{{cookiecutter.project_slug}}/static/js/vendors'),
project: path.resolve(__dirname, '../{{cookiecutter.project_slug}}/static/js/project'),
vendors: path.resolve(__dirname, '../{{cookiecutter.project_slug}}/static/js/vendors'),
},
output: {
path: path.resolve(__dirname, '../{{cookiecutter.project_slug}}/static/webpack_bundles/'),
path: path.resolve(
__dirname,
'../{{cookiecutter.project_slug}}/static/webpack_bundles/',
),
publicPath: '/static/webpack_bundles/',
filename: 'js/[name]-[fullhash].js',
chunkFilename: 'js/[name]-[hash].js',
},
plugins: [
new BundleTracker({filename: path.resolve(__dirname, '../webpack-stats.json')}),
new BundleTracker({
filename: path.resolve(__dirname, '../webpack-stats.json'),
}),
new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash].css' }),
],
module: {
@ -35,11 +40,7 @@ module.exports = {
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env',
'autoprefixer',
'pixrem',
],
plugins: ['postcss-preset-env', 'autoprefixer', 'pixrem'],
},
},
},

View File

@ -5,9 +5,9 @@ const commonConfig = require('./common.config');
{%- if cookiecutter.use_whitenoise == 'n' %}
{%- if cookiecutter.cloud_provider == 'AWS' %}
const s3BucketName = process.env.DJANGO_AWS_STORAGE_BUCKET_NAME;
const awsS3Domain = process.env.DJANGO_AWS_S3_CUSTOM_DOMAIN ?
process.env.DJANGO_AWS_S3_CUSTOM_DOMAIN
: `${s3BucketName}.s3.amazonaws.com`;
const awsS3Domain = process.env.DJANGO_AWS_S3_CUSTOM_DOMAIN
? process.env.DJANGO_AWS_S3_CUSTOM_DOMAIN
: `${s3BucketName}.s3.amazonaws.com`;
const staticUrl = `https://${awsS3Domain}/static/`;
{%- elif cookiecutter.cloud_provider == 'GCP' %}
const staticUrl = `https://storage.googleapis.com/${process.env.DJANGO_GCP_STORAGE_BUCKET_NAME}/static/`;

View File

@ -1,5 +1,5 @@
{%- if cookiecutter.frontend_pipeline == 'Webpack' %}
{%- if cookiecutter.frontend_pipeline == 'Webpack' -%}
import '../sass/project.scss';
{%- endif %}
{% endif -%}
/* Project specific Javascript goes here. */

View File

@ -1,12 +1,11 @@
@import "custom_bootstrap_vars";
@import "~bootstrap/scss/bootstrap";
@import 'custom_bootstrap_vars';
@import '~bootstrap/scss/bootstrap';
// project specific CSS goes here
////////////////////////////////
//Variables//
////////////////////////////////
///////////////
// Variables //
///////////////
// Alert colors
@ -17,9 +16,9 @@ $pink: #f2dede;
$dark-pink: #eed3d7;
$red: #b94a48;
////////////////////////////////
//Alerts//
////////////////////////////////
////////////
// Alerts //
////////////
// bootstrap alert CSS, translated to the django-standard levels of
// debug, info, success, warning, error

View File

@ -92,8 +92,7 @@
</li>
{% if request.user.is_authenticated %}
<li class="nav-item">
{# URL provided by django-allauth/account/urls.py #}
<a class="nav-link" href="{% url 'users:detail' request.user.username %}">{% translate "My Profile" %}</a>
<a class="nav-link" href="{% endraw %}{% if cookiecutter.username_type == "email" %}{% raw %}{% url 'users:detail' request.user.pk %}{% endraw %}{% else %}{% raw %}{% url 'users:detail' request.user.username %}{% endraw %}{% endif %}{% raw %}">{% translate "My Profile" %}</a>
</li>
<li class="nav-item">
{# URL provided by django-allauth/account/urls.py #}

View File

@ -1,7 +1,7 @@
{% 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 %}
{% block content %}
<div class="container">
@ -9,7 +9,7 @@
<div class="row">
<div class="col-sm-12">
<h2>{{ object.username }}</h2>
<h2>{% endraw %}{% if cookiecutter.username_type == "email" %}{% raw %}{{ object.name }}{% endraw %}{% else %}{% raw %}{{ object.username }}{% endraw %}{% endif %}{% raw %}</h2>
{% if object.name %}
<p>{{ object.name }}</p>
{% endif %}

View File

@ -1,10 +1,10 @@
{% 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 %}
{% block content %}
<h1>{{ user.username }}</h1>
<h1>{% endraw %}{% if cookiecutter.username_type == "email" %}{% raw %}{{ user.name }}{% endraw %}{% else %}{% raw %}{{ user.username }}{% endraw %}{% endif %}{% raw %}</h1>
<form class="form-horizontal" method="post" action="{% url 'users:update' %}">
{% csrf_token %}
{{ form|crispy }}

View File

@ -13,8 +13,13 @@ 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"),
{
@ -29,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 %}

View File

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

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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 %}
],
),
]

View File

@ -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 %}

View File

@ -7,7 +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")
@ -29,4 +31,4 @@ class UserFactory(DjangoModelFactory):
class Meta:
model = get_user_model()
django_get_or_create = ["username"]
django_get_or_create = ["{{cookiecutter.username_type}}"]

View File

@ -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

View File

@ -4,11 +4,19 @@ from {{ cookiecutter.project_slug }}.users.models import User
def test_user_detail(user: User):
{%- if cookiecutter.username_type == "email" %}
assert (
reverse("api:user-detail", kwargs={"pk": user.pk})
== f"/api/users/{user.pk}/"
)
assert resolve(f"/api/users/{user.pk}/").view_name == "api:user-detail"
{%- else %}
assert (
reverse("api:user-detail", kwargs={"username": user.username})
== f"/api/users/{user.username}/"
)
assert resolve(f"/api/users/{user.username}/").view_name == "api:user-detail"
{%- endif %}
def test_user_list():

View File

@ -29,7 +29,11 @@ class TestUserViewSet:
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,
}

View File

@ -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 %}

View File

@ -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()

View File

@ -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 %}

View File

@ -4,11 +4,16 @@ from {{ cookiecutter.project_slug }}.users.models import User
def test_detail(user: User):
{%- if cookiecutter.username_type == "email" %}
assert reverse("users:detail", kwargs={"pk": user.pk}) == f"/users/{user.pk}/"
assert resolve(f"/users/{user.pk}/").view_name == "users:detail"
{%- else %}
assert (
reverse("users:detail", kwargs={"username": user.username})
== f"/users/{user.username}/"
)
assert resolve(f"/users/{user.username}/").view_name == "users:detail"
{%- endif %}
def test_update():

View File

@ -39,7 +39,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()
@ -79,7 +83,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 +95,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 +107,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)

View File

@ -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("<int:pk>/", view=user_detail_view, name="detail"),
{%- else %}
path("<str:username>/", view=user_detail_view, name="detail"),
{%- endif %}
]

View File

@ -10,8 +10,13 @@ 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()
@ -39,7 +44,11 @@ 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()