Merge branch 'master' into create-startapp-template

# Conflicts:
#	docs/troubleshooting.rst
#	hooks/post_gen_project.py
This commit is contained in:
Bruno Alla 2023-01-29 12:14:53 +00:00
commit 8bf3418851
No known key found for this signature in database
141 changed files with 4583 additions and 1931 deletions

View File

@ -12,7 +12,7 @@ trim_trailing_whitespace = true
indent_style = space indent_style = space
indent_size = 4 indent_size = 4
[*.{html,css,scss,json,yml}] [*.{html,css,scss,json,yml,xml}]
indent_style = space indent_style = space
indent_size = 2 indent_size = 2

2
.github/FUNDING.yml vendored
View File

@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
liberapay: # Replace with a single Liberapay username liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username otechie: # Replace with a single Otechie username
custom: ['https://www.patreon.com/browniebroke'] custom: ["https://www.patreon.com/browniebroke"]

View File

@ -51,7 +51,7 @@ labels: bug
Logs: Logs:
<details> <details>
<pre> <pre>
$ cookiecutter https://github.com/pydanny/cookiecutter-django $ cookiecutter https://github.com/cookiecutter/cookiecutter-django
project_name [Project Name]: ... project_name [Project Name]: ...
</pre> </pre>
</details> </details>

View File

@ -3,7 +3,7 @@ name: Paid Support Request
about: Ask Core Team members to help you out about: Ask Core Team members to help you out
--- ---
Provided your question goes beyond [regular support](https://github.com/pydanny/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. 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. * Daniel Roy Greenfeld, Project Lead ([GitHub](https://github.com/pydanny), [Patreon](https://www.patreon.com/danielroygreenfeld)): expertise in Django and AWS ELB.

View File

@ -1,11 +1,11 @@
--- ---
name: Question name: Question
about: Please consider asking your question on StackOverflow or Slack about: Please ask your question on StackOverflow, Discord or GitHub Discussions.
labels: question labels: question
--- ---
First, make sure to examine [the docs](https://cookiecutter-django.readthedocs.io/en/latest/). First, make sure to examine [the docs](https://cookiecutter-django.readthedocs.io/en/latest/). If that doesn't help, we recommend one of these 3 main channels:
If that doesn't help, post a question on [StackOverflow](https://stackoverflow.com/questions/tagged/cookiecutter-django) tagged with `cookiecutter-django`, you might get more visibility there than on our issue tracker. - If your issue is related to Django + something else but was generated with cookiecutter-django, the best is to post a question on [StackOverflow](https://stackoverflow.com/questions/tagged/cookiecutter-django) tagged with `cookiecutter-django`, you would get more visibility from other communities as well.
- Join us on [Discord](https://discord.gg/uFXweDQc5a) and ask around.
Finally, feel free to join [Slack](https://join.slack.com/t/cookie-cutter/shared_invite/enQtNzI0Mzg5NjE5Nzk5LTRlYWI2YTZhYmQ4YmU1Y2Q2NmE1ZjkwOGM0NDQyNTIwY2M4ZTgyNDVkNjMxMDdhZGI5ZGE5YmJjM2M3ODJlY2U) and ask around. - Start [a discussion](https://github.com/cookiecutter/cookiecutter-django/discussions) on our project's GitHub.

View File

@ -7,7 +7,7 @@
Checklist: Checklist:
- [ ] I've made sure that `tests/test_cookiecutter_generation.py` is updated accordingly (especially if adding or updating a template option) - [ ] I've made sure that tests are updated accordingly (especially if adding or updating a template option)
- [ ] I've updated the documentation or confirm that my change doesn't require any updates - [ ] I've updated the documentation or confirm that my change doesn't require any updates
## Rationale ## Rationale

View File

@ -1,4 +1,3 @@
## [{{merge_date.strftime('%Y-%m-%d')}}]
{%- for change_type, pulls in grouped_pulls.items() %} {%- for change_type, pulls in grouped_pulls.items() %}
{%- if pulls %} {%- if pulls %}
### {{ change_type }} ### {{ change_type }}

View File

@ -1152,5 +1152,210 @@
"name": "dalrrard", "name": "dalrrard",
"github_login": "dalrrard", "github_login": "dalrrard",
"twitter_username": "" "twitter_username": ""
},
{
"name": "Liam Brenner",
"github_login": "SableWalnut",
"twitter_username": ""
},
{
"name": "Noah H",
"github_login": "nthall",
"twitter_username": ""
},
{
"name": "Diego Montes",
"github_login": "d57montes",
"twitter_username": ""
},
{
"name": "Chao Yang Wu",
"github_login": "goatwu1993",
"twitter_username": ""
},
{
"name": "mpoli",
"github_login": "mpoli",
"twitter_username": ""
},
{
"name": "Zach Borboa",
"github_login": "zachborboa",
"twitter_username": ""
},
{
"name": "Timm Simpkins",
"github_login": "PoDuck",
"twitter_username": ""
},
{
"name": "Douglas",
"github_login": "douglascdev",
"twitter_username": ""
},
{
"name": "Will Gordon",
"github_login": "wgordon17",
"twitter_username": ""
},
{
"name": "Bogdan Mateescu",
"github_login": "mateesville93",
"twitter_username": ""
},
{
"name": "Fuzzwah",
"github_login": "Fuzzwah",
"twitter_username": ""
},
{
"name": "Thibault J.",
"github_login": "thibault",
"twitter_username": "thibault"
},
{
"name": "Pedro Campos",
"github_login": "pcampos119104",
"twitter_username": ""
},
{
"name": "Vikas Yadav",
"github_login": "vik-y",
"twitter_username": ""
},
{
"name": "Abdullah Adeel",
"github_login": "mabdullahadeel",
"twitter_username": "abdadeel_"
},
{
"name": "Jorge Valdez",
"github_login": "jorgeavaldez",
"twitter_username": ""
},
{
"name": "Ryan Fitch",
"github_login": "ryfi",
"twitter_username": ""
},
{
"name": "ghazi-git",
"github_login": "ghazi-git",
"twitter_username": ""
},
{
"name": "Cebrail Yılmaz",
"github_login": "b1sar",
"twitter_username": ""
},
{
"name": "Artur Barseghyan",
"github_login": "barseghyanartur",
"twitter_username": ""
},
{
"name": "innicoder",
"github_login": "innicoder",
"twitter_username": ""
},
{
"name": "Naveen",
"github_login": "naveensrinivasan",
"twitter_username": "snaveen"
},
{
"name": "Nikita Sobolev",
"github_login": "sobolevn",
"twitter_username": ""
},
{
"name": "Sebastian Reyes Espinosa",
"github_login": "sebastian-code",
"twitter_username": "sebastianreyese"
},
{
"name": "jugglinmike",
"github_login": "jugglinmike",
"twitter_username": ""
},
{
"name": "monosans",
"github_login": "monosans",
"twitter_username": ""
},
{
"name": "Marcio Mazza",
"github_login": "marciomazza",
"twitter_username": "marciomazza"
},
{
"name": "Brandon Rumiser",
"github_login": "brumiser1550",
"twitter_username": ""
},
{
"name": "krati yadav",
"github_login": "krati5",
"twitter_username": ""
},
{
"name": "Abe Hanoka",
"github_login": "abe-101",
"twitter_username": "abe__101"
},
{
"name": "Adin Hodovic",
"github_login": "adinhodovic",
"twitter_username": ""
},
{
"name": "Leifur Halldor Asgeirsson",
"github_login": "leifurhauks",
"twitter_username": ""
},
{
"name": "David",
"github_login": "buckldav",
"twitter_username": ""
},
{
"name": "rguptar",
"github_login": "rguptar",
"twitter_username": ""
},
{
"name": "Omer-5",
"github_login": "Omer-5",
"twitter_username": ""
},
{
"name": "TAKAHASHI Shuuji",
"github_login": "shuuji3",
"twitter_username": ""
},
{
"name": "Thomas Booij",
"github_login": "ThomasBooij95",
"twitter_username": ""
},
{
"name": "Pamela Fox",
"github_login": "pamelafox",
"twitter_username": "pamelafox"
},
{
"name": "Robin",
"github_login": "Kaffeetasse",
"twitter_username": ""
},
{
"name": "Patrick Tran",
"github_login": "theptrk",
"twitter_username": ""
},
{
"name": "tildebox",
"github_login": "tildebox",
"twitter_username": ""
} }
] ]

View File

@ -1,12 +1,20 @@
# Config for Dependabot updates. See Documentation here: # Config for Dependabot updates. See Documentation here:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates # https://docs.github.com/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates
version: 2 version: 2
updates: updates:
# Update Github actions in workflows # Update GitHub actions in workflows
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
labels: labels:
- "update" - "update"
# Update npm packages
- package-ecosystem: "npm"
directory: "{{cookiecutter.project_slug}}/"
schedule:
interval: "daily"
labels:
- "update"

View File

@ -1,29 +0,0 @@
categories:
- title: 'Breaking Changes'
labels:
- 'breaking'
- title: 'Major Changes'
labels:
- 'major'
- title: 'Minor Changes'
labels:
- 'enhancement'
- title: 'Bugfixes'
labels:
- 'bug'
- title: 'Removals'
labels:
- 'removed'
- title: 'Documentation updates'
labels:
- 'docs'
exclude-labels:
- 'skip-changelog'
- 'update'
- 'project infrastructure'
template: |
## Changes
$CHANGES

View File

@ -2,33 +2,47 @@ name: CI
on: on:
push: push:
branches: [ master ]
pull_request: pull_request:
concurrency:
group: ${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs: jobs:
tox: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.10"
cache: pip
- name: Run pre-commit
uses: pre-commit/action@v3.0.0
tests:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
tox-env: os:
- py39 - ubuntu-latest
- black-template - windows-latest
- macOS-latest
name: "Run tests"
runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-python@v2.2.2 - uses: actions/setup-python@v4
with: with:
python-version: 3.9 python-version: "3.10"
cache: pip
- name: Install dependencies - name: Install dependencies
run: | run: pip install -r requirements.txt
python -m pip install -U pip - name: Run tests
python -m pip install -U tox run: pytest tests
- name: Tox ${{ matrix.tox-env }}
run: tox -e ${{ matrix.tox-env }}
docker: docker:
runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -36,29 +50,37 @@ jobs:
- name: Basic - name: Basic
args: "" args: ""
- name: Extended - name: Extended
args: "use_celery=y use_drf=y js_task_runner=Gulp" args: "use_celery=y use_drf=y frontend_pipeline=Gulp"
name: "${{ matrix.script.name }} Docker"
runs-on: ubuntu-latest
env: env:
DOCKER_BUILDKIT: 1 DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1 COMPOSE_DOCKER_CLI_BUILD: 1
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-python@v2.2.2 - uses: actions/setup-python@v4
with: with:
python-version: 3.9 python-version: "3.10"
cache: pip
- name: Install dependencies
run: pip install -r requirements.txt
- name: Docker ${{ matrix.script.name }} - name: Docker ${{ matrix.script.name }}
run: sh tests/test_docker.sh ${{ matrix.script.args }} run: sh tests/test_docker.sh ${{ matrix.script.args }}
bare: bare:
runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
script: script:
- name: With Celery - name: With Celery
args: "use_celery=y use_compressor=y" args: "use_celery=y frontend_pipeline='Django Compressor'"
- name: With Gulp
args: "frontend_pipeline='Gulp'"
name: "${{ matrix.script.name }} Bare metal"
runs-on: ubuntu-latest
services: services:
redis: redis:
image: redis:5.0 image: redis:5.0
@ -77,9 +99,19 @@ jobs:
DATABASE_URL: "postgres://postgres:postgres@localhost:5432/postgres" DATABASE_URL: "postgres://postgres:postgres@localhost:5432/postgres"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-python@v2.2.2 - uses: actions/setup-python@v4
with: with:
python-version: 3.9 python-version: "3.10"
cache: pip
cache-dependency-path: |
requirements.txt
{{cookiecutter.project_slug}}/requirements/base.txt
{{cookiecutter.project_slug}}/requirements/local.txt
- name: Install dependencies
run: pip install -r requirements.txt
- uses: actions/setup-node@v3
with:
node-version: "16"
- name: Bare Metal ${{ matrix.script.name }} - name: Bare Metal ${{ matrix.script.name }}
run: sh tests/test_bare.sh ${{ matrix.script.args }} run: sh tests/test_bare.sh ${{ matrix.script.args }}

View File

@ -0,0 +1,30 @@
# Creates a new issue for Major/Minor Django updates that keeps track
# of all dependencies that need to be updated/merged in order for the
# latest Django version to also be merged.
name: Django Issue Checker
on:
schedule:
- cron: "28 5 * * *"
# Manual trigger
workflow_dispatch:
jobs:
issue-checker:
# Disables this workflow from running in a repository that is not part of the indicated organization/user
if: github.repository_owner == 'cookiecutter'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Create Django Major Issue
run: python scripts/create_django_issue.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,14 +0,0 @@
name: Release Drafter
on:
push:
branches:
- master
jobs:
release_notes:
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,62 +1,40 @@
# Automatically close issues that have a keyword mark (an HTML comment) # Automatically close issues or pull requests that have a label, after a custom delay, if no one replies.
# in the last comment in the issue, by a group of predefined users, after a custom delay.
# https://github.com/tiangolo/issue-manager # https://github.com/tiangolo/issue-manager
# Default config:
# <!-- issue-manager: answered -->
# Wait 10 days and comment: "Assuming the original issue was solved, it will be automatically closed now"
# Extra config:
# '<!-- issue-manager: waiting -->'
# Wait 10 days and comment: "Automatically closing. To re-open, please provide the additional information requested"
name: Issue Manager name: Issue Manager
on: on:
# Every day at midnight
schedule: schedule:
- cron: "0 0 * * *" - cron: "12 0 * * *"
# Manual trigger issue_comment:
types:
- created
issues:
types:
- labeled
pull_request_target:
types:
- labeled
workflow_dispatch: workflow_dispatch:
jobs: jobs:
issue-manager: issue-manager:
# Disables this workflow from running in a repository that is not part of the indicated organization/user
if: github.repository_owner == 'cookiecutter'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: tiangolo/issue-manager@0.4.0 - uses: tiangolo/issue-manager@0.4.0
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
config: > config: >
{ {
"answered": { "answered": {
"delay": 864000, "message": "Assuming the question was answered, this will be automatically closed now."
"message": "Assuming the original issue was solved, it will be automatically closed now.", },
"users": [ "solved": {
"pydanny", "message": "Assuming the original issue was solved, it will be automatically closed now."
"audreyr",
"luzfcb",
"theskumar",
"jayfk",
"burhan",
"webyneter",
"browniebroke",
"sfdye"
]
}, },
"waiting": { "waiting": {
"delay": 864000, "message": "Automatically closing after waiting for additional info. To re-open, please provide the additional information requested."
"message": "Automatically closing. To re-open, please provide the additional information requested.",
"users": [
"pydanny",
"audreyr",
"luzfcb",
"theskumar",
"jayfk",
"burhan",
"webyneter",
"browniebroke",
"sfdye"
]
} }
} }

View File

@ -1,32 +1,43 @@
# Run pre-commit autoupdate every day at midnight # Run pre-commit autoupdate every day at midnight
# and create a pull request if any changes # and create a pull request if any changes
name: Pre-commit auto-update name: Pre-commit auto-update
on: on:
schedule: schedule:
- cron: "0 0 * * *" - cron: "15 2 * * *"
workflow_dispatch: # to trigger manually workflow_dispatch: # to trigger manually
permissions:
contents: read
jobs: jobs:
auto-update: auto-update:
# 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
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-python@v2.2.2 - uses: actions/setup-python@v4
with: with:
python-version: 3.9 python-version: "3.10"
- name: Install pre-commit - name: Install pre-commit
run: pip install pre-commit run: pip install pre-commit
- name: Run pre-commit autoupdate - name: Autoupdate template
run: pre-commit autoupdate
- name: Autoupdate generated projects
working-directory: "{{cookiecutter.project_slug}}" working-directory: "{{cookiecutter.project_slug}}"
run: pre-commit autoupdate run: pre-commit autoupdate
- name: Create Pull Request - name: Create Pull Request
uses: peter-evans/create-pull-request@v3.10.1 uses: peter-evans/create-pull-request@v4
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
branch: update/pre-commit-autoupdate branch: update/pre-commit-autoupdate

View File

@ -8,27 +8,27 @@ on:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build: release:
runs-on: ubuntu-latest # Disables this workflow from running in a repository that is not part of the indicated organization/user
if: github.repository_owner == 'cookiecutter'
runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v4
with: with:
python-version: 3.9 python-version: "3.10"
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install -r requirements.txt pip install -r requirements.txt
- name: Set git details
run: |
git config --global user.name "github-actions"
git config --global user.email "action@github.com"
- name: Update list - name: Update list
run: python scripts/update_changelog.py run: python scripts/update_changelog.py
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v4.12.0
with:
commit_message: Update Changelog
file_pattern: CHANGELOG.md

View File

@ -5,26 +5,35 @@ on:
branches: branches:
- master - master
permissions:
contents: read
jobs: jobs:
build: build:
runs-on: ubuntu-latest # 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
runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v4
with: with:
python-version: 3.9 python-version: "3.10"
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install -r requirements.txt pip install -r requirements.txt
- name: Update list - name: Update list
run: python scripts/update_contributors.py run: python scripts/update_contributors.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Commit changes - name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v4.12.0 uses: stefanzweifel/git-auto-commit-action@v4.16.0
with: with:
commit_message: Update Contributors commit_message: Update Contributors
file_pattern: CONTRIBUTORS.md .github/contributors.json file_pattern: CONTRIBUTORS.md .github/contributors.json

36
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,36 @@
exclude: "{{cookiecutter.project_slug}}"
default_stages: [commit]
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: check-yaml
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.1
hooks:
- id: pyupgrade
args: [--py310-plus]
exclude: hooks/
- repo: https://github.com/psf/black
rev: 22.12.0
hooks:
- id: black
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
hooks:
- id: flake8
ci:
autoupdate_schedule: weekly
skip: []
submodules: false

View File

@ -15,6 +15,7 @@ label_prs: update
requirements: requirements:
- "requirements.txt" - "requirements.txt"
- "docs/requirements.txt"
- "{{cookiecutter.project_slug}}/requirements/base.txt" - "{{cookiecutter.project_slug}}/requirements/base.txt"
- "{{cookiecutter.project_slug}}/requirements/local.txt" - "{{cookiecutter.project_slug}}/requirements/local.txt"
- "{{cookiecutter.project_slug}}/requirements/production.txt" - "{{cookiecutter.project_slug}}/requirements/production.txt"

15
.readthedocs.yaml Normal file
View File

@ -0,0 +1,15 @@
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
# Version of Python and requirements required to build the docs
python:
version: "3.8"
install:
- requirements: docs/requirements.txt

File diff suppressed because it is too large Load Diff

3
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +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/).

42
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,42 @@
# How to Contribute
Always happy to get issues identified and pull requests!
## Getting your pull request merged in
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.
## Testing
### Installation
Please install [tox](https://tox.readthedocs.io/en/latest/), which is a generic virtualenv management and test command line tool.
[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/):
$ pip install tox
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/).
### Run the Tests
Tox uses pytest under the hood, hence it supports the same syntax for selecting tests.
For further information please consult the [pytest usage docs](https://pytest.org/latest/usage.html#specifying-tests-selecting-tests).
To run all tests using various versions of python in virtualenvs defined in tox.ini, just run tox.:
$ tox
It is possible to test with a specific version of python. To do this, the command
is:
$ tox -e py310
This will run pytest with the python3.10 interpreter, for example.
To run a particular test with tox for against your current Python version:
$ tox -e py -- -k test_default_configuration

View File

@ -1,54 +0,0 @@
How to Contribute
=================
Always happy to get issues identified and pull requests!
Getting your pull request merged in
------------------------------------
#. Keep it small. The smaller the pull request the more likely I'll pull it in.
#. Pull requests that fix a current issue get priority for review.
Testing
-------
Installation
~~~~~~~~~~~~
Please install `tox`_, which is a generic virtualenv management and test command line tool.
`tox`_ is available for download from `PyPI`_ via `pip`_::
$ pip install tox
It will automatically create a fresh virtual environment and install our test dependencies,
such as `pytest-cookies`_ and `flake8`_.
Run the Tests
~~~~~~~~~~~~~
Tox uses py.test under the hood, hence it supports the same syntax for selecting tests.
For further information please consult the `pytest usage docs`_.
To run all tests using various versions of python in virtualenvs defined in tox.ini, just run tox.::
$ tox
It is possible to test with a specific version of python. To do this, the command
is::
$ tox -e py39
This will run py.test with the python3.9 interpreter, for example.
To run a particular test with tox for against your current Python version::
$ tox -e py -- -k test_default_configuration
.. _`pytest usage docs`: https://pytest.org/latest/usage.html#specifying-tests-selecting-tests
.. _`tox`: https://tox.readthedocs.io/en/latest/
.. _`pip`: https://pypi.python.org/pypi/pip/
.. _`pytest-cookies`: https://pypi.python.org/pypi/pytest-cookies/
.. _`flake8`: https://pypi.python.org/pypi/flake8/
.. _`PyPI`: https://pypi.python.org/pypi

View File

@ -131,6 +131,20 @@ Listed in alphabetical order.
</td> </td>
<td>Qoyyuum</td> <td>Qoyyuum</td>
</tr> </tr>
<tr>
<td>Abdullah Adeel</td>
<td>
<a href="https://github.com/mabdullahadeel">mabdullahadeel</a>
</td>
<td>abdadeel_</td>
</tr>
<tr>
<td>Abe Hanoka</td>
<td>
<a href="https://github.com/abe-101">abe-101</a>
</td>
<td>abe__101</td>
</tr>
<tr> <tr>
<td>Adam Bogdał</td> <td>Adam Bogdał</td>
<td> <td>
@ -152,6 +166,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>Adin Hodovic</td>
<td>
<a href="https://github.com/adinhodovic">adinhodovic</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Agam Dua</td> <td>Agam Dua</td>
<td> <td>
@ -278,6 +299,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>Artur Barseghyan</td>
<td>
<a href="https://github.com/barseghyanartur">barseghyanartur</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>AsheKR</td> <td>AsheKR</td>
<td> <td>
@ -348,6 +376,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>Bogdan Mateescu</td>
<td>
<a href="https://github.com/mateesville93">mateesville93</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Bouke Haarsma</td> <td>Bouke Haarsma</td>
<td> <td>
@ -355,6 +390,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>Brandon Rumiser</td>
<td>
<a href="https://github.com/brumiser1550">brumiser1550</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Brent Payne</td> <td>Brent Payne</td>
<td> <td>
@ -390,6 +432,20 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>Cebrail Yılmaz</td>
<td>
<a href="https://github.com/b1sar">b1sar</a>
</td>
<td></td>
</tr>
<tr>
<td>Chao Yang Wu</td>
<td>
<a href="https://github.com/goatwu1993">goatwu1993</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Charlie Macfarlane Brodie</td> <td>Charlie Macfarlane Brodie</td>
<td> <td>
@ -551,6 +607,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>David</td>
<td>
<a href="https://github.com/buckldav">buckldav</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>David Díaz</td> <td>David Díaz</td>
<td> <td>
@ -614,6 +677,13 @@ Listed in alphabetical order.
</td> </td>
<td>purplediane88</td> <td>purplediane88</td>
</tr> </tr>
<tr>
<td>Diego Montes</td>
<td>
<a href="https://github.com/d57montes">d57montes</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Dong Huynh</td> <td>Dong Huynh</td>
<td> <td>
@ -621,6 +691,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>Douglas</td>
<td>
<a href="https://github.com/douglascdev">douglascdev</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Duda Nogueira</td> <td>Duda Nogueira</td>
<td> <td>
@ -705,6 +782,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>Fuzzwah</td>
<td>
<a href="https://github.com/Fuzzwah">Fuzzwah</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Gabriel Mejia</td> <td>Gabriel Mejia</td>
<td> <td>
@ -726,6 +810,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>ghazi-git</td>
<td>
<a href="https://github.com/ghazi-git">ghazi-git</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Gilbishkosma</td> <td>Gilbishkosma</td>
<td> <td>
@ -824,6 +915,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>innicoder</td>
<td>
<a href="https://github.com/innicoder">innicoder</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Irfan Ahmad</td> <td>Irfan Ahmad</td>
<td> <td>
@ -929,6 +1027,20 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>Jorge Valdez</td>
<td>
<a href="https://github.com/jorgeavaldez">jorgeavaldez</a>
</td>
<td></td>
</tr>
<tr>
<td>jugglinmike</td>
<td>
<a href="https://github.com/jugglinmike">jugglinmike</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Jules Cheron</td> <td>Jules Cheron</td>
<td> <td>
@ -1013,6 +1125,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>krati yadav</td>
<td>
<a href="https://github.com/krati5">krati5</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Krzysztof Szumny</td> <td>Krzysztof Szumny</td>
<td> <td>
@ -1048,6 +1167,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>Leifur Halldor Asgeirsson</td>
<td>
<a href="https://github.com/leifurhauks">leifurhauks</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Leo won</td> <td>Leo won</td>
<td> <td>
@ -1076,6 +1202,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>Liam Brenner</td>
<td>
<a href="https://github.com/SableWalnut">SableWalnut</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Lin Xianyi</td> <td>Lin Xianyi</td>
<td> <td>
@ -1118,6 +1251,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>Marcio Mazza</td>
<td>
<a href="https://github.com/marciomazza">marciomazza</a>
</td>
<td>marciomazza</td>
</tr>
<tr> <tr>
<td>Martin Blech</td> <td>Martin Blech</td>
<td> <td>
@ -1251,6 +1391,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>monosans</td>
<td>
<a href="https://github.com/monosans">monosans</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>mozillazg</td> <td>mozillazg</td>
<td> <td>
@ -1258,6 +1405,20 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>mpoli</td>
<td>
<a href="https://github.com/mpoli">mpoli</a>
</td>
<td></td>
</tr>
<tr>
<td>Naveen</td>
<td>
<a href="https://github.com/naveensrinivasan">naveensrinivasan</a>
</td>
<td>snaveen</td>
</tr>
<tr> <tr>
<td>Nico Stefani</td> <td>Nico Stefani</td>
<td> <td>
@ -1265,6 +1426,20 @@ Listed in alphabetical order.
</td> </td>
<td>moby_dick91</td> <td>moby_dick91</td>
</tr> </tr>
<tr>
<td>Nikita Sobolev</td>
<td>
<a href="https://github.com/sobolevn">sobolevn</a>
</td>
<td></td>
</tr>
<tr>
<td>Noah H</td>
<td>
<a href="https://github.com/nthall">nthall</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Oleg Russkin</td> <td>Oleg Russkin</td>
<td> <td>
@ -1272,6 +1447,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>Omer-5</td>
<td>
<a href="https://github.com/Omer-5">Omer-5</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Pablo</td> <td>Pablo</td>
<td> <td>
@ -1279,6 +1461,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>Pamela Fox</td>
<td>
<a href="https://github.com/pamelafox">pamelafox</a>
</td>
<td>pamelafox</td>
</tr>
<tr> <tr>
<td>Parbhat Puri</td> <td>Parbhat Puri</td>
<td> <td>
@ -1286,6 +1475,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>Patrick Tran</td>
<td>
<a href="https://github.com/theptrk">theptrk</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Pawan Chaurasia</td> <td>Pawan Chaurasia</td>
<td> <td>
@ -1293,6 +1489,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>Pedro Campos</td>
<td>
<a href="https://github.com/pcampos119104">pcampos119104</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Peter Bittner</td> <td>Peter Bittner</td>
<td> <td>
@ -1363,6 +1566,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>rguptar</td>
<td>
<a href="https://github.com/rguptar">rguptar</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Richard Hajdu</td> <td>Richard Hajdu</td>
<td> <td>
@ -1370,6 +1580,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>Robin</td>
<td>
<a href="https://github.com/Kaffeetasse">Kaffeetasse</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Roman Afanaskin</td> <td>Roman Afanaskin</td>
<td> <td>
@ -1391,6 +1608,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>Ryan Fitch</td>
<td>
<a href="https://github.com/ryfi">ryfi</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Sam Collins</td> <td>Sam Collins</td>
<td> <td>
@ -1405,6 +1629,13 @@ Listed in alphabetical order.
</td> </td>
<td>saschalalala</td> <td>saschalalala</td>
</tr> </tr>
<tr>
<td>Sebastian Reyes Espinosa</td>
<td>
<a href="https://github.com/sebastian-code">sebastian-code</a>
</td>
<td>sebastianreyese</td>
</tr>
<tr> <tr>
<td>Simon Rey</td> <td>Simon Rey</td>
<td> <td>
@ -1461,6 +1692,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>TAKAHASHI Shuuji</td>
<td>
<a href="https://github.com/shuuji3">shuuji3</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Tames McTigue</td> <td>Tames McTigue</td>
<td> <td>
@ -1482,6 +1720,20 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>Thibault J.</td>
<td>
<a href="https://github.com/thibault">thibault</a>
</td>
<td>thibault</td>
</tr>
<tr>
<td>Thomas Booij</td>
<td>
<a href="https://github.com/ThomasBooij95">ThomasBooij95</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Théo Segonds</td> <td>Théo Segonds</td>
<td> <td>
@ -1489,6 +1741,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>tildebox</td>
<td>
<a href="https://github.com/tildebox">tildebox</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Tim Claessens</td> <td>Tim Claessens</td>
<td> <td>
@ -1503,6 +1762,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>Timm Simpkins</td>
<td>
<a href="https://github.com/PoDuck">PoDuck</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Tom Atkins</td> <td>Tom Atkins</td>
<td> <td>
@ -1559,6 +1825,13 @@ Listed in alphabetical order.
</td> </td>
<td>highcenburg</td> <td>highcenburg</td>
</tr> </tr>
<tr>
<td>Vikas Yadav</td>
<td>
<a href="https://github.com/vik-y">vik-y</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>Vitaly Babiy</td> <td>Vitaly Babiy</td>
<td> <td>
@ -1594,6 +1867,13 @@ Listed in alphabetical order.
</td> </td>
<td>g01dhand</td> <td>g01dhand</td>
</tr> </tr>
<tr>
<td>Will Gordon</td>
<td>
<a href="https://github.com/wgordon17">wgordon17</a>
</td>
<td></td>
</tr>
<tr> <tr>
<td>William Archinal</td> <td>William Archinal</td>
<td> <td>
@ -1629,6 +1909,13 @@ Listed in alphabetical order.
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr>
<td>Zach Borboa</td>
<td>
<a href="https://github.com/zachborboa">zachborboa</a>
</td>
<td></td>
</tr>
</table> </table>
### Special Thanks ### Special Thanks

247
README.md Normal file
View File

@ -0,0 +1,247 @@
# Cookiecutter Django
[![Build Status](https://img.shields.io/github/actions/workflow/status/cookiecutter/cookiecutter-django/ci.yml?branch=master)](https://github.com/cookiecutter/cookiecutter-django/actions/workflows/ci.yml?query=branch%3Amaster)
[![Documentation Status](https://readthedocs.org/projects/cookiecutter-django/badge/?version=latest)](https://cookiecutter-django.readthedocs.io/en/latest/?badge=latest)
[![Updates](https://pyup.io/repos/github/cookiecutter/cookiecutter-django/shield.svg)](https://pyup.io/repos/github/cookiecutter/cookiecutter-django/)
[![Join our Discord](https://img.shields.io/badge/Discord-cookiecutter-5865F2?style=flat&logo=discord&logoColor=white)](https://discord.gg/uFXweDQc5a)
[![Code Helpers Badge](https://www.codetriage.com/cookiecutter/cookiecutter-django/badges/users.svg)](https://www.codetriage.com/cookiecutter/cookiecutter-django)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)
Powered by [Cookiecutter](https://github.com/cookiecutter/cookiecutter), Cookiecutter Django is a framework for jumpstarting
production-ready Django projects quickly.
- Documentation: <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 and livereload
- Send emails via [Anymail](https://github.com/anymail/django-anymail) (using [Mailgun](http://www.mailgun.com/) by default or Amazon SES if AWS is selected cloud provider, but switchable)
- Media storage using Amazon S3, Google Cloud Storage or Azure Storage
- Docker support using [docker-compose](https://github.com/docker/compose) for development and production (using [Traefik](https://traefik.io/) with [LetsEncrypt](https://letsencrypt.org/) support)
- [Procfile](https://devcenter.heroku.com/articles/procfile) for deploying to Heroku
- Instructions for deploying to [PythonAnywhere](https://www.pythonanywhere.com/)
- Run tests with unittest or pytest
- Customizable PostgreSQL version
- Default integration with [pre-commit](https://github.com/pre-commit/pre-commit) for identifying simple issues before submission to code review
## Optional Integrations
*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
## 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).
## 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.
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>
</p>
Two Scoops of Django 3.x is the best ice cream-themed Django reference in the universe!
### PyUp
<p align="center">
<a href="https://pyup.io/"><img src="https://pyup.io/static/images/logo.png"></a>
</p>
PyUp brings you automated security and dependency updates used by Google and other organizations. Free for open source projects!
## Usage
Let's pretend you want to create a Django project called "redditclone". Rather than using `startproject`
and then editing the results to include your name, email, and various configuration issues that always get forgotten until the worst possible moment, get [cookiecutter](https://github.com/cookiecutter/cookiecutter) to do all the work.
First, get Cookiecutter. Trust me, it's awesome:
$ pip install "cookiecutter>=1.7.0"
Now run it against this repo:
$ cookiecutter https://github.com/cookiecutter/cookiecutter-django
You'll be prompted for some values. Provide them, then a Django project will be created for you.
**Warning**: After this point, change 'Daniel Greenfeld', 'pydanny', etc to your own information.
Answer the prompts with your own desired [options](http://cookiecutter-django.readthedocs.io/en/latest/project-generation-options.html). For example:
Cloning into 'cookiecutter-django'...
remote: Counting objects: 550, done.
remote: Compressing objects: 100% (310/310), done.
remote: Total 550 (delta 283), reused 479 (delta 222)
Receiving objects: 100% (550/550), 127.66 KiB | 58 KiB/s, done.
Resolving deltas: 100% (283/283), done.
project_name [My Awesome Project]: Reddit Clone
project_slug [reddit_clone]: reddit
description [Behold My Awesome Project!]: A reddit clone.
author_name [Daniel Roy Greenfeld]: Daniel Greenfeld
domain_name [example.com]: myreddit.com
email [daniel-greenfeld@example.com]: pydanny@gmail.com
version [0.1.0]: 0.0.1
Select open_source_license:
1 - MIT
2 - BSD
3 - GPLv3
4 - Apache Software License 2.0
5 - Not open source
Choose from 1, 2, 3, 4, 5 [1]: 1
timezone [UTC]: America/Los_Angeles
windows [n]: n
use_pycharm [n]: y
use_docker [n]: n
Select postgresql_version:
1 - 14
2 - 13
3 - 12
4 - 11
5 - 10
Choose from 1, 2, 3, 4, 5 [1]: 1
Select cloud_provider:
1 - AWS
2 - GCP
3 - None
Choose from 1, 2, 3 [1]: 1
Select mail_service:
1 - Mailgun
2 - Amazon SES
3 - Mailjet
4 - Mandrill
5 - Postmark
6 - Sendgrid
7 - SendinBlue
8 - SparkPost
9 - Other SMTP
Choose from 1, 2, 3, 4, 5, 6, 7, 8, 9 [1]: 1
use_async [n]: n
use_drf [n]: y
Select frontend_pipeline:
1 - None
2 - Django Compressor
3 - Gulp
Choose from 1, 2, 3, 4 [1]: 1
use_celery [n]: y
use_mailhog [n]: n
use_sentry [n]: y
use_whitenoise [n]: n
use_heroku [n]: y
Select ci_tool:
1 - None
2 - Travis
3 - Gitlab
4 - Github
Choose from 1, 2, 3, 4 [1]: 4
keep_local_envs_in_vcs [y]: y
debug [n]: n
Enter the project and take a look around:
$ cd reddit/
$ ls
Create a git repo and push it there:
$ git init
$ git add .
$ git commit -m "first awesome commit"
$ git remote add origin git@github.com:pydanny/redditclone.git
$ git push -u origin master
Now take a look at your repo. Don't forget to carefully look at the generated README. Awesome, right?
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)
## 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).
## For Readers of Two Scoops of Django
You may notice that some elements of this project do not exactly match what we describe in chapter 3. The reason for that is this project, amongst other things, serves as a test bed for trying out new ideas and concepts. Sometimes they work, sometimes they don't, but the end result is that it won't necessarily match precisely what is described in the book I co-authored.
## 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.
## "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
## Releases
Need a stable release? You can find them at <https://github.com/cookiecutter/cookiecutter-django/releases>
## Not Exactly What You Want?
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.
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.
### Submit a Pull Request
We accept pull requests if they're small, atomic, and make our own project development
experience better.
## Articles
- [Cookiecutter Django With Amazon RDS](https://haseeburrehman.com/posts/cookiecutter-django-with-amazon-rds/) - Apr, 2, 2021
- [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

@ -1,324 +0,0 @@
Cookiecutter Django
===================
.. image:: https://img.shields.io/github/workflow/status/pydanny/cookiecutter-django/CI/master
:target: https://github.com/pydanny/cookiecutter-django/actions?query=workflow%3ACI
:alt: Build Status
.. image:: https://readthedocs.org/projects/cookiecutter-django/badge/?version=latest
:target: https://cookiecutter-django.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status
.. image:: https://pyup.io/repos/github/pydanny/cookiecutter-django/shield.svg
:target: https://pyup.io/repos/github/pydanny/cookiecutter-django/
:alt: Updates
.. image:: https://img.shields.io/badge/cookiecutter-Join%20on%20Slack-green?style=flat&logo=slack
:target: https://join.slack.com/t/cookie-cutter/shared_invite/enQtNzI0Mzg5NjE5Nzk5LTRlYWI2YTZhYmQ4YmU1Y2Q2NmE1ZjkwOGM0NDQyNTIwY2M4ZTgyNDVkNjMxMDdhZGI5ZGE5YmJjM2M3ODJlY2U
.. image:: https://www.codetriage.com/pydanny/cookiecutter-django/badges/users.svg
:target: https://www.codetriage.com/pydanny/cookiecutter-django
:alt: Code Helpers Badge
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/ambv/black
:alt: Code style: black
Powered by Cookiecutter_, Cookiecutter Django is a framework for jumpstarting
production-ready Django projects quickly.
* Documentation: https://cookiecutter-django.readthedocs.io/en/latest/
* See Troubleshooting_ for common errors and obstacles
* If you have problems with Cookiecutter Django, please open issues_ don't send
emails to the maintainers.
.. _Troubleshooting: https://cookiecutter-django.readthedocs.io/en/latest/troubleshooting.html
.. _528: https://github.com/pydanny/cookiecutter-django/issues/528#issuecomment-212650373
.. _issues: https://github.com/pydanny/cookiecutter-django/issues/new
Features
---------
* For Django 3.1
* Works with Python 3.9
* Renders Django projects with 100% starting test coverage
* Twitter Bootstrap_ v4 (`maintained Foundation fork`_ also available)
* 12-Factor_ based settings via django-environ_
* Secure by default. We believe in SSL.
* Optimized development and production settings
* Registration via django-allauth_
* Comes with custom user model ready to go
* Optional basic ASGI setup for Websockets
* Optional custom static build using Gulp and livereload
* Send emails via Anymail_ (using Mailgun_ by default or Amazon SES if AWS is selected cloud provider, but switchable)
* Media storage using Amazon S3 or Google Cloud Storage
* Docker support using docker-compose_ for development and production (using Traefik_ with LetsEncrypt_ support)
* Procfile_ for deploying to Heroku
* Instructions for deploying to PythonAnywhere_
* Run tests with unittest or pytest
* Customizable PostgreSQL version
* Default integration with pre-commit_ for identifying simple issues before submission to code review
.. _`maintained Foundation fork`: https://github.com/Parbhat/cookiecutter-django-foundation
Optional Integrations
---------------------
*These features can be enabled during initial project setup.*
* Serve static files from Amazon S3, Google Cloud Storage or Whitenoise_
* Configuration for Celery_ and Flower_ (the latter in Docker setup only)
* Integration with MailHog_ for local email testing
* Integration with Sentry_ for error logging
.. _Bootstrap: https://github.com/twbs/bootstrap
.. _django-environ: https://github.com/joke2k/django-environ
.. _12-Factor: http://12factor.net/
.. _django-allauth: https://github.com/pennersr/django-allauth
.. _django-avatar: https://github.com/grantmcconnaughey/django-avatar
.. _Procfile: https://devcenter.heroku.com/articles/procfile
.. _Mailgun: http://www.mailgun.com/
.. _Whitenoise: https://whitenoise.readthedocs.io/
.. _Celery: http://www.celeryproject.org/
.. _Flower: https://github.com/mher/flower
.. _Anymail: https://github.com/anymail/django-anymail
.. _MailHog: https://github.com/mailhog/MailHog
.. _Sentry: https://sentry.io/welcome/
.. _docker-compose: https://github.com/docker/compose
.. _PythonAnywhere: https://www.pythonanywhere.com/
.. _Traefik: https://traefik.io/
.. _LetsEncrypt: https://letsencrypt.org/
.. _pre-commit: https://github.com/pre-commit/pre-commit
Constraints
-----------
* Only maintained 3rd party libraries are used.
* Uses PostgreSQL everywhere (10.16 - 13.2)
* 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.
Projects that provide financial support to the maintainers:
~~~~~~~~~~~~~~~~~~~~~~~~~
.. image:: 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
:name: Two Scoops of Django 3.x
:align: center
:alt: Two Scoops of Django
:target: https://www.feldroy.com/products//two-scoops-of-django-3-x
Two Scoops of Django 3.x is the best ice cream-themed Django reference in the universe!
pyup
~~~~~~~~~~~~~~~~~~
.. image:: https://pyup.io/static/images/logo.png
:name: pyup
:align: center
:alt: pyup
:target: https://pyup.io/
Pyup brings you automated security and dependency updates used by Google and other organizations. Free for open source projects!
Usage
------
Let's pretend you want to create a Django project called "redditclone". Rather than using ``startproject``
and then editing the results to include your name, email, and various configuration issues that always get forgotten until the worst possible moment, get cookiecutter_ to do all the work.
First, get Cookiecutter. Trust me, it's awesome::
$ pip install "cookiecutter>=1.7.0"
Now run it against this repo::
$ cookiecutter https://github.com/pydanny/cookiecutter-django
You'll be prompted for some values. Provide them, then a Django project will be created for you.
**Warning**: After this point, change 'Daniel Greenfeld', 'pydanny', etc to your own information.
Answer the prompts with your own desired options_. For example::
Cloning into 'cookiecutter-django'...
remote: Counting objects: 550, done.
remote: Compressing objects: 100% (310/310), done.
remote: Total 550 (delta 283), reused 479 (delta 222)
Receiving objects: 100% (550/550), 127.66 KiB | 58 KiB/s, done.
Resolving deltas: 100% (283/283), done.
project_name [Project Name]: Reddit Clone
project_slug [reddit_clone]: reddit
author_name [Daniel Roy Greenfeld]: Daniel Greenfeld
email [you@example.com]: pydanny@gmail.com
description [Behold My Awesome Project!]: A reddit clone.
domain_name [example.com]: myreddit.com
version [0.1.0]: 0.0.1
timezone [UTC]: America/Los_Angeles
use_whitenoise [n]: n
use_celery [n]: y
use_mailhog [n]: n
use_sentry [n]: y
use_pycharm [n]: y
windows [n]: n
use_docker [n]: n
use_heroku [n]: y
use_compressor [n]: y
Select postgresql_version:
1 - 13.2
2 - 12.6
3 - 11.11
4 - 10.16
Choose from 1, 2, 3, 4, 5 [1]: 1
Select js_task_runner:
1 - None
2 - Gulp
Choose from 1, 2 [1]: 1
Select cloud_provider:
1 - AWS
2 - GCP
3 - None
Choose from 1, 2, 3 [1]: 1
custom_bootstrap_compilation [n]: n
Select open_source_license:
1 - MIT
2 - BSD
3 - GPLv3
4 - Apache Software License 2.0
5 - Not open source
Choose from 1, 2, 3, 4, 5 [1]: 1
keep_local_envs_in_vcs [y]: y
debug[n]: n
Enter the project and take a look around::
$ cd reddit/
$ ls
Create a git repo and push it there::
$ git init
$ git add .
$ git commit -m "first awesome commit"
$ git remote add origin git@github.com:pydanny/redditclone.git
$ git push -u origin master
Now take a look at your repo. Don't forget to carefully look at the generated README. Awesome, right?
For local development, see the following:
* `Developing locally`_
* `Developing locally using docker`_
.. _options: http://cookiecutter-django.readthedocs.io/en/latest/project-generation-options.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`_ 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_.
* For anything else, you can chat with us on `Slack`_.
.. _`Stack Overflow`: http://stackoverflow.com/questions/tagged/cookiecutter-django
.. _`issue`: https://github.com/pydanny/cookiecutter-django/issues
.. _`Slack`: https://join.slack.com/t/cookie-cutter/shared_invite/enQtNzI0Mzg5NjE5Nzk5LTRlYWI2YTZhYmQ4YmU1Y2Q2NmE1ZjkwOGM0NDQyNTIwY2M4ZTgyNDVkNjMxMDdhZGI5ZGE5YmJjM2M3ODJlY2U
For Readers of Two Scoops of Django
--------------------------------------------
You may notice that some elements of this project do not exactly match what we describe in chapter 3. The reason for that is this project, amongst other things, serves as a test bed for trying out new ideas and concepts. Sometimes they work, sometimes they don't, but the end result is that it won't necessarily match precisely what is described in the book I co-authored.
For pyup.io Users
-----------------
If you are using `pyup.io`_ to keep your dependencies updated and secure, use the code *cookiecutter* during checkout to get 15% off every month.
.. _`pyup.io`: https://pyup.io
"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.
Releases
--------
Need a stable release? You can find them at https://github.com/pydanny/cookiecutter-django/releases
Not Exactly What You Want?
---------------------------
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.
It's up to you whether or not to rename your fork.
If you do rename your fork, I encourage you to submit it to the following places:
* cookiecutter_ so it gets listed in the README as a template.
* The cookiecutter grid_ on Django Packages.
.. _cookiecutter: https://github.com/cookiecutter/cookiecutter
.. _grid: https://www.djangopackages.com/grids/g/cookiecutters/
Submit a Pull Request
~~~~~~~~~~~~~~~~~~~~~~
We accept pull requests if they're small, atomic, and make our own project development
experience better.
Articles
---------
* `Using cookiecutter-django with Google Cloud Storage`_ - Mar. 12, 2019
* `cookiecutter-django with Nginx, Route 53 and ELB`_ - Feb. 12, 2018
* `cookiecutter-django and Amazon RDS`_ - Feb. 7, 2018
* `Using Cookiecutter to Jumpstart a Django Project on Windows with PyCharm`_ - May 19, 2017
* `Exploring with Cookiecutter`_ - Dec. 3, 2016
* `Introduction to Cookiecutter-Django`_ - Feb. 19, 2016
* `Django and GitLab - Running Continuous Integration and tests with your FREE account`_ - May. 11, 2016
* `Development and Deployment of Cookiecutter-Django on Fedora`_ - Jan. 18, 2016
* `Development and Deployment of Cookiecutter-Django via Docker`_ - Dec. 29, 2015
* `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.
.. _`Using cookiecutter-django with Google Cloud Storage`: https://ahhda.github.io/cloud/gce/django/2019/03/12/using-django-cookiecutter-cloud-storage.html
.. _`cookiecutter-django with Nginx, Route 53 and ELB`: https://msaizar.com/blog/cookiecutter-django-nginx-route-53-and-elb/
.. _`cookiecutter-django and Amazon RDS`: https://msaizar.com/blog/cookiecutter-django-and-amazon-rds/
.. _`Exploring with Cookiecutter`: http://www.snowboardingcoder.com/django/2016/12/03/exploring-with-cookiecutter/
.. _`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/
.. _`Development and Deployment of Cookiecutter-Django via Docker`: https://realpython.com/blog/python/development-and-deployment-of-cookiecutter-django-via-docker/
.. _`Development and Deployment of Cookiecutter-Django on Fedora`: https://realpython.com/blog/python/development-and-deployment-of-cookiecutter-django-on-fedora/
.. _`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/
.. _`Introduction to Cookiecutter-Django`: http://krzysztofzuraw.com/blog/2016/django-cookiecutter.html
.. _`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
Code of Conduct
---------------
Everyone interacting in the Cookiecutter project's codebases, issue trackers, chat
rooms, and mailing lists is expected to follow the `PyPA Code of Conduct`_.
.. _`PyPA Code of Conduct`: https://www.pypa.io/en/latest/code-of-conduct/

View File

@ -18,18 +18,16 @@
"use_pycharm": "n", "use_pycharm": "n",
"use_docker": "n", "use_docker": "n",
"postgresql_version": [ "postgresql_version": [
"13.2", "14",
"12.6", "13",
"11.11", "12",
"10.16" "11",
], "10"
"js_task_runner": [
"None",
"Gulp"
], ],
"cloud_provider": [ "cloud_provider": [
"AWS", "AWS",
"GCP", "GCP",
"Azure",
"None" "None"
], ],
"mail_service": [ "mail_service": [
@ -45,8 +43,11 @@
], ],
"use_async": "n", "use_async": "n",
"use_drf": "n", "use_drf": "n",
"custom_bootstrap_compilation": "n", "frontend_pipeline": [
"use_compressor": "n", "None",
"Django Compressor",
"Gulp"
],
"use_celery": "n", "use_celery": "n",
"use_mailhog": "n", "use_mailhog": "n",
"use_sentry": "n", "use_sentry": "n",

View File

@ -7,10 +7,7 @@
# #
# All configuration values have a default; values that are commented out # All configuration values have a default; values that are commented out
# serve to show the default. # serve to show the default.
from datetime import datetime from datetime import datetime
import os
import sys
now = datetime.now() now = datetime.now()
@ -42,7 +39,7 @@ master_doc = "index"
# General information about the project. # General information about the project.
project = "Cookiecutter Django" project = "Cookiecutter Django"
copyright = "2013-{}, Daniel Roy Greenfeld".format(now.year) copyright = f"2013-{now.year}, Daniel Roy Greenfeld"
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
@ -92,7 +89,7 @@ pygments_style = "sphinx"
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
html_theme = "default" html_theme = "sphinx_rtd_theme"
# Theme options are theme-specific and customize the look and feel of a theme # Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the # further. For a list of options available for each theme, see the
@ -242,7 +239,8 @@ texinfo_documents = [
"Cookiecutter Django documentation", "Cookiecutter Django documentation",
"Daniel Roy Greenfeld", "Daniel Roy Greenfeld",
"Cookiecutter Django", "Cookiecutter Django",
"A Cookiecutter template for creating production-ready Django projects quickly.", "A Cookiecutter template for creating production-ready "
"Django projects quickly.",
"Miscellaneous", "Miscellaneous",
) )
] ]

View File

@ -3,8 +3,8 @@ Deployment on Heroku
.. index:: Heroku .. index:: Heroku
Commands to run Script
--------------- ------
Run these commands to deploy the project to Heroku: Run these commands to deploy the project to Heroku:
@ -12,14 +12,15 @@ Run these commands to deploy the project to Heroku:
heroku create --buildpack heroku/python heroku create --buildpack heroku/python
heroku addons:create heroku-postgresql:hobby-dev heroku addons:create heroku-postgresql:mini
# On Windows use double quotes for the time zone, e.g. # On Windows use double quotes for the time zone, e.g.
# heroku pg:backups schedule --at "02:00 America/Los_Angeles" DATABASE_URL # heroku pg:backups schedule --at "02:00 America/Los_Angeles" DATABASE_URL
heroku pg:backups schedule --at '02:00 America/Los_Angeles' DATABASE_URL heroku pg:backups schedule --at '02:00 America/Los_Angeles' DATABASE_URL
heroku pg:promote DATABASE_URL heroku pg:promote DATABASE_URL
heroku addons:create heroku-redis:hobby-dev heroku addons:create heroku-redis:mini
# Assuming you chose Mailgun as mail service (see below for others)
heroku addons:create mailgun:starter heroku addons:create mailgun:starter
heroku config:set PYTHONHASHSEED=random heroku config:set PYTHONHASHSEED=random
@ -53,11 +54,25 @@ Run these commands to deploy the project to Heroku:
heroku open heroku open
Notes
-----
Email Service
+++++++++++++
The script above assumes that you've chose Mailgun as email service. If you want to use another one, check the `documentation for django-anymail <https://anymail.readthedocs.io>`_ to know which environment variables to set. Heroku provides other `add-ons for emails <https://elements.heroku.com/addons#email-sms>`_ (e.g. Sendgrid) which can be configured with a similar one line command.
.. warning:: .. warning::
.. include:: mailgun.rst .. include:: mailgun.rst
Heroku & Docker
+++++++++++++++
Although Heroku has some sort of `Docker support`_, it's not supported by cookiecutter-django.
We invite you to follow Heroku documentation about it.
.. _Docker support: https://devcenter.heroku.com/articles/build-docker-images-heroku-yml
Optional actions Optional actions
---------------- ----------------
@ -97,7 +112,7 @@ Or add the DSN for your account, if you already have one:
Gulp & Bootstrap compilation Gulp & Bootstrap compilation
++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++
If you've opted for a custom bootstrap build, you'll most likely need to setup If you've opted for Gulp, you'll most likely need to setup
your app to use `multiple buildpacks`_: one for Python & one for Node.js: your app to use `multiple buildpacks`_: one for Python & one for Node.js:
.. code-block:: bash .. code-block:: bash
@ -111,11 +126,3 @@ which runs Gulp in cookiecutter-django.
If things don't work, please refer to the Heroku docs. If things don't work, please refer to the Heroku docs.
.. _multiple buildpacks: https://devcenter.heroku.com/articles/using-multiple-buildpacks-for-an-app .. _multiple buildpacks: https://devcenter.heroku.com/articles/using-multiple-buildpacks-for-an-app
About Heroku & Docker
---------------------
Although Heroku has some sort of `Docker support`_, it's not supported by cookiecutter-django.
We invite you to follow Heroku documentation about it.
.. _Docker support: https://devcenter.heroku.com/articles/build-docker-images-heroku-yml

View File

@ -15,7 +15,7 @@ Full instructions follow, but here's a high-level view.
2. Set your config variables in the *postactivate* script 2. Set your config variables in the *postactivate* script
3. Run the *manage.py* ``migrate`` and ``collectstatic`` {%- if cookiecutter.use_compressor == "y" %}and ``compress`` {%- endif %}commands 3. Run the *manage.py* ``migrate`` and ``collectstatic`` commands. If you've opted for django-compressor, also run ``compress``
4. Add an entry to the PythonAnywhere *Web tab* 4. Add an entry to the PythonAnywhere *Web tab*
@ -25,7 +25,6 @@ Full instructions follow, but here's a high-level view.
Once you've been through this one-off config, future deployments are much simpler: just ``git pull`` and then hit the "Reload" button :) Once you've been through this one-off config, future deployments are much simpler: just ``git pull`` and then hit the "Reload" button :)
Getting your code and dependencies installed on PythonAnywhere Getting your code and dependencies installed on PythonAnywhere
-------------------------------------------------------------- --------------------------------------------------------------
@ -35,11 +34,10 @@ Make sure your project is fully committed and pushed up to Bitbucket or Github o
git clone <my-repo-url> # you can also use hg git clone <my-repo-url> # you can also use hg
cd my-project-name cd my-project-name
mkvirtualenv --python=/usr/bin/python3.9 my-project-name mkvirtualenv --python=/usr/bin/python3.10 my-project-name
pip install -r requirements/production.txt # may take a few minutes pip install -r requirements/production.txt # may take a few minutes
Setting environment variables in the console Setting environment variables in the console
-------------------------------------------- --------------------------------------------
@ -57,7 +55,7 @@ Set environment variables via the virtualenv "postactivate" script (this will se
vi $VIRTUAL_ENV/bin/postactivate vi $VIRTUAL_ENV/bin/postactivate
**TIP:** *If you don't like vi, you can also edit this file via the PythonAnywhere "Files" menu; look in the ".virtualenvs" folder*. .. note:: If you don't like vi, you can also edit this file via the PythonAnywhere "Files" menu; look in the ".virtualenvs" folder.
Add these exports Add these exports
@ -73,13 +71,14 @@ Add these exports
export DJANGO_AWS_ACCESS_KEY_ID= export DJANGO_AWS_ACCESS_KEY_ID=
export DJANGO_AWS_SECRET_ACCESS_KEY= export DJANGO_AWS_SECRET_ACCESS_KEY=
export DJANGO_AWS_STORAGE_BUCKET_NAME= export DJANGO_AWS_STORAGE_BUCKET_NAME=
export DATABASE_URL='<see below>' export DATABASE_URL='<see Database setup section below>'
export REDIS_URL='<see Redis section below>'
**NOTE:** *The AWS details are not required if you're using whitenoise or the built-in pythonanywhere static files service, but you do need to set them to blank, as above.* .. note:: The AWS details are not required if you're using whitenoise or the built-in pythonanywhere static files service, but you do need to set them to blank, as above.
Database setup: Database setup
--------------- --------------
Go to the PythonAnywhere **Databases tab** and configure your database. Go to the PythonAnywhere **Databases tab** and configure your database.
@ -109,18 +108,26 @@ Now run the migration, and collectstatic:
source $VIRTUAL_ENV/bin/postactivate source $VIRTUAL_ENV/bin/postactivate
python manage.py migrate python manage.py migrate
python manage.py collectstatic python manage.py collectstatic
{%- if cookiecutter.use_compressor == "y" %}python manage.py compress {%- endif %} # if using django-compressor:
python manage.py compress
# and, optionally # and, optionally
python manage.py createsuperuser python manage.py createsuperuser
Redis
-----
PythonAnywhere does NOT `offer a built-in solution <https://www.pythonanywhere.com/forums/topic/1666/>`_ for Redis, however the production setup from Cookiecutter Django uses Redis as cache and requires one.
We recommend to signup to a separate service offering hosted Redis (e.g. `Redislab <https://redis.com/>`_) and use the URL they provide.
Configure the PythonAnywhere Web Tab Configure the PythonAnywhere Web Tab
------------------------------------ ------------------------------------
Go to the PythonAnywhere **Web tab**, hit **Add new web app**, and choose **Manual Config**, and then the version of Python you used for your virtualenv. Go to the PythonAnywhere **Web tab**, hit **Add new web app**, and choose **Manual Config**, and then the version of Python you used for your virtualenv.
**NOTE:** *If you're using a custom domain (not on \*.pythonanywhere.com), then you'll need to set up a CNAME with your domain registrar.* .. note:: If you're using a custom domain (not on \*.pythonanywhere.com), then you'll need to set up a CNAME with your domain registrar.
When you're redirected back to the web app config screen, set the **path to your virtualenv**. If you used virtualenvwrapper as above, you can just enter its name. When you're redirected back to the web app config screen, set the **path to your virtualenv**. If you used virtualenvwrapper as above, you can just enter its name.
@ -153,15 +160,14 @@ Click through to the **WSGI configuration file** link (near the top) and edit th
Back on the Web tab, hit **Reload**, and your app should be live! Back on the Web tab, hit **Reload**, and your app should be live!
**NOTE:** *you may see security warnings until you set up your SSL certificates. If you .. note:: You may see security warnings until you set up your SSL certificates. If you want to suppress them temporarily, set ``DJANGO_SECURE_SSL_REDIRECT`` to blank. Follow `these instructions <https://help.pythonanywhere.com/pages/HTTPSSetup>`_ to get SSL set up.
want to suppress them temporarily, set DJANGO_SECURE_SSL_REDIRECT to blank. Follow
the instructions here to get SSL set up: https://help.pythonanywhere.com/pages/SSLOwnDomains/*
Optional: static files Optional: static files
---------------------- ----------------------
If you want to use the PythonAnywhere static files service instead of using whitenoise or S3, you'll find its configuration section on the Web tab. Essentially you'll need an entry to match your ``STATIC_URL`` and ``STATIC_ROOT`` settings. There's more info here: https://help.pythonanywhere.com/pages/DjangoStaticFiles If you want to use the PythonAnywhere static files service instead of using whitenoise or S3, you'll find its configuration section on the Web tab. Essentially you'll need an entry to match your ``STATIC_URL`` and ``STATIC_ROOT`` settings. There's more info `in this article <https://help.pythonanywhere.com/pages/DjangoStaticFiles>`_.
Future deployments Future deployments
@ -176,8 +182,9 @@ For subsequent deployments, the procedure is much simpler. In a Bash console:
git pull git pull
python manage.py migrate python manage.py migrate
python manage.py collectstatic python manage.py collectstatic
{%- if cookiecutter.use_compressor == "y" %}python manage.py compress {%- endif %} # if using django-compressor:
python manage.py compress
And then go to the Web tab and hit **Reload** And then go to the Web tab and hit **Reload**
**TIP:** *if you're really keen, you can set up git-push based deployments: https://blog.pythonanywhere.com/87/* .. note:: If you're really keen, you can set up git-push based deployments: https://blog.pythonanywhere.com/87/

View File

@ -3,9 +3,6 @@ Getting Up and Running Locally With Docker
.. index:: Docker .. index:: Docker
The steps below will get you up and running with a local development environment.
All of these commands assume you are in the root of your generated project.
.. note:: .. note::
If you're new to Docker, please be aware that some resources are cached system-wide If you're new to Docker, please be aware that some resources are cached system-wide
@ -18,11 +15,17 @@ Prerequisites
* Docker; if you don't have it yet, follow the `installation instructions`_; * Docker; if you don't have it yet, follow the `installation instructions`_;
* Docker Compose; refer to the official documentation for the `installation guide`_. * Docker Compose; refer to the official documentation for the `installation guide`_.
* Pre-commit; refer to the official documentation for the [pre-commit](https://pre-commit.com/#install). * Pre-commit; refer to the official documentation for the `pre-commit`_.
* Cookiecutter; refer to the official GitHub repository of `Cookiecutter`_
.. _`installation instructions`: https://docs.docker.com/install/#supported-platforms .. _`installation instructions`: https://docs.docker.com/install/#supported-platforms
.. _`installation guide`: https://docs.docker.com/compose/install/ .. _`installation guide`: https://docs.docker.com/compose/install/
.. _`pre-commit`: https://pre-commit.com/#install .. _`pre-commit`: https://pre-commit.com/#install
.. _`Cookiecutter`: https://github.com/cookiecutter/cookiecutter
Before Getting Started
----------------------
.. include:: generate-project-block.rst
Build the Stack Build the Stack
--------------- ---------------
@ -167,16 +170,18 @@ docker
The ``container_name`` from the yml file can be used to check on containers with docker commands, for example: :: The ``container_name`` from the yml file can be used to check on containers with docker commands, for example: ::
$ docker logs worker $ docker logs <project_slug>_local_celeryworker
$ docker top worker $ docker top <project_slug>_local_celeryworker
Notice that the ``container_name`` is generated dynamically using your project slug as a prefix
Mailhog Mailhog
~~~~~~~ ~~~~~~~
When developing locally you can go with MailHog_ for email testing provided ``use_mailhog`` was set to ``y`` on setup. To proceed, When developing locally you can go with MailHog_ for email testing provided ``use_mailhog`` was set to ``y`` on setup. To proceed,
#. make sure ``mailhog`` container is up and running; #. make sure ``<project_slug>_local_mailhog`` container is up and running;
#. open up ``http://127.0.0.1:8025``. #. open up ``http://127.0.0.1:8025``.
@ -188,7 +193,7 @@ Celery tasks in local development
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When not using docker Celery tasks are set to run in Eager mode, so that a full stack is not needed. When using docker the task scheduler will be used by default. When not using docker Celery tasks are set to run in Eager mode, so that a full stack is not needed. When using docker the task scheduler will be used by default.
If you need tasks to be executed on the main thread during development set CELERY_TASK_ALWAYS_EAGER = True in config/settings/local.py. If you need tasks to be executed on the main thread during development set ``CELERY_TASK_ALWAYS_EAGER = True`` in ``config/settings/local.py``.
Possible uses could be for testing, or ease of profiling with DJDT. Possible uses could be for testing, or ease of profiling with DJDT.
@ -213,7 +218,7 @@ Developing locally with HTTPS
Increasingly it is becoming necessary to develop software in a secure environment in order that there are very few changes when deploying to production. Recently Facebook changed their policies for apps/sites that use Facebook login which requires the use of an HTTPS URL for the OAuth redirect URL. So if you want to use the ``users`` application with a OAuth provider such as Facebook, securing your communication to the local development environment will be necessary. Increasingly it is becoming necessary to develop software in a secure environment in order that there are very few changes when deploying to production. Recently Facebook changed their policies for apps/sites that use Facebook login which requires the use of an HTTPS URL for the OAuth redirect URL. So if you want to use the ``users`` application with a OAuth provider such as Facebook, securing your communication to the local development environment will be necessary.
In order to create a secure environment, we need to have a trusted SSL certficate installed in our Docker application. In order to create a secure environment, we need to have a trusted SSL certificate installed in our Docker application.
#. **Let's Encrypt** #. **Let's Encrypt**

View File

@ -9,7 +9,7 @@ Setting Up Development Environment
Make sure to have the following on your host: Make sure to have the following on your host:
* Python 3.9 * Python 3.10
* PostgreSQL_. * PostgreSQL_.
* Redis_, if using Celery * Redis_, if using Celery
* Cookiecutter_ * Cookiecutter_
@ -18,15 +18,14 @@ First things first.
#. Create a virtualenv: :: #. Create a virtualenv: ::
$ python3.9 -m venv <virtual env path> $ python3.10 -m venv <virtual env path>
#. Activate the virtualenv you have just created: :: #. Activate the virtualenv you have just created: ::
$ source <virtual env path>/bin/activate $ source <virtual env path>/bin/activate
#. Install cookiecutter-django: :: #.
.. include:: generate-project-block.rst
$ cookiecutter gh:pydanny/cookiecutter-django
#. Install development requirements: :: #. Install development requirements: ::
@ -42,7 +41,9 @@ First things first.
#. Create a new PostgreSQL database using createdb_: :: #. Create a new PostgreSQL database using createdb_: ::
$ createdb <what you have entered as the project_slug at setup stage> -U postgres --password <password> $ createdb --username=postgres <project_slug>
``project_slug`` is what you have entered as the project_slug at the setup stage.
.. note:: .. note::
@ -81,13 +82,13 @@ First things first.
or if you're running asynchronously: :: or if you're running asynchronously: ::
$ uvicorn config.asgi:application --host 0.0.0.0 --reload $ uvicorn config.asgi:application --host 0.0.0.0 --reload --reload-include '*.html'
.. _PostgreSQL: https://www.postgresql.org/download/ .. _PostgreSQL: https://www.postgresql.org/download/
.. _Redis: https://redis.io/download .. _Redis: https://redis.io/download
.. _CookieCutter: https://github.com/cookiecutter/cookiecutter .. _CookieCutter: https://github.com/cookiecutter/cookiecutter
.. _createdb: https://www.postgresql.org/docs/current/static/app-createdb.html .. _createdb: https://www.postgresql.org/docs/current/static/app-createdb.html
.. _initial PostgreSQL set up: http://suite.opengeo.org/docs/latest/dataadmin/pgGettingStarted/firstconnect.html .. _initial PostgreSQL set up: https://web.archive.org/web/20190303010033/http://suite.opengeo.org/docs/latest/dataadmin/pgGettingStarted/firstconnect.html
.. _postgres documentation: https://www.postgresql.org/docs/current/static/auth-pg-hba-conf.html .. _postgres documentation: https://www.postgresql.org/docs/current/static/auth-pg-hba-conf.html
.. _pre-commit: https://pre-commit.com/ .. _pre-commit: https://pre-commit.com/
.. _direnv: https://direnv.net/ .. _direnv: https://direnv.net/
@ -140,22 +141,55 @@ In production, we have Mailgun_ configured to have your back!
Celery Celery
------ ------
If the project is configured to use Celery as a task scheduler then by default tasks are set to run on the main thread If the project is configured to use Celery as a task scheduler then, by default, tasks are set to run on the main thread when developing locally instead of getting sent to a broker. However, if you have Redis setup on your local machine, you can set the following in ``config/settings/local.py``::
when developing locally. If you have the appropriate setup on your local machine then set the following
in ``config/settings/local.py``::
CELERY_TASK_ALWAYS_EAGER = False CELERY_TASK_ALWAYS_EAGER = False
To run Celery locally, make sure redis-server is installed (instructions are available at https://redis.io/topics/quickstart), run the server in one terminal with `redis-server`, and then start celery in another terminal with the following command:: Next, make sure `redis-server` is installed (per the `Getting started with Redis`_ guide) and run the server in one terminal::
celery -A config.celery_app worker --loglevel=info $ redis-server
Start the Celery worker by running the following command in another terminal::
$ celery -A config.celery_app worker --loglevel=info
That Celery worker should be running whenever your app is running, typically as a background process,
so that it can pick up any tasks that get queued. Learn more from the `Celery Workers Guide`_.
The project comes with a simple task for manual testing purposes, inside `<project_slug>/users/tasks.py`. To queue that task locally, start the Django shell, import the task, and call `delay()` on it::
$ python manage.py shell
>> from <project_slug>.users.tasks import get_users_count
>> get_users_count.delay()
You can also use Django admin to queue up tasks, thanks to the `django-celerybeat`_ package.
.. _Getting started with Redis guide: https://redis.io/docs/getting-started/
.. _Celery Workers Guide: https://docs.celeryq.dev/en/stable/userguide/workers.html
.. _django-celerybeat: https://django-celery-beat.readthedocs.io/en/latest/
Sass Compilation & Live Reloading Sass Compilation & Live Reloading
--------------------------------- ---------------------------------
If youd like to take advantage of live reloading and Sass compilation you can do so with a little If you've opted for Gulp as front-end pipeline, the project comes configured with `Sass`_ compilation and `live reloading`_. As you change you Sass/JS source files, the task runner will automatically rebuild the corresponding CSS and JS assets and reload them in your browser without refreshing the page.
bit of preparation, see :ref:`sass-compilation-live-reload`.
#. Make sure that `Node.js`_ v16 is installed on your machine.
#. In the project root, install the JS dependencies with::
$ npm install
#. Now - with your virtualenv activated - start the application by running::
$ npm run dev
The app will now run with live reloading enabled, applying front-end changes dynamically.
.. note:: The task will start 2 processes in parallel: the static assets build loop on one side, and the Django server on the other. You do NOT need to run Django as your would normally with ``manage.py runserver``.
.. _Node.js: http://nodejs.org/download/
.. _Sass: https://sass-lang.com/
.. _live reloading: https://browsersync.io
Summary Summary
------- -------

View File

@ -13,7 +13,7 @@ If you set up your project to `develop locally with docker`_, run the following
$ docker-compose -f local.yml up docs $ docker-compose -f local.yml up docs
Navigate to port 7000 on your host to see the documentation. This will be opened automatically at `localhost`_ for local, non-docker development. Navigate to port 9000 on your host to see the documentation. This will be opened automatically at `localhost`_ for local, non-docker development.
Note: using Docker for documentation sets up a temporary SQLite file by setting the environment variable ``DATABASE_URL=sqlite:///readthedocs.db`` in ``docs/conf.py`` to avoid a dependency on PostgreSQL. Note: using Docker for documentation sets up a temporary SQLite file by setting the environment variable ``DATABASE_URL=sqlite:///readthedocs.db`` in ``docs/conf.py`` to avoid a dependency on PostgreSQL.
@ -36,7 +36,7 @@ To setup your documentation on `ReadTheDocs`_, you must
Additionally, you can auto-build Pull Request previews, but `you must enable it`_. Additionally, you can auto-build Pull Request previews, but `you must enable it`_.
.. _localhost: http://localhost:7000/ .. _localhost: http://localhost:9000/
.. _Sphinx: https://www.sphinx-doc.org/en/master/index.html .. _Sphinx: https://www.sphinx-doc.org/en/master/index.html
.. _develop locally: ./developing-locally.html .. _develop locally: ./developing-locally.html
.. _develop locally with docker: ./developing-locally-docker.html .. _develop locally with docker: ./developing-locally-docker.html

View File

@ -6,11 +6,11 @@ FAQ
Why is there a django.contrib.sites directory in Cookiecutter Django? Why is there a django.contrib.sites directory in Cookiecutter Django?
--------------------------------------------------------------------- ---------------------------------------------------------------------
It is there to add a migration so you don't have to manually change the ``sites.Site`` record from ``example.com`` to whatever your domain is. Instead, your ``{{cookiecutter.domain_name}}`` and {{cookiecutter.project_name}} value is placed by **Cookiecutter** in the domain and name fields respectively. It is there to add a migration so you don't have to manually change the ``sites.Site`` record from ``example.com`` to whatever your domain is. Instead, your ``{{cookiecutter.domain_name}}`` and ``{{cookiecutter.project_name}}`` value is placed by **Cookiecutter** in the domain and name fields respectively.
See `0003_set_site_domain_and_name.py`_. See `0003_set_site_domain_and_name.py`_.
.. _`0003_set_site_domain_and_name.py`: https://github.com/pydanny/cookiecutter-django/blob/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/contrib/sites/migrations/0003_set_site_domain_and_name.py .. _`0003_set_site_domain_and_name.py`: https://github.com/cookiecutter/cookiecutter-django/blob/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/contrib/sites/migrations/0003_set_site_domain_and_name.py
Why aren't you using just one configuration file (12-Factor App) Why aren't you using just one configuration file (12-Factor App)

View File

@ -0,0 +1,7 @@
Generate a new cookiecutter-django project: ::
$ cookiecutter gh:cookiecutter/cookiecutter-django
For more information refer to
:ref:`Project Generation Options <template-options>`.

View File

@ -1,13 +1,14 @@
.. cookiecutter-django documentation master file. .. cookiecutter-django documentation master file.
Welcome to Cookiecutter Django's documentation! Welcome to Cookiecutter Django's documentation!
==================================================================== ===============================================
A Cookiecutter_ template for Django. Powered by Cookiecutter_, Cookiecutter Django is a project template for jumpstarting production-ready Django projects. The template offers a number of generation options, we invite you to check the :ref:`dedicated page <template-options>` to learn more about each of them.
.. _cookiecutter: https://github.com/cookiecutter/cookiecutter .. _cookiecutter: https://github.com/cookiecutter/cookiecutter
Contents: Contents
--------
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
@ -28,7 +29,7 @@ Contents:
troubleshooting troubleshooting
Indices and tables Indices and tables
================== ------------------
* :ref:`genindex` * :ref:`genindex`
* :ref:`search` * :ref:`search`

View File

@ -1,24 +0,0 @@
.. _sass-compilation-live-reload:
Sass Compilation & Live Reloading
=================================
If you'd like to take advantage of `live reload`_ and Sass compilation:
- Make sure that nodejs_ is installed. Then in the project root run::
$ npm install
.. _nodejs: http://nodejs.org/download/
- Now you just need::
$ npm run dev
The base app will now run as it would with the usual ``manage.py runserver`` but with live reloading and Sass compilation enabled.
When changing your Sass files, they will be automatically recompiled and change will be reflected in your browser without refreshing.
To get live reloading to work you'll probably need to install an `appropriate browser extension`_
.. _live reload: http://livereload.com/
.. _appropriate browser extension: http://livereload.com/extensions/

View File

@ -1,7 +1,7 @@
If your email server used to send email isn't configured properly (Mailgun by default), If your email server used to send email isn't configured properly (Mailgun by default),
attempting to send an email will cause an Internal Server Error. attempting to send an email will cause an Internal Server Error.
By default, django-allauth is setup to `have emails verifications mandatory`_, By default, ``django-allauth`` is setup to `have emails verifications mandatory`_,
which means it'll send a verification email when an unverified user tries to which means it'll send a verification email when an unverified user tries to
log-in or when someone tries to sign-up. log-in or when someone tries to sign-up.

View File

@ -1,6 +1,12 @@
.. _template-options:
Project Generation Options Project Generation Options
========================== ==========================
This page describes all the template options that will be prompted by the `cookiecutter CLI`_ prior to generating your project.
.. _cookiecutter CLI: https://github.com/cookiecutter/cookiecutter
project_name: project_name:
Your project's human-readable name, capitals and spaces allowed. Your project's human-readable name, capitals and spaces allowed.
@ -49,23 +55,19 @@ use_docker:
postgresql_version: postgresql_version:
Select a PostgreSQL_ version to use. The choices are: Select a PostgreSQL_ version to use. The choices are:
1. 13.2 1. 14
2. 12.6 2. 13
3. 11.11 3. 12
4. 10.16 4. 11
5. 10
js_task_runner:
Select a JavaScript task runner. The choices are:
1. None
2. Gulp_
cloud_provider: cloud_provider:
Select a cloud provider for static & media files. The choices are: Select a cloud provider for static & media files. The choices are:
1. AWS_ 1. AWS_
2. GCP_ 2. GCP_
3. None 3. Azure_
4. None
Note that if you choose no cloud provider, media files won't work. Note that if you choose no cloud provider, media files won't work.
@ -88,13 +90,12 @@ use_async:
use_drf: use_drf:
Indicates whether the project should be configured to use `Django Rest Framework`_. Indicates whether the project should be configured to use `Django Rest Framework`_.
custom_bootstrap_compilation: frontend_pipeline:
Indicates whether the project should support Bootstrap recompilation Select a pipeline to compile and optimise frontend assets (JS, CSS, ...):
via the selected JavaScript task runner's task. This can be useful
for real-time Bootstrap variable alteration.
use_compressor: 1. None
Indicates whether the project should be configured to use `Django Compressor`_. 2. `Django Compressor`_
3. `Gulp`_: support Bootstrap recompilation with real-time variables alteration.
use_celery: use_celery:
Indicates whether the project should be configured to use Celery_. Indicates whether the project should be configured to use Celery_.
@ -147,6 +148,7 @@ debug:
.. _AWS: https://aws.amazon.com/s3/ .. _AWS: https://aws.amazon.com/s3/
.. _GCP: https://cloud.google.com/storage/ .. _GCP: https://cloud.google.com/storage/
.. _Azure: https://azure.microsoft.com/en-us/products/storage/blobs/
.. _Amazon SES: https://aws.amazon.com/ses/ .. _Amazon SES: https://aws.amazon.com/ses/
.. _Mailgun: https://www.mailgun.com .. _Mailgun: https://www.mailgun.com

2
docs/requirements.txt Normal file
View File

@ -0,0 +1,2 @@
sphinx==5.3.0
sphinx-rtd-theme==1.1.1

View File

@ -46,8 +46,12 @@ DJANGO_AWS_SECRET_ACCESS_KEY AWS_SECRET_ACCESS_KEY n/a
DJANGO_AWS_STORAGE_BUCKET_NAME AWS_STORAGE_BUCKET_NAME n/a raises error DJANGO_AWS_STORAGE_BUCKET_NAME AWS_STORAGE_BUCKET_NAME n/a raises error
DJANGO_AWS_S3_REGION_NAME AWS_S3_REGION_NAME n/a None DJANGO_AWS_S3_REGION_NAME AWS_S3_REGION_NAME n/a None
DJANGO_AWS_S3_CUSTOM_DOMAIN AWS_S3_CUSTOM_DOMAIN n/a None DJANGO_AWS_S3_CUSTOM_DOMAIN AWS_S3_CUSTOM_DOMAIN n/a None
DJANGO_AWS_S3_MAX_MEMORY_SIZE AWS_S3_MAX_MEMORY_SIZE n/a 100_000_000
DJANGO_GCP_STORAGE_BUCKET_NAME GS_BUCKET_NAME n/a raises error DJANGO_GCP_STORAGE_BUCKET_NAME GS_BUCKET_NAME n/a raises error
GOOGLE_APPLICATION_CREDENTIALS n/a n/a raises error GOOGLE_APPLICATION_CREDENTIALS n/a n/a raises error
DJANGO_AZURE_ACCOUNT_KEY AZURE_ACCOUNT_KEY n/a raises error
DJANGO_AZURE_ACCOUNT_NAME AZURE_ACCOUNT_NAME n/a raises error
DJANGO_AZURE_CONTAINER_NAME AZURE_CONTAINER n/a raises error
SENTRY_DSN SENTRY_DSN n/a raises error SENTRY_DSN SENTRY_DSN n/a raises error
SENTRY_ENVIRONMENT n/a n/a production SENTRY_ENVIRONMENT n/a n/a production
SENTRY_TRACES_SAMPLE_RATE n/a n/a 0.0 SENTRY_TRACES_SAMPLE_RATE n/a n/a 0.0

View File

@ -28,10 +28,15 @@ Coverage
You should build your tests to provide the highest level of **code coverage**. You can run the ``pytest`` with code ``coverage`` by typing in the following command: :: You should build your tests to provide the highest level of **code coverage**. You can run the ``pytest`` with code ``coverage`` by typing in the following command: ::
$ docker-compose -f local.yml run --rm django coverage run -m pytest $ coverage run -m pytest
Once the tests are complete, in order to see the code coverage, run the following command: :: Once the tests are complete, in order to see the code coverage, run the following command: ::
$ coverage report
If you're running the project locally with Docker, use these commands instead: ::
$ docker-compose -f local.yml run --rm django coverage run -m pytest
$ docker-compose -f local.yml run --rm django coverage report $ docker-compose -f local.yml run --rm django coverage report
.. note:: .. note::
@ -53,4 +58,4 @@ Once the tests are complete, in order to see the code coverage, run the followin
.. _develop locally with docker: ./developing-locally-docker.html .. _develop locally with docker: ./developing-locally-docker.html
.. _customize: https://docs.pytest.org/en/latest/customize.html .. _customize: https://docs.pytest.org/en/latest/customize.html
.. _unittest: https://docs.python.org/3/library/unittest.html#module-unittest .. _unittest: https://docs.python.org/3/library/unittest.html#module-unittest
.. _configuring: https://coverage.readthedocs.io/en/v4.5.x/config.html .. _configuring: https://coverage.readthedocs.io/en/latest/config.html

View File

@ -47,5 +47,5 @@ Others
#. To create a new app using the recommended directory structure, run `django-admin startapp --template=../startapp_template myappname` from inside the project root. This template can be customized to suit your needs. (see `startapp`_) #. To create a new app using the recommended directory structure, run `django-admin startapp --template=../startapp_template myappname` from inside the project root. This template can be customized to suit your needs. (see `startapp`_)
.. _#528: https://github.com/pydanny/cookiecutter-django/issues/528#issuecomment-212650373 .. _#528: https://github.com/cookiecutter/cookiecutter-django/issues/528#issuecomment-212650373
.. _startapp: https://docs.djangoproject.com/en/dev/ref/django-admin/#startapp .. _startapp: https://docs.djangoproject.com/en/dev/ref/django-admin/#startapp

View File

@ -5,7 +5,8 @@ NOTE:
can potentially be run in Python 2.x environment can potentially be run in Python 2.x environment
(at least so we presume in `pre_gen_project.py`). (at least so we presume in `pre_gen_project.py`).
TODO: ? restrict Cookiecutter Django project initialization to Python 3.x environments only TODO: restrict Cookiecutter Django project initialization to
Python 3.x environments only
""" """
from __future__ import print_function from __future__ import print_function
@ -59,6 +60,10 @@ def remove_docker_files():
file_names = ["local.yml", "production.yml", ".dockerignore"] file_names = ["local.yml", "production.yml", ".dockerignore"]
for file_name in file_names: for file_name in file_names:
os.remove(file_name) os.remove(file_name)
if "{{ cookiecutter.use_pycharm }}".lower() == "y":
file_names = ["docker_compose_up_django.xml", "docker_compose_up_docs.xml"]
for file_name in file_names:
os.remove(os.path.join(".idea", "runConfigurations", file_name))
def remove_utility_files(): def remove_utility_files():
@ -86,6 +91,11 @@ def remove_gulp_files():
file_names = ["gulpfile.js"] file_names = ["gulpfile.js"]
for file_name in file_names: for file_name in file_names:
os.remove(file_name) os.remove(file_name)
remove_sass_files()
def remove_sass_files():
shutil.rmtree(os.path.join("{{cookiecutter.project_slug}}", "static", "sass"))
def remove_packagejson_file(): def remove_packagejson_file():
@ -128,13 +138,6 @@ def remove_dotgithub_folder():
shutil.rmtree(".github") shutil.rmtree(".github")
def append_to_project_gitignore(path):
gitignore_file_path = ".gitignore"
with open(gitignore_file_path, "a") as gitignore_file:
gitignore_file.write(path)
gitignore_file.write(os.linesep)
def generate_random_string( def generate_random_string(
length, using_digits=False, using_ascii_letters=False, using_punctuation=False length, using_digits=False, using_ascii_letters=False, using_punctuation=False
): ):
@ -165,8 +168,8 @@ def set_flag(file_path, flag, value=None, formatted=None, *args, **kwargs):
random_string = generate_random_string(*args, **kwargs) random_string = generate_random_string(*args, **kwargs)
if random_string is None: if random_string is None:
print( print(
"We couldn't find a secure pseudo-random number generator on your system. " "We couldn't find a secure pseudo-random number generator on your "
"Please, make sure to manually {} later.".format(flag) "system. Please, make sure to manually {} later.".format(flag)
) )
random_string = flag random_string = flag
if formatted is not None: if formatted is not None:
@ -249,10 +252,10 @@ def set_celery_flower_password(file_path, value=None):
return celery_flower_password return celery_flower_password
def append_to_gitignore_file(s): def append_to_gitignore_file(ignored_line):
with open(".gitignore", "a") as gitignore_file: with open(".gitignore", "a") as gitignore_file:
gitignore_file.write(s) gitignore_file.write(ignored_line)
gitignore_file.write(os.linesep) gitignore_file.write("\n")
def set_flags_in_envs(postgres_user, celery_flower_user, debug=False): def set_flags_in_envs(postgres_user, celery_flower_user, debug=False):
@ -291,6 +294,7 @@ def set_flags_in_settings_files():
def remove_envs_and_associated_files(): def remove_envs_and_associated_files():
shutil.rmtree(".envs") shutil.rmtree(".envs")
os.remove("merge_production_dotenvs_in_dotenv.py") os.remove("merge_production_dotenvs_in_dotenv.py")
shutil.rmtree("tests")
def remove_celery_compose_dirs(): def remove_celery_compose_dirs():
@ -319,6 +323,11 @@ def remove_drf_starter_files():
"{{cookiecutter.project_slug}}", "users", "tests", "test_drf_views.py" "{{cookiecutter.project_slug}}", "users", "tests", "test_drf_views.py"
) )
) )
os.remove(
os.path.join(
"{{cookiecutter.project_slug}}", "users", "tests", "test_swagger.py"
)
)
shutil.rmtree(os.path.join("startapp_template", "api")) shutil.rmtree(os.path.join("startapp_template", "api"))
os.remove(os.path.join("startapp_template", "tests", "test_drf_urls.py-tpl")) os.remove(os.path.join("startapp_template", "tests", "test_drf_urls.py-tpl"))
os.remove(os.path.join("startapp_template", "tests", "test_drf_views.py-tpl")) os.remove(os.path.join("startapp_template", "tests", "test_drf_views.py-tpl"))
@ -353,13 +362,13 @@ def main():
if ( if (
"{{ cookiecutter.use_docker }}".lower() == "y" "{{ cookiecutter.use_docker }}".lower() == "y"
and "{{ cookiecutter.cloud_provider}}".lower() != "aws" and "{{ cookiecutter.cloud_provider}}" != "AWS"
): ):
remove_aws_dockerfile() remove_aws_dockerfile()
if "{{ cookiecutter.use_heroku }}".lower() == "n": if "{{ cookiecutter.use_heroku }}".lower() == "n":
remove_heroku_files() remove_heroku_files()
elif "{{ cookiecutter.use_compressor }}".lower() == "n": elif "{{ cookiecutter.frontend_pipeline }}" != "Django Compressor":
remove_heroku_build_hooks() remove_heroku_build_hooks()
if ( if (
@ -379,13 +388,13 @@ def main():
if "{{ cookiecutter.keep_local_envs_in_vcs }}".lower() == "y": if "{{ cookiecutter.keep_local_envs_in_vcs }}".lower() == "y":
append_to_gitignore_file("!.envs/.local/") append_to_gitignore_file("!.envs/.local/")
if "{{ cookiecutter.js_task_runner}}".lower() == "none": if "{{ cookiecutter.frontend_pipeline }}" != "Gulp":
remove_gulp_files() remove_gulp_files()
remove_packagejson_file() remove_packagejson_file()
if "{{ cookiecutter.use_docker }}".lower() == "y": if "{{ cookiecutter.use_docker }}".lower() == "y":
remove_node_dockerfile() remove_node_dockerfile()
if "{{ cookiecutter.cloud_provider}}".lower() == "none": if "{{ cookiecutter.cloud_provider}}" == "None":
print( print(
WARNING + "You chose not to use a cloud provider, " WARNING + "You chose not to use a cloud provider, "
"media files won't be served in production." + TERMINATOR "media files won't be served in production." + TERMINATOR
@ -397,13 +406,13 @@ def main():
if "{{ cookiecutter.use_docker }}".lower() == "y": if "{{ cookiecutter.use_docker }}".lower() == "y":
remove_celery_compose_dirs() remove_celery_compose_dirs()
if "{{ cookiecutter.ci_tool }}".lower() != "travis": if "{{ cookiecutter.ci_tool }}" != "Travis":
remove_dottravisyml_file() remove_dottravisyml_file()
if "{{ cookiecutter.ci_tool }}".lower() != "gitlab": if "{{ cookiecutter.ci_tool }}" != "Gitlab":
remove_dotgitlabciyml_file() remove_dotgitlabciyml_file()
if "{{ cookiecutter.ci_tool }}".lower() != "github": if "{{ cookiecutter.ci_tool }}" != "Github":
remove_dotgithub_folder() remove_dotgithub_folder()
if "{{ cookiecutter.use_drf }}".lower() == "n": if "{{ cookiecutter.use_drf }}".lower() == "n":

View File

@ -4,7 +4,8 @@ NOTE:
as the whole Cookiecutter Django project initialization as the whole Cookiecutter Django project initialization
can potentially be run in Python 2.x environment. can potentially be run in Python 2.x environment.
TODO: ? restrict Cookiecutter Django project initialization to Python 3.x environments only TODO: restrict Cookiecutter Django project initialization
to Python 3.x environments only
""" """
from __future__ import print_function from __future__ import print_function
@ -35,11 +36,11 @@ if "{{ cookiecutter.use_docker }}".lower() == "n":
if python_major_version == 2: if python_major_version == 2:
print( print(
WARNING + "You're running cookiecutter under Python 2, but the generated " WARNING + "You're running cookiecutter under Python 2, but the generated "
"project requires Python 3.9+. Do you want to proceed (y/n)? " + TERMINATOR "project requires Python 3.10+. Do you want to proceed (y/n)? " + TERMINATOR
) )
yes_options, no_options = frozenset(["y"]), frozenset(["n"]) yes_options, no_options = frozenset(["y"]), frozenset(["n"])
while True: while True:
choice = raw_input().lower() choice = raw_input().lower() # noqa: F821
if choice in yes_options: if choice in yes_options:
break break
@ -65,18 +66,17 @@ if (
and "{{ cookiecutter.cloud_provider }}" == "None" and "{{ cookiecutter.cloud_provider }}" == "None"
): ):
print( print(
"You should either use Whitenoise or select a Cloud Provider to serve static files" "You should either use Whitenoise or select a "
"Cloud Provider to serve static files"
) )
sys.exit(1) sys.exit(1)
if ( if (
"{{ cookiecutter.cloud_provider }}" == "GCP" "{{ cookiecutter.mail_service }}" == "Amazon SES"
and "{{ cookiecutter.mail_service }}" == "Amazon SES" and "{{ cookiecutter.cloud_provider }}" != "AWS"
) or (
"{{ cookiecutter.cloud_provider }}" == "None"
and "{{ cookiecutter.mail_service }}" == "Amazon SES"
): ):
print( print(
"You should either use AWS or select a different Mail Service for sending emails." "You should either use AWS or select a different "
"Mail Service for sending emails."
) )
sys.exit(1) sys.exit(1)

View File

@ -1,4 +1,3 @@
[pytest] [pytest]
addopts = -v --tb=short addopts = -v --tb=short
python_paths = .
norecursedirs = .tox .git */migrations/* */static/* docs venv */{{cookiecutter.project_slug}}/* norecursedirs = .tox .git */migrations/* */static/* docs venv */{{cookiecutter.project_slug}}/*

View File

@ -1,24 +1,26 @@
cookiecutter==1.7.3 cookiecutter==2.1.1
sh==1.14.2 sh==1.14.3; sys_platform != "win32"
binaryornot==0.4.4 binaryornot==0.4.4
# Code quality # Code quality
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
black==21.9b0 black==22.12.0
isort==5.9.3 isort==5.12.0
flake8==3.9.2 flake8==6.0.0
flake8-isort==4.0.0 flake8-isort==6.0.0
pre-commit==2.15.0 pre-commit==3.0.1
# Testing # Testing
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
tox==3.24.4 tox==4.4.2
pytest==6.2.5 pytest==7.2.1
pytest-cookies==0.6.1 pytest-cookies==0.6.1
pytest-instafail==0.4.2 pytest-instafail==0.4.2
pyyaml==5.4.1 pyyaml==6.0
# Scripting # Scripting
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
PyGithub==1.55 PyGithub==1.57
jinja2==3.0.2 gitpython==3.1.30
jinja2==3.1.2
requests==2.28.2

View File

@ -1 +0,0 @@

View File

@ -0,0 +1,320 @@
"""
Creates an issue that generates a table for dependency checking whether
all packages support the latest Django version. "Latest" does not include
patches, only comparing major and minor version numbers.
This script handles when there are multiple Django versions that need
to keep up to date.
"""
from __future__ import annotations
import os
import re
import sys
from collections.abc import Iterable
from pathlib import Path
from typing import TYPE_CHECKING, Any, NamedTuple
import requests
from github import Github
if TYPE_CHECKING:
from github.Issue import Issue
CURRENT_FILE = Path(__file__)
ROOT = CURRENT_FILE.parents[1]
REQUIREMENTS_DIR = ROOT / "{{cookiecutter.project_slug}}" / "requirements"
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN", None)
GITHUB_REPO = os.getenv("GITHUB_REPOSITORY", None)
class DjVersion(NamedTuple):
"""
Wrapper to parse, compare and render Django versions.
Only keeps track on (major, minor) versions, excluding patches and pre-releases.
"""
major: int
minor: int
def __str__(self) -> str:
"""To render as string."""
return f"{self.major}.{self.minor}"
@classmethod
def parse(cls, version_str: str) -> DjVersion:
"""Parse interesting values from the version string."""
major, minor, *_ = version_str.split(".")
return cls(major=int(major), minor=int(minor))
@classmethod
def parse_to_tuple(cls, version_str: str):
version = cls.parse(version_str=version_str)
return version.major, version.minor
def get_package_info(package: str) -> dict:
"""Get package metadata using PyPI API."""
# "django" converts to "Django" on redirect
r = requests.get(f"https://pypi.org/pypi/{package}/json", allow_redirects=True)
if not r.ok:
print(f"Couldn't find package: {package}")
sys.exit(1)
return r.json()
def get_django_versions() -> Iterable[DjVersion]:
"""List all django versions."""
django_package_info: dict[str, Any] = get_package_info("django")
releases = django_package_info["releases"].keys()
for release_str in releases:
if release_str.replace(".", "").isdigit():
# Exclude pre-releases with non-numeric characters in version
yield DjVersion.parse(release_str)
def get_name_and_version(requirements_line: str) -> tuple[str, ...]:
"""Get the name a version of a package from a line in the requirement file."""
full_name, version = requirements_line.split(" ", 1)[0].split("==")
name_without_extras = full_name.split("[", 1)[0]
return name_without_extras, version
def get_all_latest_django_versions(
django_max_version: tuple[DjVersion] = None,
) -> tuple[DjVersion, list[DjVersion]]:
"""
Grabs all Django versions that are worthy of a GitHub issue.
Depends on Django versions having higher major version or minor version.
"""
_django_max_version = (99, 99)
if django_max_version:
_django_max_version = django_max_version
print("Fetching all Django versions from PyPI")
base_txt = REQUIREMENTS_DIR / "base.txt"
with base_txt.open() as f:
for line in f.readlines():
if "django==" in line.lower():
break
else:
print(f"django not found in {base_txt}") # Huh...?
sys.exit(1)
# Begin parsing and verification
_, current_version_str = get_name_and_version(line)
# Get a tuple of (major, minor) - ignoring patch version
current_minor_version = DjVersion.parse(current_version_str)
newer_versions: set[DjVersion] = set()
for django_version in get_django_versions():
if current_minor_version < django_version <= _django_max_version:
newer_versions.add(django_version)
return current_minor_version, sorted(newer_versions, reverse=True)
_TABLE_HEADER = """
## {file}.txt
| Name | Version in Master | {dj_version} Compatible Version | OK |
| ---- | :---------------: | :-----------------------------: | :-: |
"""
VITAL_BUT_UNKNOWN = [
"django-environ", # not updated often
]
class GitHubManager:
def __init__(self, base_dj_version: DjVersion, needed_dj_versions: list[DjVersion]):
self.github = Github(GITHUB_TOKEN)
self.repo = self.github.get_repo(GITHUB_REPO)
self.base_dj_version = base_dj_version
self.needed_dj_versions = needed_dj_versions
# (major+minor) Version and description
self.existing_issues: dict[DjVersion, Issue] = {}
# Load all requirements from our requirements files and preload their
# package information like a cache:
self.requirements_files = ["base", "local", "production"]
# Format:
# requirement file name: {package name: (master_version, package_info)}
self.requirements: dict[str, dict[str, tuple[str, dict]]] = {
x: {} for x in self.requirements_files
}
def setup(self) -> None:
self.load_requirements()
self.load_existing_issues()
def load_requirements(self):
print("Reading requirements")
for requirements_file in self.requirements_files:
with (REQUIREMENTS_DIR / f"{requirements_file}.txt").open() as f:
for line in f.readlines():
if (
"==" in line
and not line.startswith("{%")
and not line.startswith(" #")
and not line.startswith("#")
and not line.startswith(" ")
):
name, version = get_name_and_version(line)
self.requirements[requirements_file][name] = (
version,
get_package_info(name),
)
def load_existing_issues(self):
"""Closes the issue if the base Django version is greater than needed"""
print("Load existing issues from GitHub")
qualifiers = {
"repo": GITHUB_REPO,
"author": "app/github-actions",
"state": "open",
"is": "issue",
"in": "title",
}
issues = list(
self.github.search_issues(
"[Django Update]", "created", "desc", **qualifiers
)
)
print(f"Found {len(issues)} issues matching search")
for issue in issues:
matches = re.match(r"\[Update Django] Django (\d+.\d+)$", issue.title)
if not matches:
continue
issue_version = DjVersion.parse(matches.group(1))
if self.base_dj_version > issue_version:
issue.edit(state="closed")
print(f"Closed issue {issue.title} (ID: [{issue.id}]({issue.url}))")
else:
self.existing_issues[issue_version] = issue
def get_compatibility(
self, package_name: str, package_info: dict, needed_dj_version: DjVersion
):
"""
Verify compatibility via setup.py classifiers. If Django is not in the
classifiers, then default compatibility is n/a and OK is .
If it's a package that's vital but known to not be updated often, we give it
a . If a package has or 🕒, then we allow manual update. Automatic updates
only include and .
"""
# If issue previously existed, find package and skip any gtg, manually
# updated packages, or known releases that will happen but haven't yet
if issue := self.existing_issues.get(needed_dj_version):
if index := issue.body.find(package_name):
name, _current, prev_compat, ok = (
s.strip() for s in issue.body[index:].split("|", 4)[:4]
)
if ok in ("", "", "🕒"):
return prev_compat, ok
if package_name in VITAL_BUT_UNKNOWN:
return "", ""
# Check classifiers if it includes Django
supported_dj_versions: list[DjVersion] = []
for classifier in package_info["info"]["classifiers"]:
# Usually in the form of "Framework :: Django :: 3.2"
tokens = classifier.split(" ")
if len(tokens) >= 5 and tokens[2].lower() == "django":
version = DjVersion.parse(tokens[4])
if len(version) == 2:
supported_dj_versions.append(version)
if supported_dj_versions:
if any(v >= needed_dj_version for v in supported_dj_versions):
return package_info["info"]["version"], ""
else:
return "", ""
# Django classifier DNE; assume it isn't a Django lib
# Great exceptions include pylint-django, where we need to do this manually...
return "n/a", ""
HOME_PAGE_URL_KEYS = [
"home_page",
"project_url",
"docs_url",
"package_url",
"release_url",
"bugtrack_url",
]
def _get_md_home_page_url(self, package_info: dict):
urls = [
package_info["info"].get(url_key) for url_key in self.HOME_PAGE_URL_KEYS
]
try:
return f"[{{}}]({next(item for item in urls if item)})"
except StopIteration:
return "{}"
def generate_markdown(self, needed_dj_version: DjVersion):
requirements = f"{needed_dj_version} requirements tables\n\n"
for _file in self.requirements_files:
requirements += _TABLE_HEADER.format_map(
{"file": _file, "dj_version": needed_dj_version}
)
for package_name, (version, info) in self.requirements[_file].items():
compat_version, icon = self.get_compatibility(
package_name, info, needed_dj_version
)
requirements += (
f"| {self._get_md_home_page_url(info).format(package_name)} "
f"| {version.strip()} "
f"| {compat_version.strip()} "
f"| {icon} "
f"|\n"
)
return requirements
def create_or_edit_issue(self, needed_dj_version: DjVersion, description: str):
if issue := self.existing_issues.get(needed_dj_version):
print(f"Editing issue #{issue.number} for Django {needed_dj_version}")
issue.edit(body=description)
else:
print(f"Creating new issue for Django {needed_dj_version}")
issue = self.repo.create_issue(
f"[Update Django] Django {needed_dj_version}", description
)
issue.add_to_labels(f"django{needed_dj_version}")
def generate(self):
for version in self.needed_dj_versions:
print(f"Handling GitHub issue for Django {version}")
md_content = self.generate_markdown(version)
print(f"Generated markdown:\n\n{md_content}")
self.create_or_edit_issue(version, md_content)
def main(django_max_version=None) -> None:
# Check if there are any djs
current_dj, latest_djs = get_all_latest_django_versions(
django_max_version=django_max_version
)
if not latest_djs:
sys.exit(0)
manager = GitHubManager(current_dj, latest_djs)
manager.setup()
manager.generate()
if __name__ == "__main__":
if GITHUB_REPO is None:
raise RuntimeError(
"No github repo, please set the environment variable GITHUB_REPOSITORY"
)
max_version = None
last_arg = sys.argv[-1]
if CURRENT_FILE.name not in last_arg:
max_version = DjVersion.parse_to_tuple(version_str=last_arg)
main(django_max_version=max_version)

View File

@ -1,22 +1,31 @@
import datetime as dt
import os import os
import re
from collections.abc import Iterable
from pathlib import Path from pathlib import Path
import git
import github.PullRequest
import github.Repository
from github import Github from github import Github
from jinja2 import Template from jinja2 import Template
import datetime as dt
CURRENT_FILE = Path(__file__) CURRENT_FILE = Path(__file__)
ROOT = CURRENT_FILE.parents[1] ROOT = CURRENT_FILE.parents[1]
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN", None) GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
GITHUB_REPO = os.getenv("GITHUB_REPOSITORY")
# Generate changelog for PRs merged yesterday GIT_BRANCH = os.getenv("GITHUB_REF_NAME")
MERGED_DATE = dt.date.today() - dt.timedelta(days=1)
def main() -> None: def main() -> None:
""" """
Script entry point. Script entry point.
""" """
merged_pulls = list(iter_pulls()) # Generate changelog for PRs merged yesterday
merged_date = dt.date.today() - dt.timedelta(days=1)
repo = Github(login_or_token=GITHUB_TOKEN).get_repo(GITHUB_REPO)
merged_pulls = list(iter_pulls(repo, merged_date))
print(f"Merged pull requests: {merged_pulls}")
if not merged_pulls: if not merged_pulls:
print("Nothing was merged, existing.") print("Nothing was merged, existing.")
return return
@ -25,30 +34,50 @@ def main() -> None:
grouped_pulls = group_pulls_by_change_type(merged_pulls) grouped_pulls = group_pulls_by_change_type(merged_pulls)
# Generate portion of markdown # Generate portion of markdown
rendered_content = generate_md(grouped_pulls) release_changes_summary = generate_md(grouped_pulls)
print(f"Summary of changes: {release_changes_summary}")
# Update CHANGELOG.md file # Update CHANGELOG.md file
file_path = ROOT / "CHANGELOG.md" release = f"{merged_date:%Y.%m.%d}"
old_content = file_path.read_text() changelog_path = ROOT / "CHANGELOG.md"
updated_content = old_content.replace( write_changelog(changelog_path, release, release_changes_summary)
"<!-- GENERATOR_PLACEHOLDER -->", print(f"Wrote {changelog_path}")
f"<!-- GENERATOR_PLACEHOLDER -->\n\n{rendered_content}",
# Update version
setup_py_path = ROOT / "setup.py"
update_version(setup_py_path, release)
print(f"Updated version in {setup_py_path}")
# Commit changes, create tag and push
update_git_repo([changelog_path, setup_py_path], release)
# Create GitHub release
github_release = repo.create_git_release(
tag=release,
name=release,
message=release_changes_summary,
) )
file_path.write_text(updated_content) print(f"Created release on GitHub {github_release}")
def iter_pulls(): def iter_pulls(
repo: github.Repository.Repository,
merged_date: dt.date,
) -> Iterable[github.PullRequest.PullRequest]:
"""Fetch merged pull requests at the date we're interested in.""" """Fetch merged pull requests at the date we're interested in."""
repo = Github(login_or_token=GITHUB_TOKEN).get_repo("pydanny/cookiecutter-django")
recent_pulls = repo.get_pulls( recent_pulls = repo.get_pulls(
state="closed", sort="updated", direction="desc" state="closed",
sort="updated",
direction="desc",
).get_page(0) ).get_page(0)
for pull in recent_pulls: for pull in recent_pulls:
if pull.merged and pull.merged_at.date() == MERGED_DATE: if pull.merged and pull.merged_at.date() == merged_date:
yield pull yield pull
def group_pulls_by_change_type(pull_requests_list): def group_pulls_by_change_type(
pull_requests_list: list[github.PullRequest.PullRequest],
) -> dict[str, list[github.PullRequest.PullRequest]]:
"""Group pull request by change type.""" """Group pull request by change type."""
grouped_pulls = { grouped_pulls = {
"Changed": [], "Changed": [],
@ -56,7 +85,7 @@ def group_pulls_by_change_type(pull_requests_list):
"Updated": [], "Updated": [],
} }
for pull in pull_requests_list: for pull in pull_requests_list:
label_names = {l.name for l in pull.labels} label_names = {label.name for label in pull.labels}
if "update" in label_names: if "update" in label_names:
group_name = "Updated" group_name = "Updated"
elif "bug" in label_names: elif "bug" in label_names:
@ -67,12 +96,63 @@ def group_pulls_by_change_type(pull_requests_list):
return grouped_pulls return grouped_pulls
def generate_md(grouped_pulls): def generate_md(grouped_pulls: dict[str, list[github.PullRequest.PullRequest]]) -> str:
"""Generate markdown file from Jinja template.""" """Generate markdown file from Jinja template."""
changelog_template = ROOT / ".github" / "changelog-template.md" changelog_template = ROOT / ".github" / "changelog-template.md"
template = Template(changelog_template.read_text(), autoescape=True) template = Template(changelog_template.read_text(), autoescape=True)
return template.render(merge_date=MERGED_DATE, grouped_pulls=grouped_pulls) return template.render(grouped_pulls=grouped_pulls)
def write_changelog(file_path: Path, release: str, content: str) -> None:
"""Write Release details to the changelog file."""
content = f"## {release}\n{content}"
old_content = file_path.read_text()
updated_content = old_content.replace(
"<!-- GENERATOR_PLACEHOLDER -->",
f"<!-- GENERATOR_PLACEHOLDER -->\n\n{content}",
)
file_path.write_text(updated_content)
def update_version(file_path: Path, release: str) -> None:
"""Update template version in setup.py."""
old_content = file_path.read_text()
updated_content = re.sub(
r'\nversion = "\d+\.\d+\.\d+"\n',
f'\nversion = "{release}"\n',
old_content,
)
file_path.write_text(updated_content)
def update_git_repo(paths: list[Path], release: str) -> None:
"""Commit, tag changes in git repo and push to origin."""
repo = git.Repo(ROOT)
for path in paths:
repo.git.add(path)
message = f"Release {release}"
user = repo.git.config("--get", "user.name")
email = repo.git.config("--get", "user.email")
repo.git.commit(
m=message,
author=f"{user} <{email}>",
)
repo.git.tag("-a", release, m=message)
server = f"https://{GITHUB_TOKEN}@github.com/{GITHUB_REPO}.git"
print(f"Pushing changes to {GIT_BRANCH} branch of {GITHUB_REPO}")
repo.git.push(server, GIT_BRANCH)
repo.git.push("--tags", server, GIT_BRANCH)
if __name__ == "__main__": if __name__ == "__main__":
if GITHUB_REPO is None:
raise RuntimeError(
"No github repo, please set the environment variable GITHUB_REPOSITORY"
)
if GIT_BRANCH is None:
raise RuntimeError(
"No git branch set, please set the GITHUB_REF_NAME environment variable"
)
main() main()

View File

@ -1,5 +1,7 @@
import json import json
import os
from pathlib import Path from pathlib import Path
from github import Github from github import Github
from github.NamedUser import NamedUser from github.NamedUser import NamedUser
from jinja2 import Template from jinja2 import Template
@ -7,6 +9,8 @@ from jinja2 import Template
CURRENT_FILE = Path(__file__) CURRENT_FILE = Path(__file__)
ROOT = CURRENT_FILE.parents[1] ROOT = CURRENT_FILE.parents[1]
BOT_LOGINS = ["pyup-bot"] BOT_LOGINS = ["pyup-bot"]
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN", None)
GITHUB_REPO = os.getenv("GITHUB_REPOSITORY", None)
def main() -> None: def main() -> None:
@ -39,7 +43,7 @@ def iter_recent_authors():
Use Github API to fetch recent authors rather than Use Github API to fetch recent authors rather than
git CLI to work with Github usernames. git CLI to work with Github usernames.
""" """
repo = Github(per_page=5).get_repo("pydanny/cookiecutter-django") repo = Github(login_or_token=GITHUB_TOKEN, per_page=5).get_repo(GITHUB_REPO)
recent_pulls = repo.get_pulls( recent_pulls = repo.get_pulls(
state="closed", sort="updated", direction="desc" state="closed", sort="updated", direction="desc"
).get_page(0) ).get_page(0)
@ -101,4 +105,8 @@ def write_md_file(contributors):
if __name__ == "__main__": if __name__ == "__main__":
if GITHUB_REPO is None:
raise RuntimeError(
"No github repo, please set the environment variable GITHUB_REPOSITORY"
)
main() main()

7
setup.cfg Normal file
View File

@ -0,0 +1,7 @@
[flake8]
exclude = docs
max-line-length = 88
[isort]
profile = black
known_first_party = tests,scripts,hooks

View File

@ -1,21 +1,11 @@
#!/usr/bin/env python #!/usr/bin/env python
import os
import sys
try: try:
from setuptools import setup from setuptools import setup
except ImportError: except ImportError:
from distutils.core import setup from distutils.core import setup
# Our version ALWAYS matches the version of Django we support # We use calendar versioning
# If Django has a new release, we branch, tag, then update this setting after the tag. version = "2023.01.27"
version = "3.1.13"
if sys.argv[-1] == "tag":
os.system(f'git tag -a {version} -m "version {version}"')
os.system("git push --tags")
sys.exit()
with open("README.rst") as readme_file: with open("README.rst") as readme_file:
long_description = readme_file.read() long_description = readme_file.read()
@ -23,24 +13,27 @@ with open("README.rst") as readme_file:
setup( setup(
name="cookiecutter-django", name="cookiecutter-django",
version=version, version=version,
description="A Cookiecutter template for creating production-ready Django projects quickly.", description=(
"A Cookiecutter template for creating production-ready "
"Django projects quickly."
),
long_description=long_description, long_description=long_description,
author="Daniel Roy Greenfeld", author="Daniel Roy Greenfeld",
author_email="pydanny@gmail.com", author_email="pydanny@gmail.com",
url="https://github.com/pydanny/cookiecutter-django", url="https://github.com/cookiecutter/cookiecutter-django",
packages=[], packages=[],
license="BSD", license="BSD",
zip_safe=False, zip_safe=False,
classifiers=[ classifiers=[
"Development Status :: 4 - Beta", "Development Status :: 4 - Beta",
"Environment :: Console", "Environment :: Console",
"Framework :: Django :: 3.0", "Framework :: Django :: 4.0",
"Intended Audience :: Developers", "Intended Audience :: Developers",
"Natural Language :: English", "Natural Language :: English",
"License :: OSI Approved :: BSD License", "License :: OSI Approved :: BSD License",
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: CPython",
"Topic :: Software Development", "Topic :: Software Development",
], ],

0
tests/__init__.py Normal file
View File

View File

@ -6,19 +6,12 @@
set -o errexit set -o errexit
set -x set -x
# Install modern pip with new resolver:
# https://blog.python.org/2020/11/pip-20-3-release-new-resolver.html
pip install 'pip>=20.3'
# install test requirements
pip install -r requirements.txt
# create a cache directory # create a cache directory
mkdir -p .cache/bare mkdir -p .cache/bare
cd .cache/bare cd .cache/bare
# create the project using the default settings in cookiecutter.json # create the project using the default settings in cookiecutter.json
cookiecutter ../../ --no-input --overwrite-if-exists use_docker=n $@ cookiecutter ../../ --no-input --overwrite-if-exists use_docker=n "$@"
cd my_awesome_project cd my_awesome_project
# Install OS deps # Install OS deps
@ -35,3 +28,18 @@ pre-commit run --show-diff-on-failure -a
# run the project's tests # run the project's tests
pytest pytest
# Make sure the check doesn't raise any warnings
python manage.py check --fail-level WARNING
if [ -f "package.json" ]
then
npm install
if [ -f "gulpfile.js" ]
then
npm run build
fi
fi
# Generate the HTML for the documentation
cd docs && make html

View File

@ -1,15 +1,25 @@
import os import os
import re import re
import sys
import pytest import pytest
from cookiecutter.exceptions import FailedHookException
import sh try:
import sh
except (ImportError, ModuleNotFoundError):
sh = None # sh doesn't support Windows
import yaml import yaml
from binaryornot.check import is_binary from binaryornot.check import is_binary
from cookiecutter.exceptions import FailedHookException
PATTERN = r"{{(\s?cookiecutter)[.](.*?)}}" PATTERN = r"{{(\s?cookiecutter)[.](.*?)}}"
RE_OBJ = re.compile(PATTERN) RE_OBJ = re.compile(PATTERN)
if sys.platform.startswith("win"):
pytest.skip("sh doesn't support windows", allow_module_level=True)
elif sys.platform.startswith("darwin") and os.getenv("CI"):
pytest.skip("skipping slow macOS tests on CI", allow_module_level=True)
@pytest.fixture @pytest.fixture
def context(): def context():
@ -37,14 +47,17 @@ SUPPORTED_COMBINATIONS = [
{"use_pycharm": "n"}, {"use_pycharm": "n"},
{"use_docker": "y"}, {"use_docker": "y"},
{"use_docker": "n"}, {"use_docker": "n"},
{"postgresql_version": "13.2"}, {"postgresql_version": "14"},
{"postgresql_version": "12.6"}, {"postgresql_version": "13"},
{"postgresql_version": "11.11"}, {"postgresql_version": "12"},
{"postgresql_version": "10.16"}, {"postgresql_version": "11"},
{"postgresql_version": "10"},
{"cloud_provider": "AWS", "use_whitenoise": "y"}, {"cloud_provider": "AWS", "use_whitenoise": "y"},
{"cloud_provider": "AWS", "use_whitenoise": "n"}, {"cloud_provider": "AWS", "use_whitenoise": "n"},
{"cloud_provider": "GCP", "use_whitenoise": "y"}, {"cloud_provider": "GCP", "use_whitenoise": "y"},
{"cloud_provider": "GCP", "use_whitenoise": "n"}, {"cloud_provider": "GCP", "use_whitenoise": "n"},
{"cloud_provider": "Azure", "use_whitenoise": "y"},
{"cloud_provider": "Azure", "use_whitenoise": "n"},
{"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Mailgun"}, {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Mailgun"},
{"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Mailjet"}, {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Mailjet"},
{"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Mandrill"}, {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Mandrill"},
@ -71,17 +84,23 @@ SUPPORTED_COMBINATIONS = [
{"cloud_provider": "GCP", "mail_service": "SendinBlue"}, {"cloud_provider": "GCP", "mail_service": "SendinBlue"},
{"cloud_provider": "GCP", "mail_service": "SparkPost"}, {"cloud_provider": "GCP", "mail_service": "SparkPost"},
{"cloud_provider": "GCP", "mail_service": "Other SMTP"}, {"cloud_provider": "GCP", "mail_service": "Other SMTP"},
# Note: cloud_providers GCP and None with mail_service Amazon SES is not supported {"cloud_provider": "Azure", "mail_service": "Mailgun"},
{"cloud_provider": "Azure", "mail_service": "Mailjet"},
{"cloud_provider": "Azure", "mail_service": "Mandrill"},
{"cloud_provider": "Azure", "mail_service": "Postmark"},
{"cloud_provider": "Azure", "mail_service": "Sendgrid"},
{"cloud_provider": "Azure", "mail_service": "SendinBlue"},
{"cloud_provider": "Azure", "mail_service": "SparkPost"},
{"cloud_provider": "Azure", "mail_service": "Other SMTP"},
# Note: cloud_providers GCP, Azure, and None
# with mail_service Amazon SES is not supported
{"use_async": "y"}, {"use_async": "y"},
{"use_async": "n"}, {"use_async": "n"},
{"use_drf": "y"}, {"use_drf": "y"},
{"use_drf": "n"}, {"use_drf": "n"},
{"js_task_runner": "None"}, {"frontend_pipeline": "None"},
{"js_task_runner": "Gulp"}, {"frontend_pipeline": "Django Compressor"},
{"custom_bootstrap_compilation": "y"}, {"frontend_pipeline": "Gulp"},
{"custom_bootstrap_compilation": "n"},
{"use_compressor": "y"},
{"use_compressor": "n"},
{"use_celery": "y"}, {"use_celery": "y"},
{"use_celery": "n"}, {"use_celery": "n"},
{"use_mailhog": "y"}, {"use_mailhog": "y"},
@ -105,20 +124,21 @@ SUPPORTED_COMBINATIONS = [
UNSUPPORTED_COMBINATIONS = [ UNSUPPORTED_COMBINATIONS = [
{"cloud_provider": "None", "use_whitenoise": "n"}, {"cloud_provider": "None", "use_whitenoise": "n"},
{"cloud_provider": "GCP", "mail_service": "Amazon SES"}, {"cloud_provider": "GCP", "mail_service": "Amazon SES"},
{"cloud_provider": "Azure", "mail_service": "Amazon SES"},
{"cloud_provider": "None", "mail_service": "Amazon SES"}, {"cloud_provider": "None", "mail_service": "Amazon SES"},
] ]
def _fixture_id(ctx): def _fixture_id(ctx):
"""Helper to get a user friendly test name from the parametrized context.""" """Helper to get a user-friendly test name from the parametrized context."""
return "-".join(f"{key}:{value}" for key, value in ctx.items()) return "-".join(f"{key}:{value}" for key, value in ctx.items())
def build_files_list(root_dir): def build_files_list(base_dir):
"""Build a list containing absolute paths to the generated files.""" """Build a list containing absolute paths to the generated files."""
return [ return [
os.path.join(dirpath, file_path) os.path.join(dirpath, file_path)
for dirpath, subdirs, files in os.walk(root_dir) for dirpath, subdirs, files in os.walk(base_dir)
for file_path in files for file_path in files
] ]
@ -130,7 +150,7 @@ def check_paths(paths):
if is_binary(path): if is_binary(path):
continue continue
for line in open(path, "r"): for line in open(path):
match = RE_OBJ.search(line) match = RE_OBJ.search(line)
assert match is None, f"cookiecutter variable not replaced in {path}" assert match is None, f"cookiecutter variable not replaced in {path}"
@ -142,10 +162,10 @@ def test_project_generation(cookies, context, context_override):
result = cookies.bake(extra_context={**context, **context_override}) result = cookies.bake(extra_context={**context, **context_override})
assert result.exit_code == 0 assert result.exit_code == 0
assert result.exception is None assert result.exception is None
assert result.project.basename == context["project_slug"] assert result.project_path.name == context["project_slug"]
assert result.project.isdir() assert result.project_path.is_dir()
paths = build_files_list(str(result.project)) paths = build_files_list(str(result.project_path))
assert paths assert paths
check_paths(paths) check_paths(paths)
@ -156,7 +176,7 @@ def test_flake8_passes(cookies, context_override):
result = cookies.bake(extra_context=context_override) result = cookies.bake(extra_context=context_override)
try: try:
sh.flake8(_cwd=str(result.project)) sh.flake8(_cwd=str(result.project_path))
except sh.ErrorReturnCode as e: except sh.ErrorReturnCode as e:
pytest.fail(e.stdout.decode()) pytest.fail(e.stdout.decode())
@ -168,7 +188,12 @@ def test_black_passes(cookies, context_override):
try: try:
sh.black( sh.black(
"--check", "--diff", "--exclude", "migrations", _cwd=str(result.project) "--check",
"--diff",
"--exclude",
"migrations",
".",
_cwd=str(result.project_path),
) )
except sh.ErrorReturnCode as e: except sh.ErrorReturnCode as e:
pytest.fail(e.stdout.decode()) pytest.fail(e.stdout.decode())
@ -187,10 +212,10 @@ def test_travis_invokes_pytest(cookies, context, use_docker, expected_test_scrip
assert result.exit_code == 0 assert result.exit_code == 0
assert result.exception is None assert result.exception is None
assert result.project.basename == context["project_slug"] assert result.project_path.name == context["project_slug"]
assert result.project.isdir() assert result.project_path.is_dir()
with open(f"{result.project}/.travis.yml", "r") as travis_yml: with open(f"{result.project_path}/.travis.yml") as travis_yml:
try: try:
yml = yaml.safe_load(travis_yml)["jobs"]["include"] yml = yaml.safe_load(travis_yml)["jobs"]["include"]
assert yml[0]["script"] == ["flake8"] assert yml[0]["script"] == ["flake8"]
@ -214,10 +239,10 @@ def test_gitlab_invokes_flake8_and_pytest(
assert result.exit_code == 0 assert result.exit_code == 0
assert result.exception is None assert result.exception is None
assert result.project.basename == context["project_slug"] assert result.project_path.name == context["project_slug"]
assert result.project.isdir() assert result.project_path.is_dir()
with open(f"{result.project}/.gitlab-ci.yml", "r") as gitlab_yml: with open(f"{result.project_path}/.gitlab-ci.yml") as gitlab_yml:
try: try:
gitlab_config = yaml.safe_load(gitlab_yml) gitlab_config = yaml.safe_load(gitlab_yml)
assert gitlab_config["flake8"]["script"] == ["flake8"] assert gitlab_config["flake8"]["script"] == ["flake8"]
@ -241,10 +266,10 @@ def test_github_invokes_linter_and_pytest(
assert result.exit_code == 0 assert result.exit_code == 0
assert result.exception is None assert result.exception is None
assert result.project.basename == context["project_slug"] assert result.project_path.name == context["project_slug"]
assert result.project.isdir() assert result.project_path.is_dir()
with open(f"{result.project}/.github/workflows/ci.yml", "r") as github_yml: with open(f"{result.project_path}/.github/workflows/ci.yml") as github_yml:
try: try:
github_config = yaml.safe_load(github_yml) github_config = yaml.safe_load(github_yml)
linter_present = False linter_present = False
@ -264,7 +289,7 @@ def test_github_invokes_linter_and_pytest(
@pytest.mark.parametrize("slug", ["project slug", "Project_Slug"]) @pytest.mark.parametrize("slug", ["project slug", "Project_Slug"])
def test_invalid_slug(cookies, context, slug): def test_invalid_slug(cookies, context, slug):
"""Invalid slug should failed pre-generation hook.""" """Invalid slug should fail pre-generation hook."""
context.update({"project_slug": slug}) context.update({"project_slug": slug})
result = cookies.bake(extra_context=context) result = cookies.bake(extra_context=context)
@ -295,6 +320,6 @@ def test_pycharm_docs_removed(cookies, context, use_pycharm, pycharm_docs_exist)
context.update({"use_pycharm": use_pycharm}) context.update({"use_pycharm": use_pycharm})
result = cookies.bake(extra_context=context) result = cookies.bake(extra_context=context)
with open(f"{result.project}/docs/index.rst", "r") as f: with open(f"{result.project_path}/docs/index.rst") as f:
has_pycharm_docs = "pycharm/configuration" in f.read() has_pycharm_docs = "pycharm/configuration" in f.read()
assert has_pycharm_docs is pycharm_docs_exist assert has_pycharm_docs is pycharm_docs_exist

View File

@ -6,15 +6,12 @@
set -o errexit set -o errexit
set -x set -x
# install test requirements
pip install -r requirements.txt
# create a cache directory # create a cache directory
mkdir -p .cache/docker mkdir -p .cache/docker
cd .cache/docker cd .cache/docker
# create the project using the default settings in cookiecutter.json # create the project using the default settings in cookiecutter.json
cookiecutter ../../ --no-input --overwrite-if-exists use_docker=y $@ cookiecutter ../../ --no-input --overwrite-if-exists use_docker=y "$@"
cd my_awesome_project cd my_awesome_project
# Lint by running pre-commit on all files # Lint by running pre-commit on all files
@ -24,6 +21,9 @@ git init
git add . git add .
pre-commit run --show-diff-on-failure -a pre-commit run --show-diff-on-failure -a
# make sure all images build
docker-compose -f local.yml build
# run the project's type checks # run the project's type checks
docker-compose -f local.yml run django mypy my_awesome_project docker-compose -f local.yml run django mypy my_awesome_project
@ -35,3 +35,9 @@ docker-compose -f local.yml run django python manage.py makemigrations --dry-run
# Test support for translations # Test support for translations
docker-compose -f local.yml run django python manage.py makemessages --all docker-compose -f local.yml run django python manage.py makemessages --all
# Make sure the check doesn't raise any warnings
docker-compose -f local.yml run django python manage.py check --fail-level WARNING
# Generate the HTML for the documentation
docker-compose -f local.yml run docs make html

28
tests/test_hooks.py Normal file
View File

@ -0,0 +1,28 @@
"""Unit tests for the hooks"""
import os
from pathlib import Path
import pytest
from hooks.post_gen_project import append_to_gitignore_file
@pytest.fixture()
def working_directory(tmp_path):
prev_cwd = Path.cwd()
os.chdir(tmp_path)
try:
yield tmp_path
finally:
os.chdir(prev_cwd)
def test_append_to_gitignore_file(working_directory):
gitignore_file = working_directory / ".gitignore"
gitignore_file.write_text("node_modules/\n")
append_to_gitignore_file(".envs/*")
linesep = os.linesep.encode()
assert (
gitignore_file.read_bytes() == b"node_modules/" + linesep + b".envs/*" + linesep
)
assert gitignore_file.read_text() == "node_modules/\n.envs/*\n"

View File

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

View File

@ -8,3 +8,4 @@
.readthedocs.yml .readthedocs.yml
.travis.yml .travis.yml
venv venv
.git

View File

@ -12,7 +12,7 @@ trim_trailing_whitespace = true
indent_style = space indent_style = space
indent_size = 4 indent_size = 4
[*.{html,css,scss,json,yml}] [*.{html,css,scss,json,yml,xml}]
indent_style = space indent_style = space
indent_size = 2 indent_size = 2

View File

@ -44,6 +44,12 @@ DJANGO_AWS_STORAGE_BUCKET_NAME=
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
GOOGLE_APPLICATION_CREDENTIALS= GOOGLE_APPLICATION_CREDENTIALS=
DJANGO_GCP_STORAGE_BUCKET_NAME= DJANGO_GCP_STORAGE_BUCKET_NAME=
{% elif cookiecutter.cloud_provider == 'Azure' %}
# Azure
# ------------------------------------------------------------------------------
DJANGO_AZURE_ACCOUNT_KEY=
DJANGO_AZURE_ACCOUNT_NAME=
DJANGO_AZURE_CONTAINER_NAME=
{% endif %} {% endif %}
# django-allauth # django-allauth
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -1,7 +1,95 @@
# Config for Dependabot updates. See Documentation here:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2 version: 2
updates: updates:
# Update Github actions in workflows # Update GitHub actions in workflows
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
directory: "/" directory: "/"
# Check for updates to GitHub Actions every weekday
schedule: schedule:
interval: "daily" interval: "daily"
{%- if cookiecutter.use_docker == 'y' %}
# Enable version updates for Docker
# We need to specify each Dockerfile in a separate entry because Dependabot doesn't
# support wildcards or recursively checking subdirectories. Check this issue for updates:
# https://github.com/dependabot/dependabot-core/issues/2178
- package-ecosystem: "docker"
# Look for a `Dockerfile` in the `compose/local/django` directory
directory: "compose/local/django/"
# Check for updates to GitHub Actions every weekday
schedule:
interval: "daily"
# Enable version updates for 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
schedule:
interval: "daily"
# Enable version updates for 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
schedule:
interval: "daily"
# Enable version updates for 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
schedule:
interval: "daily"
# Enable version updates for 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
schedule:
interval: "daily"
# Enable version updates for 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
schedule:
interval: "daily"
# Enable version updates for 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
schedule:
interval: "daily"
{%- endif %}
# Enable version updates for Python/Pip - Production
- 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
schedule:
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
schedule:
interval: "daily"
{%- endif %}

View File

@ -14,6 +14,9 @@ on:
branches: [ "master", "main" ] branches: [ "master", "main" ]
paths-ignore: [ "docs/**" ] paths-ignore: [ "docs/**" ]
concurrency:
group: {% raw %}${{ github.head_ref || github.run_id }}{% endraw %}
cancel-in-progress: true
jobs: jobs:
linter: linter:
@ -21,18 +24,19 @@ jobs:
steps: steps:
- name: Checkout Code Repository - name: Checkout Code Repository
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Set up Python 3.9 - name: Set up Python
uses: actions/setup-python@v2 uses: actions/setup-python@v3
with: with:
python-version: 3.9 python-version: "3.10"
cache: pip
cache-dependency-path: |
requirements/base.txt
requirements/local.txt
# Run all pre-commit hooks on all the files. - name: Run pre-commit
# Getting only staged files can be tricky in case a new PR is opened uses: pre-commit/action@v2.0.3
# since the action is run on a branch in detached head state
- name: Install and Run Pre-commit
uses: pre-commit/action@v2.0.0
# With no caching at all the entire ci process takes 4m 30s to complete! # With no caching at all the entire ci process takes 4m 30s to complete!
pytest: pytest:
@ -64,7 +68,7 @@ jobs:
steps: steps:
- name: Checkout Code Repository - name: Checkout Code Repository
uses: actions/checkout@v2 uses: actions/checkout@v3
{%- if cookiecutter.use_docker == 'y' %} {%- if cookiecutter.use_docker == 'y' %}
- name: Build the Stack - name: Build the Stack
@ -80,27 +84,14 @@ jobs:
run: docker-compose -f local.yml down run: docker-compose -f local.yml down
{%- else %} {%- else %}
- name: Set up Python 3.9 - name: Set up Python
uses: actions/setup-python@v2 uses: actions/setup-python@v3
with: with:
python-version: 3.9 python-version: "3.10"
cache: pip
- name: Get pip cache dir cache-dependency-path: |
id: pip-cache-location requirements/base.txt
run: | requirements/local.txt
echo "::set-output name=dir::$(pip cache dir)"
{%- raw %}
- name: Cache pip Project Dependencies
uses: actions/cache@v2
with:
# Get the location of pip cache dir
path: ${{ steps.pip-cache-location.outputs.dir }}
# Look to see if there is a cache hit for the corresponding requirements file
key: ${{ runner.os }}-pip-${{ hashFiles('**/local.txt') }}
restore-keys: |
${{ runner.os }}-pip-
{%- endraw %}
- name: Install Dependencies - name: Install Dependencies
run: | run: |

View File

@ -326,14 +326,25 @@ Session.vim
# Auto-generated tag files # Auto-generated tag files
tags tags
# Redis dump file
dump.rdb
### Project template ### Project template
{% if cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'n' %} {%- if cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'n' %}
MailHog MailHog
{%- endif %} {%- endif %}
{{ cookiecutter.project_slug }}/media/ {{ cookiecutter.project_slug }}/media/
.pytest_cache/ .pytest_cache/
{% if cookiecutter.use_docker == 'y' %} {%- if cookiecutter.use_docker == 'y' %}
.ipython/ .ipython/
{%- endif %} {%- endif %}
{%- if cookiecutter.frontend_pipeline == 'Gulp' %}
project.css
project.min.css
vendors.js
*.min.js
*.min.js.map
{%- endif %}

View File

@ -13,7 +13,7 @@ variables:
flake8: flake8:
stage: lint stage: lint
image: python:3.9-alpine image: python:3.10-alpine
before_script: before_script:
- pip install -q flake8 - pip install -q flake8
script: script:
@ -22,7 +22,7 @@ flake8:
pytest: pytest:
stage: test stage: test
{% if cookiecutter.use_docker == 'y' -%} {% if cookiecutter.use_docker == 'y' -%}
image: docker/compose:latest image: docker/compose:1.29.2
tags: tags:
- docker - docker
services: services:
@ -35,7 +35,7 @@ pytest:
script: script:
- docker-compose -f local.yml run django pytest - docker-compose -f local.yml run django pytest
{%- else -%} {%- else -%}
image: python:3.9 image: python:3.10
tags: tags:
- python - python
services: services:

View File

@ -0,0 +1,23 @@
<component name="ProjectRunConfigurationManager">
<configuration name="docker-compose up django" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
<deployment type="docker-compose.yml">
<settings>
<option name="envFilePath" value=""/>
<option name="services">
<list>
<option value="django"/>
{%- if cookiecutter.use_celery == 'y' %}
<option value="celeryworker"/>
<option value="celerybeat"/>
{%- endif %}
{%- if cookiecutter.frontend_pipeline == 'Gulp' %}
<option value="node"/>
{%- endif %}
</list>
</option>
<option name="sourceFilePath" value="local.yml"/>
</settings>
</deployment>
<method v="2"/>
</configuration>
</component>

View File

@ -0,0 +1,16 @@
<component name="ProjectRunConfigurationManager">
<configuration name="docker-compose up docs" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
<deployment type="docker-compose.yml">
<settings>
<option name="envFilePath" value=""/>
<option name="services">
<list>
<option value="docs"/>
</list>
</option>
<option name="sourceFilePath" value="local.yml"/>
</settings>
</deployment>
<method v="2"/>
</configuration>
</component>

View File

@ -13,7 +13,7 @@
</facet> </facet>
</component> </component>
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
{% if cookiecutter.js_task_runner != 'None' %} {% if cookiecutter.frontend_pipeline == 'Gulp' %}
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/node_modules" /> <excludeFolder url="file://$MODULE_DIR$/node_modules" />
</content> </content>

View File

@ -1,33 +1,37 @@
exclude: 'docs|node_modules|migrations|.git|.tox' exclude: "^docs/|/migrations/"
default_stages: [commit] default_stages: [commit]
fail_fast: true
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1 rev: v4.4.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: end-of-file-fixer - id: end-of-file-fixer
- id: check-yaml - id: check-yaml
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.1
hooks:
- id: pyupgrade
args: [--py310-plus]
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 21.9b0 rev: 22.12.0
hooks: hooks:
- id: black - id: black
- repo: https://github.com/timothycrosley/isort - repo: https://github.com/PyCQA/isort
rev: 5.9.3 rev: 5.12.0
hooks: hooks:
- id: isort - id: isort
- repo: https://gitlab.com/pycqa/flake8 - repo: https://github.com/PyCQA/flake8
rev: 3.9.2 rev: 6.0.0
hooks: hooks:
- id: flake8 - id: flake8
args: ['--config=setup.cfg'] args: ["--config=setup.cfg"]
additional_dependencies: [flake8-isort] additional_dependencies: [flake8-isort]
# sets up .pre-commit-ci.yaml to ensure pre-commit dependencies stay up to date # sets up .pre-commit-ci.yaml to ensure pre-commit dependencies stay up to date
ci: ci:
autoupdate_schedule: weekly autoupdate_schedule: weekly

View File

@ -1,6 +1,6 @@
[MASTER] [MASTER]
load-plugins=pylint_django{% if cookiecutter.use_celery == "y" %}, pylint_celery{% endif %} load-plugins=pylint_django{% if cookiecutter.use_celery == "y" %}, pylint_celery{% endif %}
django-settings-module=config.settings.base django-settings-module=config.settings.local
[FORMAT] [FORMAT]
max-line-length=120 max-line-length=120

View File

@ -7,6 +7,6 @@ build:
image: testing image: testing
python: python:
version: 3.9 version: 3.10
install: install:
- requirements: requirements/local.txt - requirements: requirements/local.txt

View File

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

View File

@ -1,10 +1,10 @@
release: python manage.py migrate release: python manage.py migrate
{% if cookiecutter.use_async == "y" -%} {%- if cookiecutter.use_async == "y" %}
web: gunicorn config.asgi:application -k uvicorn.workers.UvicornWorker web: gunicorn config.asgi:application -k uvicorn.workers.UvicornWorker
{%- else %} {%- else %}
web: gunicorn config.wsgi:application web: gunicorn config.wsgi:application
{%- endif %} {%- endif %}
{% if cookiecutter.use_celery == "y" -%} {%- if cookiecutter.use_celery == "y" %}
worker: REMAP_SIGTERM=SIGQUIT celery -A config.celery_app worker --loglevel=info worker: REMAP_SIGTERM=SIGQUIT celery -A config.celery_app worker --loglevel=info
beat: REMAP_SIGTERM=SIGQUIT celery -A config.celery_app beat --loglevel=info beat: REMAP_SIGTERM=SIGQUIT celery -A config.celery_app beat --loglevel=info
{%- endif %} {%- endif %}

View File

@ -0,0 +1,140 @@
# {{cookiecutter.project_name}}
{{ cookiecutter.description }}
[![Built with Cookiecutter Django](https://img.shields.io/badge/built%20with-Cookiecutter%20Django-ff69b4.svg?logo=cookiecutter)](https://github.com/cookiecutter/cookiecutter-django/)
[![Black code style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)
{%- if cookiecutter.open_source_license != "Not open source" %}
License: {{cookiecutter.open_source_license}}
{%- endif %}
## Settings
Moved to [settings](http://cookiecutter-django.readthedocs.io/en/latest/settings.html).
## Basic Commands
### 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 **superuser account**, use this command:
$ 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.
### Type checks
Running type checks with mypy:
$ mypy {{cookiecutter.project_slug}}
### Test coverage
To run the tests, check your test coverage, and generate an HTML coverage report:
$ coverage run -m pytest
$ coverage html
$ open htmlcov/index.html
#### Running tests with pytest
$ pytest
### Live reloading and Sass CSS compilation
Moved to [Live reloading and SASS compilation](https://cookiecutter-django.readthedocs.io/en/latest/developing-locally.html#sass-compilation-live-reloading).
{%- if cookiecutter.use_celery == "y" %}
### Celery
This app comes with Celery.
To run a celery worker:
``` 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.
{%- endif %}
{%- if cookiecutter.use_mailhog == "y" %}
### Email Server
{%- if cookiecutter.use_docker == "y" %}
In development, it is often nice to be able to see emails that are being sent from your application. For that reason local SMTP server [MailHog](https://github.com/mailhog/MailHog) with a web interface is available as docker container.
Container mailhog will start automatically when you will run all docker containers.
Please check [cookiecutter-django Docker documentation](http://cookiecutter-django.readthedocs.io/en/latest/deployment-with-docker.html) for more details how to start all containers.
With MailHog running, to view messages that are sent by your application, open your browser and go to `http://127.0.0.1:8025`
{%- else %}
In development, it is often nice to be able to see emails that are being sent from your application. If you choose to use [MailHog](https://github.com/mailhog/MailHog) when generating the project a local SMTP server with a web interface will be available.
1. [Download the latest MailHog release](https://github.com/mailhog/MailHog/releases) for your OS.
2. Rename the build to `MailHog`.
3. Copy the file to the project root.
4. Make it executable:
$ chmod +x MailHog
5. Spin up another terminal window and start it there:
./MailHog
6. Check out <http://127.0.0.1:8025/> to see how it goes.
Now you have your own mail server running locally, ready to receive whatever you send it.
{%- endif %}
{%- endif %}
{%- if cookiecutter.use_sentry == "y" %}
### Sentry
Sentry is an error logging aggregator service. You can sign up for a free account at <https://sentry.io/signup/?code=cookiecutter> or download and host it yourself.
The system is set up with reasonable defaults, including 404 logging and integration with the WSGI application.
You must set the DSN url in production.
{%- endif %}
## Deployment
The following details how to deploy this application.
{%- if cookiecutter.use_heroku.lower() == "y" %}
### Heroku
See detailed [cookiecutter-django Heroku documentation](http://cookiecutter-django.readthedocs.io/en/latest/deployment-on-heroku.html).
{%- endif %}
{%- if cookiecutter.use_docker.lower() == "y" %}
### Docker
See detailed [cookiecutter-django Docker documentation](http://cookiecutter-django.readthedocs.io/en/latest/deployment-with-docker.html).
{%- endif %}
{%- if cookiecutter.frontend_pipeline == 'Gulp' %}
### Custom Bootstrap Compilation
The generated CSS is set up with automatic Bootstrap recompilation with variables of your choice.
Bootstrap v5 is installed using npm and customised by tweaking your variables in `static/sass/custom_bootstrap_vars`.
You can find a list of available variables [in the bootstrap source](https://github.com/twbs/bootstrap/blob/main/scss/_variables.scss), or get explanations on them in the [Bootstrap docs](https://getbootstrap.com/docs/5.1/customize/sass/).
Bootstrap's javascript as well as its dependencies is concatenated into a single file: `static/js/vendors.js`.
{%- endif %}

View File

@ -1,175 +0,0 @@
{{cookiecutter.project_name}}
{{ '=' * cookiecutter.project_name|length }}
{{cookiecutter.description}}
.. image:: https://img.shields.io/badge/built%20with-Cookiecutter%20Django-ff69b4.svg?logo=cookiecutter
:target: https://github.com/pydanny/cookiecutter-django/
:alt: Built with Cookiecutter Django
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/ambv/black
:alt: Black code style
{%- if cookiecutter.open_source_license != "Not open source" %}
:License: {{cookiecutter.open_source_license}}
{%- endif %}
Settings
--------
Moved to settings_.
.. _settings: http://cookiecutter-django.readthedocs.io/en/latest/settings.html
Basic Commands
--------------
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 an **superuser account**, use this command::
$ 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.
Type checks
^^^^^^^^^^^
Running type checks with mypy:
::
$ mypy {{cookiecutter.project_slug}}
Test coverage
^^^^^^^^^^^^^
To run the tests, check your test coverage, and generate an HTML coverage report::
$ coverage run -m pytest
$ coverage html
$ open htmlcov/index.html
Running tests with py.test
~~~~~~~~~~~~~~~~~~~~~~~~~~
::
$ pytest
Live reloading and Sass CSS compilation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Moved to `Live reloading and SASS compilation`_.
.. _`Live reloading and SASS compilation`: http://cookiecutter-django.readthedocs.io/en/latest/live-reloading-and-sass-compilation.html
{%- if cookiecutter.use_celery == "y" %}
Celery
^^^^^^
This app comes with Celery.
To run a celery worker:
.. code-block:: 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.
{%- endif %}
{%- if cookiecutter.use_mailhog == "y" %}
Email Server
^^^^^^^^^^^^
{%- if cookiecutter.use_docker == 'y' %}
In development, it is often nice to be able to see emails that are being sent from your application. For that reason local SMTP server `MailHog`_ with a web interface is available as docker container.
Container mailhog will start automatically when you will run all docker containers.
Please check `cookiecutter-django Docker documentation`_ for more details how to start all containers.
With MailHog running, to view messages that are sent by your application, open your browser and go to ``http://127.0.0.1:8025``
{%- else %}
In development, it is often nice to be able to see emails that are being sent from your application. If you choose to use `MailHog`_ when generating the project a local SMTP server with a web interface will be available.
#. `Download the latest MailHog release`_ for your OS.
#. Rename the build to ``MailHog``.
#. Copy the file to the project root.
#. Make it executable: ::
$ chmod +x MailHog
#. Spin up another terminal window and start it there: ::
./MailHog
#. Check out `<http://127.0.0.1:8025/>`_ to see how it goes.
Now you have your own mail server running locally, ready to receive whatever you send it.
.. _`Download the latest MailHog release`: https://github.com/mailhog/MailHog/releases
{%- endif %}
.. _mailhog: https://github.com/mailhog/MailHog
{%- endif %}
{%- if cookiecutter.use_sentry == "y" %}
Sentry
^^^^^^
Sentry is an error logging aggregator service. You can sign up for a free account at https://sentry.io/signup/?code=cookiecutter or download and host it yourself.
The system is setup with reasonable defaults, including 404 logging and integration with the WSGI application.
You must set the DSN url in production.
{%- endif %}
Deployment
----------
The following details how to deploy this application.
{%- if cookiecutter.use_heroku.lower() == "y" %}
Heroku
^^^^^^
See detailed `cookiecutter-django Heroku documentation`_.
.. _`cookiecutter-django Heroku documentation`: http://cookiecutter-django.readthedocs.io/en/latest/deployment-on-heroku.html
{%- endif %}
{%- if cookiecutter.use_docker.lower() == "y" %}
Docker
^^^^^^
See detailed `cookiecutter-django Docker documentation`_.
.. _`cookiecutter-django Docker documentation`: http://cookiecutter-django.readthedocs.io/en/latest/deployment-with-docker.html
{%- endif %}
{%- if cookiecutter.custom_bootstrap_compilation == "y" %}
Custom Bootstrap Compilation
^^^^^^
The generated CSS is set up with automatic Bootstrap recompilation with variables of your choice.
Bootstrap v4 is installed using npm and customised by tweaking your variables in ``static/sass/custom_bootstrap_vars``.
You can find a list of available variables `in the bootstrap source`_, or get explanations on them in the `Bootstrap docs`_.
{%- if cookiecutter.js_task_runner == 'Gulp' %}
Bootstrap's javascript as well as its dependencies is concatenated into a single file: ``static/js/vendors.js``.
{%- endif %}
.. _in the bootstrap source: https://github.com/twbs/bootstrap/blob/v4-dev/scss/_variables.scss
.. _Bootstrap docs: https://getbootstrap.com/docs/4.1/getting-started/theming/
{%- endif %}

View File

@ -1,4 +1,4 @@
ARG PYTHON_VERSION=3.9-slim-buster ARG PYTHON_VERSION=3.10-slim-bullseye
# define an alias for the specfic python version used in this file. # define an alias for the specfic python version used in this file.
FROM python:${PYTHON_VERSION} as python FROM python:${PYTHON_VERSION} as python

View File

@ -5,4 +5,4 @@ set -o nounset
rm -f './celerybeat.pid' rm -f './celerybeat.pid'
celery -A config.celery_app beat -l INFO exec watchfiles celery.__main__.main --args '-A config.celery_app beat -l INFO'

View File

@ -3,9 +3,6 @@
set -o errexit set -o errexit
set -o nounset set -o nounset
exec watchfiles celery.__main__.main \
celery \ --args \
-A config.celery_app \ "-A config.celery_app -b \"${CELERY_BROKER_URL}\" flower --basic_auth=\"${CELERY_FLOWER_USER}:${CELERY_FLOWER_PASSWORD}\""
-b "${CELERY_BROKER_URL}" \
flower \
--basic_auth="${CELERY_FLOWER_USER}:${CELERY_FLOWER_PASSWORD}"

View File

@ -4,4 +4,4 @@ set -o errexit
set -o nounset set -o nounset
watchgod celery.__main__.main --args -A config.celery_app worker -l INFO exec watchfiles celery.__main__.main --args '-A config.celery_app worker -l INFO'

View File

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

View File

@ -1,28 +1,61 @@
FROM python:3.9-slim-buster 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
# Python build stage
FROM python as python-build-stage
ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONDONTWRITEBYTECODE 1
RUN apt-get update \ RUN apt-get update && apt-get install --no-install-recommends -y \
# dependencies for building Python packages # dependencies for building Python packages
&& apt-get install -y build-essential \ build-essential \
# psycopg2 dependencies # psycopg2 dependencies
&& apt-get install -y libpq-dev \ libpq-dev \
# Translations dependencies
&& apt-get install -y gettext \
# Uncomment below lines to enable Sphinx output to latex and pdf
# && apt-get install -y texlive-latex-recommended \
# && apt-get install -y texlive-fonts-recommended \
# && apt-get install -y texlive-latex-extra \
# && apt-get install -y latexmk \
# cleaning up unused files # cleaning up unused files
&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Requirements are installed here to ensure they will be cached. # Requirements are installed here to ensure they will be cached.
COPY ./requirements /requirements COPY ./requirements /requirements
# All imports needed for autodoc.
RUN pip install -r /requirements/local.txt -r /requirements/production.txt # create python dependency wheels
RUN pip wheel --no-cache-dir --wheel-dir /usr/src/app/wheels \
-r /requirements/local.txt -r /requirements/production.txt \
&& rm -rf /requirements
# Python 'run' stage
FROM python as python-run-stage
ARG BUILD_ENVIRONMENT
ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE 1
RUN apt-get update && apt-get install --no-install-recommends -y \
# To run the Makefile
make \
# psycopg2 dependencies
libpq-dev \
# Translations dependencies
gettext \
# Uncomment below lines to enable Sphinx output to latex and pdf
# texlive-latex-recommended \
# texlive-fonts-recommended \
# texlive-latex-extra \
# latexmk \
# cleaning up unused files
&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
&& rm -rf /var/lib/apt/lists/*
# copy python dependency wheels from python-build-stage
COPY --from=python-build-stage /usr/src/app/wheels /wheels
# use wheels to install python dependencies
RUN pip install --no-cache /wheels/* \
&& rm -rf /wheels
COPY ./compose/local/docs/start /start-docs COPY ./compose/local/docs/start /start-docs
RUN sed -i 's/\r$//g' /start-docs RUN sed -i 's/\r$//g' /start-docs

View File

@ -4,4 +4,4 @@ set -o errexit
set -o pipefail set -o pipefail
set -o nounset set -o nounset
make livehtml exec make livehtml

View File

@ -1,4 +1,4 @@
FROM node:10-stretch-slim FROM node:16-bullseye-slim
WORKDIR /app WORKDIR /app

View File

@ -1,7 +1,7 @@
ARG PYTHON_VERSION=3.9-slim-buster ARG PYTHON_VERSION=3.10-slim-bullseye
{% if cookiecutter.js_task_runner == 'Gulp' -%} {% if cookiecutter.frontend_pipeline == 'Gulp' -%}
FROM node:10-stretch-slim as client-builder FROM node:16-bullseye-slim as client-builder
ARG APP_HOME=/app ARG APP_HOME=/app
WORKDIR ${APP_HOME} WORKDIR ${APP_HOME}
@ -99,7 +99,7 @@ RUN chmod +x /start-flower
# copy application code to WORKDIR # copy application code to WORKDIR
{%- if cookiecutter.js_task_runner == 'Gulp' %} {%- if cookiecutter.frontend_pipeline == 'Gulp' %}
COPY --from=client-builder --chown=django:django ${APP_HOME} ${APP_HOME} COPY --from=client-builder --chown=django:django ${APP_HOME} ${APP_HOME}
{% else %} {% else %}
COPY --chown=django:django . ${APP_HOME} COPY --chown=django:django . ${APP_HOME}

View File

@ -5,4 +5,4 @@ set -o pipefail
set -o nounset set -o nounset
celery -A config.celery_app beat -l INFO exec celery -A config.celery_app beat -l INFO

View File

@ -4,7 +4,7 @@ set -o errexit
set -o nounset set -o nounset
celery \ exec celery \
-A config.celery_app \ -A config.celery_app \
-b "${CELERY_BROKER_URL}" \ -b "${CELERY_BROKER_URL}" \
flower \ flower \

View File

@ -5,4 +5,4 @@ set -o pipefail
set -o nounset set -o nounset
celery -A config.celery_app worker -l INFO exec celery -A config.celery_app worker -l INFO

View File

@ -16,13 +16,17 @@ if [ -z "${POSTGRES_USER}" ]; then
fi fi
export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}" export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
postgres_ready() {
python << END python << END
import sys import sys
import time
import psycopg2 import psycopg2
try: suggest_unrecoverable_after = 30
start = time.time()
while True:
try:
psycopg2.connect( psycopg2.connect(
dbname="${POSTGRES_DB}", dbname="${POSTGRES_DB}",
user="${POSTGRES_USER}", user="${POSTGRES_USER}",
@ -30,16 +34,16 @@ try:
host="${POSTGRES_HOST}", host="${POSTGRES_HOST}",
port="${POSTGRES_PORT}", port="${POSTGRES_PORT}",
) )
except psycopg2.OperationalError: break
sys.exit(-1) except psycopg2.OperationalError as error:
sys.exit(0) sys.stderr.write("Waiting for PostgreSQL to become available...\n")
if time.time() - start > suggest_unrecoverable_after:
sys.stderr.write(" This is taking longer than expected. The following exception may be indicative of an unrecoverable error: '{}'\n".format(error))
time.sleep(1)
END END
}
until postgres_ready; do
>&2 echo 'Waiting for PostgreSQL to become available...'
sleep 1
done
>&2 echo 'PostgreSQL is available' >&2 echo 'PostgreSQL is available'
exec "$@" exec "$@"

View File

@ -6,7 +6,7 @@ set -o nounset
python /app/manage.py collectstatic --noinput python /app/manage.py collectstatic --noinput
{% if cookiecutter.use_whitenoise == 'y' and cookiecutter.use_compressor == 'y' %} {% if cookiecutter.use_whitenoise == 'y' and cookiecutter.frontend_pipeline == 'Django Compressor' %}
compress_enabled() { compress_enabled() {
python << END python << END
import sys import sys
@ -27,8 +27,8 @@ if compress_enabled; then
python /app/manage.py compress python /app/manage.py compress
fi fi
{%- endif %} {%- endif %}
{% if cookiecutter.use_async == 'y' %} {%- if cookiecutter.use_async == 'y' %}
/usr/local/bin/gunicorn config.asgi --bind 0.0.0.0:5000 --chdir=/app -k uvicorn.workers.UvicornWorker exec /usr/local/bin/gunicorn config.asgi --bind 0.0.0.0:5000 --chdir=/app -k uvicorn.workers.UvicornWorker
{% else %} {%- else %}
/usr/local/bin/gunicorn config.wsgi --bind 0.0.0.0:5000 --chdir=/app exec /usr/local/bin/gunicorn config.wsgi --bind 0.0.0.0:5000 --chdir=/app
{%- endif %} {%- endif %}

View File

@ -15,8 +15,8 @@ from django.core.asgi import get_asgi_application
# This allows easy placement of apps within the interior # This allows easy placement of apps within the interior
# {{ cookiecutter.project_slug }} directory. # {{ cookiecutter.project_slug }} directory.
ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
sys.path.append(str(ROOT_DIR / "{{ cookiecutter.project_slug }}")) sys.path.append(str(BASE_DIR / "{{ cookiecutter.project_slug }}"))
# If DJANGO_SETTINGS_MODULE is unset, default to the local settings # If DJANGO_SETTINGS_MODULE is unset, default to the local settings
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")

View File

@ -5,15 +5,15 @@ from pathlib import Path
import environ import environ
ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent.parent BASE_DIR = Path(__file__).resolve(strict=True).parent.parent.parent
# {{ cookiecutter.project_slug }}/ # {{ cookiecutter.project_slug }}/
APPS_DIR = ROOT_DIR / "{{ cookiecutter.project_slug }}" APPS_DIR = BASE_DIR / "{{ cookiecutter.project_slug }}"
env = environ.Env() env = environ.Env()
READ_DOT_ENV_FILE = env.bool("DJANGO_READ_DOT_ENV_FILE", default=False) READ_DOT_ENV_FILE = env.bool("DJANGO_READ_DOT_ENV_FILE", default=False)
if READ_DOT_ENV_FILE: if READ_DOT_ENV_FILE:
# OS environment variables take precedence over variables from .env # OS environment variables take precedence over variables from .env
env.read_env(str(ROOT_DIR / ".env")) env.read_env(str(BASE_DIR / ".env"))
# GENERAL # GENERAL
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -30,12 +30,10 @@ LANGUAGE_CODE = "en-us"
SITE_ID = 1 SITE_ID = 1
# https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n # https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n
USE_I18N = True USE_I18N = True
# https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n
USE_L10N = True
# https://docs.djangoproject.com/en/dev/ref/settings/#use-tz # https://docs.djangoproject.com/en/dev/ref/settings/#use-tz
USE_TZ = True USE_TZ = True
# https://docs.djangoproject.com/en/dev/ref/settings/#locale-paths # https://docs.djangoproject.com/en/dev/ref/settings/#locale-paths
LOCALE_PATHS = [str(ROOT_DIR / "locale")] LOCALE_PATHS = [str(BASE_DIR / "locale")]
# DATABASES # DATABASES
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -44,10 +42,15 @@ LOCALE_PATHS = [str(ROOT_DIR / "locale")]
DATABASES = {"default": env.db("DATABASE_URL")} DATABASES = {"default": env.db("DATABASE_URL")}
{%- else %} {%- else %}
DATABASES = { DATABASES = {
"default": env.db("DATABASE_URL", default="postgres://{% if cookiecutter.windows == 'y' %}localhost{% endif %}/{{cookiecutter.project_slug}}"), "default": env.db(
"DATABASE_URL",
default="postgres://{% if cookiecutter.windows == 'y' %}localhost{% endif %}/{{cookiecutter.project_slug}}",
),
} }
{%- endif %} {%- endif %}
DATABASES["default"]["ATOMIC_REQUESTS"] = True DATABASES["default"]["ATOMIC_REQUESTS"] = True
# https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-DEFAULT_AUTO_FIELD
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# URLS # URLS
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -71,6 +74,7 @@ DJANGO_APPS = [
] ]
THIRD_PARTY_APPS = [ THIRD_PARTY_APPS = [
"crispy_forms", "crispy_forms",
"crispy_bootstrap5",
"allauth", "allauth",
"allauth.account", "allauth.account",
"allauth.socialaccount", "allauth.socialaccount",
@ -81,11 +85,12 @@ THIRD_PARTY_APPS = [
"rest_framework", "rest_framework",
"rest_framework.authtoken", "rest_framework.authtoken",
"corsheaders", "corsheaders",
"drf_spectacular",
{%- endif %} {%- endif %}
] ]
LOCAL_APPS = [ LOCAL_APPS = [
"{{ cookiecutter.project_slug }}.users.apps.UsersConfig", "{{ cookiecutter.project_slug }}.users",
# Your stuff: custom apps go here # Your stuff: custom apps go here
] ]
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps # https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
@ -154,7 +159,7 @@ MIDDLEWARE = [
# STATIC # STATIC
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#static-root # https://docs.djangoproject.com/en/dev/ref/settings/#static-root
STATIC_ROOT = str(ROOT_DIR / "staticfiles") STATIC_ROOT = str(BASE_DIR / "staticfiles")
# https://docs.djangoproject.com/en/dev/ref/settings/#static-url # https://docs.djangoproject.com/en/dev/ref/settings/#static-url
STATIC_URL = "/static/" STATIC_URL = "/static/"
# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS # https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
@ -179,15 +184,11 @@ TEMPLATES = [
{ {
# https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND # https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND
"BACKEND": "django.template.backends.django.DjangoTemplates", "BACKEND": "django.template.backends.django.DjangoTemplates",
# https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs # https://docs.djangoproject.com/en/dev/ref/settings/#dirs
"DIRS": [str(APPS_DIR / "templates")], "DIRS": [str(APPS_DIR / "templates")],
# https://docs.djangoproject.com/en/dev/ref/settings/#app-dirs
"APP_DIRS": True,
"OPTIONS": { "OPTIONS": {
# https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
# https://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types
"loaders": [
"django.template.loaders.filesystem.Loader",
"django.template.loaders.app_directories.Loader",
],
# https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors # https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors
"context_processors": [ "context_processors": [
"django.template.context_processors.debug", "django.template.context_processors.debug",
@ -198,7 +199,7 @@ TEMPLATES = [
"django.template.context_processors.static", "django.template.context_processors.static",
"django.template.context_processors.tz", "django.template.context_processors.tz",
"django.contrib.messages.context_processors.messages", "django.contrib.messages.context_processors.messages",
"{{ cookiecutter.project_slug }}.utils.context_processors.settings_context", "{{cookiecutter.project_slug}}.users.context_processors.allauth_settings",
], ],
}, },
} }
@ -208,7 +209,8 @@ TEMPLATES = [
FORM_RENDERER = "django.forms.renderers.TemplatesSetting" FORM_RENDERER = "django.forms.renderers.TemplatesSetting"
# http://django-crispy-forms.readthedocs.io/en/latest/install.html#template-packs # http://django-crispy-forms.readthedocs.io/en/latest/install.html#template-packs
CRISPY_TEMPLATE_PACK = "bootstrap4" CRISPY_TEMPLATE_PACK = "bootstrap5"
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
# FIXTURES # FIXTURES
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -273,26 +275,37 @@ LOGGING = {
# Celery # Celery
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
if USE_TZ: if USE_TZ:
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-timezone # https://docs.celeryq.dev/en/stable/userguide/configuration.html#std:setting-timezone
CELERY_TIMEZONE = TIME_ZONE CELERY_TIMEZONE = TIME_ZONE
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-broker_url # https://docs.celeryq.dev/en/stable/userguide/configuration.html#std:setting-broker_url
CELERY_BROKER_URL = env("CELERY_BROKER_URL") CELERY_BROKER_URL = env("CELERY_BROKER_URL")
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-result_backend # https://docs.celeryq.dev/en/stable/userguide/configuration.html#std:setting-result_backend
CELERY_RESULT_BACKEND = CELERY_BROKER_URL CELERY_RESULT_BACKEND = CELERY_BROKER_URL
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-accept_content # https://docs.celeryq.dev/en/stable/userguide/configuration.html#result-extended
CELERY_RESULT_EXTENDED = True
# https://docs.celeryq.dev/en/stable/userguide/configuration.html#result-backend-always-retry
# https://github.com/celery/celery/pull/6122
CELERY_RESULT_BACKEND_ALWAYS_RETRY = True
# https://docs.celeryq.dev/en/stable/userguide/configuration.html#result-backend-max-retries
CELERY_RESULT_BACKEND_MAX_RETRIES = 10
# https://docs.celeryq.dev/en/stable/userguide/configuration.html#std:setting-accept_content
CELERY_ACCEPT_CONTENT = ["json"] CELERY_ACCEPT_CONTENT = ["json"]
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-task_serializer # https://docs.celeryq.dev/en/stable/userguide/configuration.html#std:setting-task_serializer
CELERY_TASK_SERIALIZER = "json" CELERY_TASK_SERIALIZER = "json"
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-result_serializer # https://docs.celeryq.dev/en/stable/userguide/configuration.html#std:setting-result_serializer
CELERY_RESULT_SERIALIZER = "json" CELERY_RESULT_SERIALIZER = "json"
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-time-limit # https://docs.celeryq.dev/en/stable/userguide/configuration.html#task-time-limit
# TODO: set to whatever value is adequate in your circumstances # TODO: set to whatever value is adequate in your circumstances
CELERY_TASK_TIME_LIMIT = 5 * 60 CELERY_TASK_TIME_LIMIT = 5 * 60
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-soft-time-limit # https://docs.celeryq.dev/en/stable/userguide/configuration.html#task-soft-time-limit
# TODO: set to whatever value is adequate in your circumstances # TODO: set to whatever value is adequate in your circumstances
CELERY_TASK_SOFT_TIME_LIMIT = 60 CELERY_TASK_SOFT_TIME_LIMIT = 60
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#beat-scheduler # https://docs.celeryq.dev/en/stable/userguide/configuration.html#beat-scheduler
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler" CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
# https://docs.celeryq.dev/en/stable/userguide/configuration.html#worker-send-task-events
CELERY_WORKER_SEND_TASK_EVENTS = True
# https://docs.celeryq.dev/en/stable/userguide/configuration.html#std-setting-task_send_sent_event
CELERY_TASK_SEND_SENT_EVENT = True
{%- endif %} {%- endif %}
# django-allauth # django-allauth
@ -306,9 +319,13 @@ ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_EMAIL_VERIFICATION = "mandatory" ACCOUNT_EMAIL_VERIFICATION = "mandatory"
# https://django-allauth.readthedocs.io/en/latest/configuration.html # https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_ADAPTER = "{{cookiecutter.project_slug}}.users.adapters.AccountAdapter" ACCOUNT_ADAPTER = "{{cookiecutter.project_slug}}.users.adapters.AccountAdapter"
# https://django-allauth.readthedocs.io/en/latest/forms.html
ACCOUNT_FORMS = {"signup": "{{cookiecutter.project_slug}}.users.forms.UserSignupForm"}
# https://django-allauth.readthedocs.io/en/latest/configuration.html # https://django-allauth.readthedocs.io/en/latest/configuration.html
SOCIALACCOUNT_ADAPTER = "{{cookiecutter.project_slug}}.users.adapters.SocialAccountAdapter" SOCIALACCOUNT_ADAPTER = "{{cookiecutter.project_slug}}.users.adapters.SocialAccountAdapter"
{% if cookiecutter.use_compressor == 'y' -%} # https://django-allauth.readthedocs.io/en/latest/forms.html
SOCIALACCOUNT_FORMS = {"signup": "{{cookiecutter.project_slug}}.users.forms.UserSocialSignupForm"}
{% if cookiecutter.frontend_pipeline == 'Django Compressor' -%}
# django-compressor # django-compressor
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# https://django-compressor.readthedocs.io/en/latest/quickstart/#installation # https://django-compressor.readthedocs.io/en/latest/quickstart/#installation
@ -325,11 +342,20 @@ REST_FRAMEWORK = {
"rest_framework.authentication.TokenAuthentication", "rest_framework.authentication.TokenAuthentication",
), ),
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",), "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
} }
# django-cors-headers - https://github.com/adamchainz/django-cors-headers#setup # django-cors-headers - https://github.com/adamchainz/django-cors-headers#setup
CORS_URLS_REGEX = r"^/api/.*$" CORS_URLS_REGEX = r"^/api/.*$"
# By Default swagger ui is available only to admin user(s). You can change permission classes to change that
# See more configuration options at https://drf-spectacular.readthedocs.io/en/latest/settings.html#settings
SPECTACULAR_SETTINGS = {
"TITLE": "{{ cookiecutter.project_name }} API",
"DESCRIPTION": "Documentation of API endpoints of {{ cookiecutter.project_name }}",
"VERSION": "1.0.0",
"SERVE_PERMISSIONS": ["rest_framework.permissions.IsAdminUser"],
}
{%- endif %} {%- endif %}
# Your stuff... # Your stuff...
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -69,7 +69,7 @@ if env("USE_DOCKER") == "yes":
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname()) hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
INTERNAL_IPS += [".".join(ip.split(".")[:-1] + ["1"]) for ip in ips] INTERNAL_IPS += [".".join(ip.split(".")[:-1] + ["1"]) for ip in ips]
{%- if cookiecutter.js_task_runner == 'Gulp' %} {%- if cookiecutter.frontend_pipeline == 'Gulp' %}
try: try:
_, _, ips = socket.gethostbyname_ex("node") _, _, ips = socket.gethostbyname_ex("node")
INTERNAL_IPS.extend(ips) INTERNAL_IPS.extend(ips)
@ -88,10 +88,10 @@ INSTALLED_APPS += ["django_extensions"] # noqa F405
# Celery # Celery
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
{% if cookiecutter.use_docker == 'n' -%} {% if cookiecutter.use_docker == 'n' -%}
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-always-eager # https://docs.celeryq.dev/en/stable/userguide/configuration.html#task-always-eager
CELERY_TASK_ALWAYS_EAGER = True CELERY_TASK_ALWAYS_EAGER = True
{%- endif %} {%- endif %}
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-eager-propagates # https://docs.celeryq.dev/en/stable/userguide/configuration.html#task-eager-propagates
CELERY_TASK_EAGER_PROPAGATES = True CELERY_TASK_EAGER_PROPAGATES = True
{%- endif %} {%- endif %}

View File

@ -2,11 +2,11 @@
import logging import logging
import sentry_sdk import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.logging import LoggingIntegration
{%- if cookiecutter.use_celery == 'y' %} {%- if cookiecutter.use_celery == 'y' %}
from sentry_sdk.integrations.celery import CeleryIntegration from sentry_sdk.integrations.celery import CeleryIntegration
{% endif %} {%- endif %}
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.logging import LoggingIntegration
from sentry_sdk.integrations.redis import RedisIntegration from sentry_sdk.integrations.redis import RedisIntegration
{% endif -%} {% endif -%}
@ -22,8 +22,6 @@ ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["{{ cookiecutter.domai
# DATABASES # DATABASES
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
DATABASES["default"] = env.db("DATABASE_URL") # noqa F405
DATABASES["default"]["ATOMIC_REQUESTS"] = True # noqa F405
DATABASES["default"]["CONN_MAX_AGE"] = env.int("CONN_MAX_AGE", default=60) # noqa F405 DATABASES["default"]["CONN_MAX_AGE"] = env.int("CONN_MAX_AGE", default=60) # noqa F405
# CACHES # CACHES
@ -88,6 +86,11 @@ AWS_S3_OBJECT_PARAMETERS = {
"CacheControl": f"max-age={_AWS_EXPIRY}, s-maxage={_AWS_EXPIRY}, must-revalidate" "CacheControl": f"max-age={_AWS_EXPIRY}, s-maxage={_AWS_EXPIRY}, must-revalidate"
} }
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
AWS_S3_MAX_MEMORY_SIZE = env.int(
"DJANGO_AWS_S3_MAX_MEMORY_SIZE",
default=100_000_000, # 100MB
)
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
AWS_S3_REGION_NAME = env("DJANGO_AWS_S3_REGION_NAME", default=None) AWS_S3_REGION_NAME = env("DJANGO_AWS_S3_REGION_NAME", default=None)
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#cloudfront # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#cloudfront
AWS_S3_CUSTOM_DOMAIN = env("DJANGO_AWS_S3_CUSTOM_DOMAIN", default=None) AWS_S3_CUSTOM_DOMAIN = env("DJANGO_AWS_S3_CUSTOM_DOMAIN", default=None)
@ -95,6 +98,10 @@ aws_s3_domain = AWS_S3_CUSTOM_DOMAIN or f"{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws
{% elif cookiecutter.cloud_provider == 'GCP' %} {% elif cookiecutter.cloud_provider == 'GCP' %}
GS_BUCKET_NAME = env("DJANGO_GCP_STORAGE_BUCKET_NAME") GS_BUCKET_NAME = env("DJANGO_GCP_STORAGE_BUCKET_NAME")
GS_DEFAULT_ACL = "publicRead" GS_DEFAULT_ACL = "publicRead"
{% elif cookiecutter.cloud_provider == 'Azure' %}
AZURE_ACCOUNT_KEY = env("DJANGO_AZURE_ACCOUNT_KEY")
AZURE_ACCOUNT_NAME = env("DJANGO_AZURE_ACCOUNT_NAME")
AZURE_CONTAINER = env("DJANGO_AZURE_CONTAINER_NAME")
{% endif -%} {% endif -%}
{% if cookiecutter.cloud_provider != 'None' or cookiecutter.use_whitenoise == 'y' -%} {% if cookiecutter.cloud_provider != 'None' or cookiecutter.use_whitenoise == 'y' -%}
@ -111,6 +118,9 @@ STATIC_URL = f"https://{aws_s3_domain}/static/"
STATICFILES_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.StaticRootGoogleCloudStorage" STATICFILES_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.StaticRootGoogleCloudStorage"
COLLECTFAST_STRATEGY = "collectfast.strategies.gcloud.GoogleCloudStrategy" COLLECTFAST_STRATEGY = "collectfast.strategies.gcloud.GoogleCloudStrategy"
STATIC_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/static/" STATIC_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/static/"
{% elif cookiecutter.cloud_provider == 'Azure' -%}
STATICFILES_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.StaticRootAzureStorage"
STATIC_URL = f"https://{AZURE_ACCOUNT_NAME}.blob.core.windows.net/static/"
{% endif -%} {% endif -%}
# MEDIA # MEDIA
@ -121,26 +131,17 @@ MEDIA_URL = f"https://{aws_s3_domain}/media/"
{%- elif cookiecutter.cloud_provider == 'GCP' %} {%- elif cookiecutter.cloud_provider == 'GCP' %}
DEFAULT_FILE_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.MediaRootGoogleCloudStorage" DEFAULT_FILE_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.MediaRootGoogleCloudStorage"
MEDIA_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/media/" MEDIA_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/media/"
{%- elif cookiecutter.cloud_provider == 'Azure' %}
DEFAULT_FILE_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.MediaRootAzureStorage"
MEDIA_URL = f"https://{AZURE_ACCOUNT_NAME}.blob.core.windows.net/media/"
{%- endif %} {%- endif %}
# TEMPLATES
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#templates
TEMPLATES[-1]["OPTIONS"]["loaders"] = [ # type: ignore[index] # noqa F405
(
"django.template.loaders.cached.Loader",
[
"django.template.loaders.filesystem.Loader",
"django.template.loaders.app_directories.Loader",
],
)
]
# EMAIL # EMAIL
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email # https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email
DEFAULT_FROM_EMAIL = env( DEFAULT_FROM_EMAIL = env(
"DJANGO_DEFAULT_FROM_EMAIL", default="{{cookiecutter.project_name}} <noreply@{{cookiecutter.domain_name}}>" "DJANGO_DEFAULT_FROM_EMAIL",
default="{{cookiecutter.project_name}} <noreply@{{cookiecutter.domain_name}}>",
) )
# https://docs.djangoproject.com/en/dev/ref/settings/#server-email # https://docs.djangoproject.com/en/dev/ref/settings/#server-email
SERVER_EMAIL = env("DJANGO_SERVER_EMAIL", default=DEFAULT_FROM_EMAIL) SERVER_EMAIL = env("DJANGO_SERVER_EMAIL", default=DEFAULT_FROM_EMAIL)
@ -179,7 +180,6 @@ EMAIL_BACKEND = "anymail.backends.mailjet.EmailBackend"
ANYMAIL = { ANYMAIL = {
"MAILJET_API_KEY": env("MAILJET_API_KEY"), "MAILJET_API_KEY": env("MAILJET_API_KEY"),
"MAILJET_SECRET_KEY": env("MAILJET_SECRET_KEY"), "MAILJET_SECRET_KEY": env("MAILJET_SECRET_KEY"),
"MAILJET_API_URL": env("MAILJET_API_URL", default="https://api.mailjet.com/v3"),
} }
{%- elif cookiecutter.mail_service == 'Mandrill' %} {%- elif cookiecutter.mail_service == 'Mandrill' %}
# https://anymail.readthedocs.io/en/stable/esps/mandrill/ # https://anymail.readthedocs.io/en/stable/esps/mandrill/
@ -202,8 +202,6 @@ ANYMAIL = {
EMAIL_BACKEND = "anymail.backends.sendgrid.EmailBackend" EMAIL_BACKEND = "anymail.backends.sendgrid.EmailBackend"
ANYMAIL = { ANYMAIL = {
"SENDGRID_API_KEY": env("SENDGRID_API_KEY"), "SENDGRID_API_KEY": env("SENDGRID_API_KEY"),
"SENDGRID_GENERATE_MESSAGE_ID": env("SENDGRID_GENERATE_MESSAGE_ID"),
"SENDGRID_MERGE_FIELD_FORMAT": env("SENDGRID_MERGE_FIELD_FORMAT"),
"SENDGRID_API_URL": env("SENDGRID_API_URL", default="https://api.sendgrid.com/v3/"), "SENDGRID_API_URL": env("SENDGRID_API_URL", default="https://api.sendgrid.com/v3/"),
} }
{%- elif cookiecutter.mail_service == 'SendinBlue' %} {%- elif cookiecutter.mail_service == 'SendinBlue' %}
@ -230,7 +228,7 @@ EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
ANYMAIL = {} ANYMAIL = {}
{%- endif %} {%- endif %}
{% if cookiecutter.use_compressor == 'y' -%} {% if cookiecutter.frontend_pipeline == 'Django Compressor' -%}
# django-compressor # django-compressor
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_ENABLED # https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_ENABLED
@ -238,7 +236,7 @@ COMPRESS_ENABLED = env.bool("COMPRESS_ENABLED", default=True)
{%- if cookiecutter.cloud_provider == 'None' %} {%- if cookiecutter.cloud_provider == 'None' %}
# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_STORAGE # https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_STORAGE
COMPRESS_STORAGE = "compressor.storage.GzipCompressorFileStorage" COMPRESS_STORAGE = "compressor.storage.GzipCompressorFileStorage"
{%- elif cookiecutter.cloud_provider in ('AWS', 'GCP') and cookiecutter.use_whitenoise == 'n' %} {%- elif cookiecutter.cloud_provider in ('AWS', 'GCP', 'Azure') and cookiecutter.use_whitenoise == 'n' %}
# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_STORAGE # https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_STORAGE
COMPRESS_STORAGE = STATICFILES_STORAGE COMPRESS_STORAGE = STATICFILES_STORAGE
{%- endif %} {%- endif %}
@ -370,5 +368,15 @@ sentry_sdk.init(
traces_sample_rate=env.float("SENTRY_TRACES_SAMPLE_RATE", default=0.0), traces_sample_rate=env.float("SENTRY_TRACES_SAMPLE_RATE", default=0.0),
) )
{% endif %} {% endif %}
{% if cookiecutter.use_drf == "y" -%}
# django-rest-framework
# -------------------------------------------------------------------------------
# Tools that generate code samples can use SERVERS to point to the correct domain
SPECTACULAR_SETTINGS["SERVERS"] = [ # noqa F405
{"url": "https://{{ cookiecutter.domain_name }}", "description": "Production server"}
]
{%- endif %}
# Your stuff... # Your stuff...
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -20,22 +20,14 @@ TEST_RUNNER = "django.test.runner.DiscoverRunner"
# https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers # https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"] PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
# TEMPLATES
# ------------------------------------------------------------------------------
TEMPLATES[-1]["OPTIONS"]["loaders"] = [ # type: ignore[index] # noqa F405
(
"django.template.loaders.cached.Loader",
[
"django.template.loaders.filesystem.Loader",
"django.template.loaders.app_directories.Loader",
],
)
]
# EMAIL # EMAIL
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
# DEBUGGING FOR TEMPLATES
# ------------------------------------------------------------------------------
TEMPLATES[0]["OPTIONS"]["debug"] = True # type: ignore # noqa F405
# Your stuff... # Your stuff...
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -8,6 +8,7 @@ from django.urls import include, path
from django.views import defaults as default_views from django.views import defaults as default_views
from django.views.generic import TemplateView from django.views.generic import TemplateView
{%- if cookiecutter.use_drf == 'y' %} {%- if cookiecutter.use_drf == 'y' %}
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
from rest_framework.authtoken.views import obtain_auth_token from rest_framework.authtoken.views import obtain_auth_token
{%- endif %} {%- endif %}
@ -35,6 +36,12 @@ urlpatterns += [
path("api/", include("config.api_router")), path("api/", include("config.api_router")),
# DRF auth token # DRF auth token
path("auth-token/", obtain_auth_token), path("auth-token/", obtain_auth_token),
path("api/schema/", SpectacularAPIView.as_view(), name="api-schema"),
path(
"api/docs/",
SpectacularSwaggerView.as_view(url_name="api-schema"),
name="api-docs",
),
] ]
{%- endif %} {%- endif %}

View File

@ -21,8 +21,8 @@ from django.core.wsgi import get_wsgi_application
# This allows easy placement of apps within the interior # This allows easy placement of apps within the interior
# {{ cookiecutter.project_slug }} directory. # {{ cookiecutter.project_slug }} directory.
ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
sys.path.append(str(ROOT_DIR / "{{ cookiecutter.project_slug }}")) sys.path.append(str(BASE_DIR / "{{ cookiecutter.project_slug }}"))
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
# if running multiple sites in the same mod_wsgi process. To fix this, use # if running multiple sites in the same mod_wsgi process. To fix this, use
# mod_wsgi daemon mode with each site in its own daemon process, or use # mod_wsgi daemon mode with each site in its own daemon process, or use

View File

@ -4,7 +4,7 @@
# You can set these variables from the command line, and also # You can set these variables from the command line, and also
# from the environment for the first two. # from the environment for the first two.
SPHINXOPTS ?= SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build -c . SPHINXBUILD ?= sphinx-build
SOURCEDIR = . SOURCEDIR = .
BUILDDIR = ./_build BUILDDIR = ./_build
{%- if cookiecutter.use_docker == 'y' %} {%- if cookiecutter.use_docker == 'y' %}
@ -17,24 +17,20 @@ APP = ../{{cookiecutter.project_slug}}
# Put it first so that "make" without argument is like "make help". # Put it first so that "make" without argument is like "make help".
help: help:
@$(SPHINXBUILD) help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -c .
# Build, watch and serve docs with live reload # Build, watch and serve docs with live reload
livehtml: livehtml:
sphinx-autobuild -b html sphinx-autobuild -b html
{%- if cookiecutter.use_docker == 'y' %} --host 0.0.0.0 {%- if cookiecutter.use_docker == 'y' %} --host 0.0.0.0
{%- else %} --open-browser {%- else %} --open-browser
{%- endif %} --port 7000 --watch $(APP) -c . $(SOURCEDIR) $(BUILDDIR)/html {%- endif %} --port 9000 --watch $(APP) -c . $(SOURCEDIR) $(BUILDDIR)/html
# Outputs rst files from django application code # Outputs rst files from django application code
apidocs: apidocs:
{%- if cookiecutter.use_docker == 'y' %} sphinx-apidoc -o $(SOURCEDIR)/api $(APP)
sphinx-apidoc -o $(SOURCEDIR)/api /app
{%- else %}
sphinx-apidoc -o $(SOURCEDIR)/api ../{{cookiecutter.project_slug}}
{%- endif %}
# Catch-all target: route all unknown targets to Sphinx using the new # Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile %: Makefile
@$(SPHINXBUILD) -b $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -c .

View File

@ -28,7 +28,7 @@ Docstrings to Documentation
The sphinx extension `apidoc <https://www.sphinx-doc.org/en/master/man/sphinx-apidoc.html/>`_ is used to automatically document code using signatures and docstrings. The sphinx extension `apidoc <https://www.sphinx-doc.org/en/master/man/sphinx-apidoc.html/>`_ is used to automatically document code using signatures and docstrings.
Numpy or Google style docstrings will be picked up from project files and availble for documentation. See the `Napoleon <https://sphinxcontrib-napoleon.readthedocs.io/en/latest/>`_ extension for details. Numpy or Google style docstrings will be picked up from project files and available for documentation. See the `Napoleon <https://sphinxcontrib-napoleon.readthedocs.io/en/latest/>`_ extension for details.
For an in-use example, see the `page source <_sources/users.rst.txt>`_ for :ref:`users`. For an in-use example, see the `page source <_sources/users.rst.txt>`_ for :ref:`users`.

Some files were not shown because too many files have changed in this diff Show More