├── .dockerignore ├── .editorconfig ├── .env-dist ├── .github └── workflows │ └── actions.yml ├── .gitignore ├── .pre-commit-config.yaml ├── Dockerfile ├── LICENSE ├── README.md ├── compose-entrypoint.sh ├── compose.yml ├── config ├── __init__.py-tpl ├── settings.py-tpl ├── tests │ └── test_file.py ├── urls.py-tpl └── wsgi.py-tpl ├── conftest.py-tpl ├── frontend ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico └── site.webmanifest ├── justfile ├── manage.py-tpl ├── pyproject.toml ├── requirements.in └── requirements.txt /.dockerignore: -------------------------------------------------------------------------------- 1 | **/__pycache__ 2 | *.pyc 3 | .* 4 | compose.override.yml 5 | Dockerfile 6 | justfile 7 | LICENSE 8 | README.md 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_size = 4 9 | indent_style = space 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.py] 14 | max_line_length = 100 15 | 16 | [*.{css,html,js,json,sass,scss,vue,yaml,yml}] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.md] 21 | indent_size = 4 22 | trim_trailing_whitespace = false 23 | -------------------------------------------------------------------------------- /.env-dist: -------------------------------------------------------------------------------- 1 | ALLOWED_HOSTS=* 2 | DATABASE_URL=postgres://postgres@db/postgres 3 | DJANGO_DEBUG=true 4 | -------------------------------------------------------------------------------- /.github/workflows/actions.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | 9 | env: 10 | DOCKER_BUILDKIT: "1" 11 | 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | name: Tests with Python 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 1 21 | path: ./src/github.com/${{ github.repository }}-git 22 | 23 | - name: Set up Python 3.12 24 | uses: actions/setup-python@v5 25 | with: 26 | python-version: "3.12" 27 | cache: "pip" 28 | 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install --upgrade pip uv 32 | 33 | - name: Create a project based on our settings 34 | run: | 35 | uv run --with=django \ 36 | django-admin startproject \ 37 | --extension=ini,py,toml,yaml,yml \ 38 | --template=./src/github.com/${{ github.repository }}-git/ \ 39 | test_project 40 | 41 | - name: Freeze our requirements 42 | run: | 43 | cd test_project 44 | uv pip compile requirements.in \ 45 | --output-file requirements.txt 46 | 47 | - name: Docker - Build image from starter project 48 | run: | 49 | cd test_project 50 | docker compose pull 51 | docker compose build 52 | 53 | - name: Docker - Test generated starter project 54 | run: | 55 | cd test_project 56 | docker compose run --rm utility pytest 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | cover/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | .pybuilder/ 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | # For a library or package, you might want to ignore these files since the code is 88 | # intended to run in multiple environments; otherwise, check them in: 89 | # .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 99 | __pypackages__/ 100 | 101 | # Celery stuff 102 | celerybeat-schedule 103 | celerybeat.pid 104 | 105 | # SageMath parsed files 106 | *.sage.py 107 | 108 | # Environments 109 | .env 110 | .venv 111 | env/ 112 | venv/ 113 | ENV/ 114 | env.bak/ 115 | venv.bak/ 116 | 117 | # Spyder project settings 118 | .spyderproject 119 | .spyproject 120 | 121 | # Rope project settings 122 | .ropeproject 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | 135 | # pytype static type analyzer 136 | .pytype/ 137 | 138 | # Cython debug symbols 139 | cython_debug/ 140 | 141 | # Custom compose options 142 | compose.override.yml 143 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_language_version: 2 | python: python3.12 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v5.0.0 6 | hooks: 7 | - id: check-added-large-files 8 | - id: check-case-conflict 9 | - id: check-json 10 | - id: check-merge-conflict 11 | - id: check-symlinks 12 | - id: check-toml 13 | - id: check-yaml 14 | - id: end-of-file-fixer 15 | - id: trailing-whitespace 16 | - repo: https://github.com/astral-sh/ruff-pre-commit 17 | rev: v0.11.9 18 | hooks: 19 | - id: ruff 20 | args: 21 | - --fix 22 | - id: ruff-format 23 | - repo: https://github.com/asottile/pyupgrade 24 | rev: v3.19.1 25 | hooks: 26 | - id: pyupgrade 27 | args: 28 | - --py312-plus 29 | - repo: https://github.com/adamchainz/django-upgrade 30 | rev: 1.24.0 31 | hooks: 32 | - id: django-upgrade 33 | args: 34 | - --target-version 35 | - '5.0' 36 | - repo: https://github.com/rtts/djhtml 37 | rev: 3.0.7 38 | hooks: 39 | - id: djhtml 40 | entry: djhtml --tabwidth 4 41 | alias: autoformat 42 | - repo: https://github.com/adamchainz/djade-pre-commit 43 | rev: "1.4.0" 44 | hooks: 45 | - id: djade 46 | args: [--target-version, "5.2"] 47 | - repo: https://github.com/asottile/blacken-docs 48 | rev: 1.19.1 49 | hooks: 50 | - id: blacken-docs 51 | alias: autoformat 52 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------ 2 | # Base/builder layer 3 | # ------------------------------------------------------------ 4 | 5 | FROM python:3.13-slim-bookworm AS builder 6 | 7 | ENV PIP_DISABLE_PIP_VERSION_CHECK 1 8 | ENV PYTHONDONTWRITEBYTECODE 1 9 | ENV PYTHONPATH /srv 10 | ENV PYTHONUNBUFFERED 1 11 | 12 | COPY requirements.txt /tmp/requirements.txt 13 | 14 | # add ",sharing=locked" if release should block until builder is complete 15 | RUN --mount=type=cache,target=/root/.cache,sharing=locked,id=pip \ 16 | python -m pip install --upgrade pip uv just-bin 17 | 18 | RUN --mount=type=cache,target=/root/.cache,sharing=locked,id=pip \ 19 | python -m uv pip install --system --requirement /tmp/requirements.txt 20 | 21 | # ------------------------------------------------------------ 22 | # Dev/testing layer 23 | # ------------------------------------------------------------ 24 | 25 | FROM builder AS release 26 | 27 | COPY . /src/ 28 | 29 | WORKDIR /src/ 30 | 31 | CMD ["python", "-m", "manage", "runserver", "--skip-checks", "0.0.0.0:8000"] 32 | 33 | # ------------------------------------------------------------ 34 | # TODO: Add Production notes 35 | # ------------------------------------------------------------ 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Jeff Triplett 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Welcome to django-startproject 👋

2 |

3 | 4 | CI 5 | 6 |

7 | 8 | > Django startproject template with batteries 9 | 10 | ## :triangular_flag_on_post: Core Features 11 | 12 | - Django 5.2 13 | - Python 3.13 14 | - Docker Compose (I prefer Orbstack) 15 | - Justfile recipes 16 | - Postgres auto updates 17 | - uv support 18 | - pre-commit support 19 | 20 | ## :triangular_flag_on_post: Django Features 21 | 22 | - django-click 23 | - environs[django] 24 | - psycopg[binary] 25 | - whitenoise 26 | 27 | ## :shirt: Linting/auto-formatting 28 | 29 | - djade 30 | - django-upgrade 31 | - djhtml 32 | - pre-commit 33 | - pyupgrade 34 | - ruff 35 | 36 | ### :green_heart: CI 37 | 38 | - django-test-plus 39 | - model-bakery 40 | - pytest 41 | - pytest-cov 42 | - pytest-django 43 | 44 | ### 🏠 [Homepage](https://github.com/jefftriplett/django-startproject) 45 | 46 | ## :wrench: Install 47 | 48 | ```shell 49 | $ uv run --with=django django-admin startproject \ 50 | --extension=ini,py,toml,yaml,yml \ 51 | --template=https://github.com/jefftriplett/django-startproject/archive/main.zip \ 52 | example_project 53 | 54 | $ cd example_project 55 | 56 | $ just bootstrap 57 | ``` 58 | 59 | ## :rocket: Usage 60 | 61 | ```shell 62 | # Bootstrap our project 63 | $ just bootstrap 64 | 65 | # Build our Docker Image 66 | $ just build 67 | 68 | # Run Migrations 69 | $ just manage migrate 70 | 71 | # Create a Superuser in Django 72 | $ just manage createsuperuser 73 | 74 | # Run Django on http://localhost:8000/ 75 | $ just up 76 | 77 | # Run Django in background mode 78 | $ just start 79 | 80 | # Stop all running containers 81 | $ just down 82 | 83 | # Open a bash shell/console 84 | $ just console 85 | 86 | # Run Tests 87 | $ just test 88 | 89 | # Lint the project / run pre-commit by hand 90 | $ just lint 91 | 92 | # Re-build PIP requirements 93 | $ just lock 94 | ``` 95 | 96 | ## `just` Commands 97 | 98 | ```shell 99 | $ just --list 100 | ``` 101 | 110 | ``` 111 | Available recipes: 112 | bootstrap *ARGS # Initialize project with dependencies and environment 113 | build *ARGS # Build Docker containers with optional args 114 | console # Open interactive bash console in utility container 115 | down *ARGS # Stop and remove containers, networks 116 | lint *ARGS # Run pre-commit hooks on all files 117 | lock *ARGS # Compile requirements.in to requirements.txt 118 | logs *ARGS # Show logs from containers 119 | manage *ARGS # Run Django management commands 120 | pg_dump file='db.dump' # Dump database to file 121 | pg_restore file='db.dump' # Restore database dump from file 122 | restart *ARGS # Restart containers 123 | run *ARGS # Run command in utility container 124 | start *ARGS="--detach" # Start services in detached mode by default 125 | stop *ARGS # Stop services (alias for down) 126 | tail # Show and follow logs 127 | test *ARGS # Run pytest with arguments 128 | up *ARGS # Start containers 129 | upgrade # Upgrade dependencies and lock 130 | ``` 131 | 132 | 133 | ## Author 134 | 135 | 👤 **Jeff Triplett** 136 | 137 | * Website: https://jefftriplett.com 138 | * Micro Blog: https://micro.webology.dev 139 | * Mastodon: [@webology@mastodon.social](https://mastodon.social/@webology) 140 | * Xwitter: [@webology](https://twitter.com/webology) 141 | * GitHub: [@jefftriplett](https://github.com/jefftriplett) 142 | * Hire me: [revsys](https://www.revsys.com) 143 | 144 | ## 🌟 Community Projects 145 | 146 | * [Django News Newsletter](https://django-news.com) 147 | * [Django News Jobs](https://jobs.django-news.com) 148 | * [Django Packages](https://djangopackages.org) 149 | * [DjangoCon US](https://djangocon.us) 150 | * [Awesome Django](https://awesomedjango.org) 151 | 152 | ## 🤝 Contributing 153 | 154 | Contributions, issues and feature requests are welcome!
Feel free to check [issues page](https://github.com/jefftriplett/django-startproject/issues). 155 | 156 | ## Show your support 157 | 158 | Give a ⭐️ if this project helped you! 159 | -------------------------------------------------------------------------------- /compose-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | 4 | python -m manage migrate --noinput --skip-checks 5 | 6 | python -m manage collectstatic --noinput --skip-checks 7 | 8 | exec "$@" 9 | -------------------------------------------------------------------------------- /compose.yml: -------------------------------------------------------------------------------- 1 | x-common-settings: &common-settings 2 | build: 3 | context: . 4 | dockerfile: ./Dockerfile 5 | target: release 6 | depends_on: 7 | db: 8 | condition: service_healthy 9 | environment: 10 | - "ALLOWED_HOSTS=*" 11 | - "DATABASE_URL=postgres://postgres@db/postgres" 12 | - "DJANGO_DEBUG=true" 13 | restart: on-failure 14 | volumes: 15 | - .:/src:cache 16 | 17 | services: 18 | 19 | db: 20 | environment: 21 | - "POSTGRES_HOST_AUTH_METHOD=trust" 22 | healthcheck: 23 | test: ["CMD-SHELL", "pg_isready", "-d", "postgres"] 24 | interval: 10s 25 | timeout: 3s 26 | retries: 3 27 | image: "pgautoupgrade/pgautoupgrade:latest" 28 | init: true 29 | volumes: 30 | - .:/src:cache 31 | - postgres-data:/var/lib/postgresql/data/ 32 | 33 | utility: 34 | <<: *common-settings 35 | tty: true 36 | 37 | web: 38 | <<: *common-settings 39 | command: ["python", "-m", "manage", "runserver", "--skip-checks", "0.0.0.0:8000"] 40 | entrypoint: ["/src/compose-entrypoint.sh"] 41 | init: true 42 | ports: 43 | - "8000:8000" 44 | tty: true 45 | 46 | volumes: 47 | postgres-data: 48 | -------------------------------------------------------------------------------- /config/__init__.py-tpl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefftriplett/django-startproject/f93b32ca95a1a0ce44f2b7f681c0a79d3d47158f/config/__init__.py-tpl -------------------------------------------------------------------------------- /config/settings.py-tpl: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for {{ project_name }} project. 3 | 4 | Generated by 'django-admin startproject' using Django {{ django_version }}. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/{{ docs_version }}/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/ 11 | """ 12 | 13 | from environs import env 14 | from pathlib import Path 15 | 16 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 17 | BASE_DIR = Path(__file__).resolve(strict=True).parent.parent 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/{{ docs_version }}/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = env.str( 24 | "SECRET_KEY", 25 | default="{{ secret_key }}", 26 | ) 27 | 28 | # SECURITY WARNING: don't run with debug turned on in production! 29 | DEBUG = env.bool("DJANGO_DEBUG", default=False) 30 | 31 | ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", default=[]) 32 | 33 | CSRF_TRUSTED_ORIGINS = env.list("CSRF_TRUSTED_ORIGINS", default=[]) 34 | 35 | # Application definition 36 | 37 | INSTALLED_APPS = [ 38 | "django.contrib.admin", 39 | "django.contrib.auth", 40 | "django.contrib.contenttypes", 41 | "django.contrib.messages", 42 | "django.contrib.sessions", 43 | "django.contrib.sites", 44 | "django.contrib.staticfiles", 45 | ] 46 | 47 | # Third-party apps 48 | 49 | INSTALLED_APPS += [] 50 | 51 | # Our apps 52 | 53 | INSTALLED_APPS += [] 54 | 55 | MIDDLEWARE = [ 56 | "django.middleware.security.SecurityMiddleware", 57 | "whitenoise.middleware.WhiteNoiseMiddleware", 58 | "django.contrib.sessions.middleware.SessionMiddleware", 59 | "django.middleware.common.CommonMiddleware", 60 | "django.middleware.csrf.CsrfViewMiddleware", 61 | "django.contrib.auth.middleware.AuthenticationMiddleware", 62 | "django.contrib.messages.middleware.MessageMiddleware", 63 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 64 | ] 65 | 66 | ROOT_URLCONF = "config.urls" 67 | 68 | TEMPLATES = [ 69 | { 70 | "BACKEND": "django.template.backends.django.DjangoTemplates", 71 | "DIRS": [ 72 | str(BASE_DIR.joinpath("templates")), 73 | ], 74 | "APP_DIRS": True, 75 | "OPTIONS": { 76 | "context_processors": [ 77 | "django.template.context_processors.debug", 78 | "django.template.context_processors.request", 79 | "django.contrib.auth.context_processors.auth", 80 | "django.contrib.messages.context_processors.messages", 81 | ], 82 | "debug": DEBUG, 83 | }, 84 | }, 85 | ] 86 | 87 | WSGI_APPLICATION = "config.wsgi.application" 88 | 89 | # Database 90 | # https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/#databases 91 | 92 | DATABASES = { 93 | "default": env.dj_db_url("DATABASE_URL", default="postgres:///{{ project_name }}"), 94 | } 95 | 96 | 97 | # Password validation 98 | # https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/#auth-password-validators 99 | 100 | AUTH_PASSWORD_VALIDATORS = [ 101 | { 102 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 103 | }, 104 | { 105 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 106 | }, 107 | { 108 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 109 | }, 110 | { 111 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 112 | }, 113 | ] 114 | 115 | # Internationalization 116 | # https://docs.djangoproject.com/en/{{ docs_version }}/topics/i18n/ 117 | 118 | LANGUAGE_CODE = "en-us" 119 | 120 | TIME_ZONE = "UTC" 121 | 122 | USE_I18N = True 123 | 124 | USE_TZ = True 125 | 126 | # Static files (CSS, JavaScript, Images) 127 | # https://docs.djangoproject.com/en/{{ docs_version }}/howto/static-files/ 128 | 129 | STATIC_ROOT = str(BASE_DIR.joinpath("static")) 130 | STATIC_URL = "/static/" 131 | STATICFILES_DIRS = (str(BASE_DIR.joinpath("frontend")),) 132 | # STORAGES = { 133 | # "staticfiles": { 134 | # "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage" 135 | # } 136 | # } 137 | 138 | MEDIA_URL = "/media/" 139 | MEDIA_ROOT = str(BASE_DIR.joinpath("media")) 140 | 141 | if DEBUG: 142 | EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" 143 | else: 144 | email = env.dj_email_url("EMAIL_URL", default="smtp://maildev") 145 | EMAIL_HOST = email["EMAIL_HOST"] 146 | EMAIL_HOST_PASSWORD = email["EMAIL_HOST_PASSWORD"] 147 | EMAIL_HOST_USER = email["EMAIL_HOST_USER"] 148 | EMAIL_PORT = email["EMAIL_PORT"] 149 | EMAIL_USE_TLS = email["EMAIL_USE_TLS"] 150 | 151 | # Parse cache URLS, e.g "redis://localhost:6379/0" 152 | CACHES = {"default": env.dj_cache_url("CACHE_URL", default="locmem://")} 153 | 154 | # Our settings 155 | 156 | ADMIN_URL = env.str("ADMIN_URL", default="admin/") 157 | 158 | SITE_ID = 1 159 | -------------------------------------------------------------------------------- /config/tests/test_file.py: -------------------------------------------------------------------------------- 1 | def test_equal(): 2 | assert 1 == 1 3 | -------------------------------------------------------------------------------- /config/urls.py-tpl: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls.static import static 3 | from django.contrib import admin 4 | from django.urls import path 5 | 6 | 7 | urlpatterns = [ 8 | path(settings.ADMIN_URL, admin.site.urls), 9 | ] 10 | 11 | if settings.DEBUG: 12 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 13 | -------------------------------------------------------------------------------- /config/wsgi.py-tpl: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for {{ project_name }} project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/{{ docs_version }}/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /conftest.py-tpl: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import logging 4 | 5 | import pytest 6 | 7 | 8 | logging.disable(logging.CRITICAL) 9 | 10 | 11 | @pytest.fixture(autouse=True) 12 | def use_test_settings(settings): 13 | settings.DEBUG = False 14 | 15 | settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" 16 | 17 | settings.MIDDLEWARE = [ 18 | middleware for middleware in settings.MIDDLEWARE if middleware != "whitenoise.middleware.WhiteNoiseMiddleware" 19 | ] 20 | 21 | # User a faster password hasher 22 | settings.PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"] 23 | 24 | settings.STORAGES = {"staticfiles": {"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"}} 25 | 26 | settings.WHITENOISE_AUTOREFRESH = True 27 | -------------------------------------------------------------------------------- /frontend/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefftriplett/django-startproject/f93b32ca95a1a0ce44f2b7f681c0a79d3d47158f/frontend/android-chrome-192x192.png -------------------------------------------------------------------------------- /frontend/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefftriplett/django-startproject/f93b32ca95a1a0ce44f2b7f681c0a79d3d47158f/frontend/android-chrome-512x512.png -------------------------------------------------------------------------------- /frontend/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefftriplett/django-startproject/f93b32ca95a1a0ce44f2b7f681c0a79d3d47158f/frontend/apple-touch-icon.png -------------------------------------------------------------------------------- /frontend/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefftriplett/django-startproject/f93b32ca95a1a0ce44f2b7f681c0a79d3d47158f/frontend/favicon-16x16.png -------------------------------------------------------------------------------- /frontend/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefftriplett/django-startproject/f93b32ca95a1a0ce44f2b7f681c0a79d3d47158f/frontend/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefftriplett/django-startproject/f93b32ca95a1a0ce44f2b7f681c0a79d3d47158f/frontend/favicon.ico -------------------------------------------------------------------------------- /frontend/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/static/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/static/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} 2 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | set dotenv-load := false 2 | 3 | # Show list of available commands 4 | @_default: 5 | just --list 6 | 7 | # Initialize project with dependencies and environment 8 | bootstrap *ARGS: 9 | #!/usr/bin/env bash 10 | set -euo pipefail 11 | 12 | if [ ! -f ".env" ]; then 13 | cp .env-dist .env 14 | echo ".env created" 15 | fi 16 | 17 | if [ -n "${VIRTUAL_ENV-}" ]; then 18 | python -m pip install --upgrade pip uv 19 | else 20 | echo "Skipping pip steps as VIRTUAL_ENV is not set" 21 | fi 22 | 23 | if [ ! -f "requirements.txt" ]; then 24 | uv pip compile --output-file requirements.txt requirements.in 25 | echo "requirements.txt created" 26 | fi 27 | 28 | just upgrade 29 | 30 | if [ -f "compose.yml" ]; then 31 | just build {{ ARGS }} --pull 32 | fi 33 | 34 | # Build Docker containers with optional args 35 | @build *ARGS: 36 | docker compose build {{ ARGS }} 37 | 38 | # Generate README content with cogapp 39 | [private] 40 | @cog: 41 | uv tool run --from cogapp cog -r README.md 42 | 43 | # Open interactive bash console in utility container 44 | @console: 45 | docker compose run \ 46 | --no-deps \ 47 | --rm \ 48 | utility /bin/bash 49 | 50 | # Stop and remove containers, networks 51 | @down *ARGS: 52 | docker compose down {{ ARGS }} 53 | 54 | # Format justfile with unstable formatter 55 | [private] 56 | @fmt: 57 | just --fmt --unstable 58 | 59 | # Run pre-commit hooks on all files 60 | @lint *ARGS: 61 | uv tool run --from pre-commit-uv pre-commit run {{ ARGS }} --all-files 62 | 63 | # Compile requirements.in to requirements.txt 64 | @lock *ARGS: 65 | docker compose run \ 66 | --no-deps \ 67 | --rm \ 68 | utility \ 69 | bash -c "uv pip compile {{ ARGS }} \ 70 | --output-file requirements.txt \ 71 | requirements.in" 72 | 73 | # Show logs from containers 74 | @logs *ARGS: 75 | docker compose logs {{ ARGS }} 76 | 77 | # Run Django management commands 78 | @manage *ARGS: 79 | docker compose run \ 80 | --no-deps \ 81 | --rm \ 82 | utility \ 83 | python -m manage {{ ARGS }} 84 | 85 | # Dump database to file 86 | @pg_dump file='db.dump': 87 | docker compose run \ 88 | --no-deps \ 89 | --rm \ 90 | db pg_dump \ 91 | --dbname "${DATABASE_URL:=postgres://postgres@db/postgres}" \ 92 | --file /src/{{ file }} \ 93 | --format=c \ 94 | --verbose 95 | 96 | # Restore database dump from file 97 | @pg_restore file='db.dump': 98 | docker compose run \ 99 | --no-deps \ 100 | --rm \ 101 | db pg_restore \ 102 | --clean \ 103 | --dbname "${DATABASE_URL:=postgres://postgres@db/postgres}" \ 104 | --if-exists \ 105 | --no-owner \ 106 | --verbose \ 107 | /src/{{ file }} 108 | 109 | # Restart containers 110 | @restart *ARGS: 111 | docker compose restart {{ ARGS }} 112 | 113 | # Run command in utility container 114 | @run *ARGS: 115 | docker compose run \ 116 | --no-deps \ 117 | --rm \ 118 | utility {{ ARGS }} 119 | 120 | # Start services in detached mode by default 121 | @start *ARGS="--detach": 122 | just up {{ ARGS }} 123 | 124 | # Stop services (alias for down) 125 | @stop *ARGS: 126 | just down {{ ARGS }} 127 | 128 | # Show and follow logs 129 | @tail: 130 | just logs --follow 131 | 132 | # Run pytest with arguments 133 | @test *ARGS: 134 | docker compose run \ 135 | --no-deps \ 136 | --rm \ 137 | utility python -m pytest {{ ARGS }} 138 | 139 | # Start containers 140 | @up *ARGS: 141 | docker compose up {{ ARGS }} 142 | 143 | # Upgrade dependencies and lock 144 | @upgrade: 145 | just lock --upgrade 146 | -------------------------------------------------------------------------------- /manage.py-tpl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | 4 | import os 5 | import sys 6 | 7 | 8 | def main(): 9 | """Run administrative tasks.""" 10 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") 11 | try: 12 | from django.core.management import execute_from_command_line 13 | except ImportError as exc: 14 | raise ImportError( 15 | "Couldn't import Django. Are you sure it's installed and " 16 | "available on your PYTHONPATH environment variable? Did you " 17 | "forget to activate a virtual environment?" 18 | ) from exc 19 | execute_from_command_line(sys.argv) 20 | 21 | 22 | if __name__ == "__main__": 23 | main() 24 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "{{ project_name }}" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.12" 7 | dependencies = [ 8 | "django<5.2", 9 | "django-click", 10 | "environs[django]", 11 | "psycopg[binary]", 12 | "whitenoise", 13 | ] 14 | 15 | [tool.black] 16 | target-version = ["py312"] 17 | 18 | [tool.coverage.run] 19 | omit = [ 20 | "*/admin.py", 21 | "*/manage.py", 22 | "*/migrations/*", 23 | "*/tests/*", 24 | "conftest.py", 25 | ] 26 | 27 | [tool.pytest.ini_options] 28 | DJANGO_SETTINGS_MODULE = "config.settings" 29 | addopts = "--cov --nomigrations --reuse-db" 30 | norecursedirs = ".git* frontend media static templates" 31 | python_files = "test_*.py" 32 | 33 | [tool.ruff] 34 | # Exclude a variety of commonly ignored directories. 35 | exclude = [ 36 | ".bzr", 37 | ".direnv", 38 | ".eggs", 39 | ".git", 40 | ".github", 41 | ".hg", 42 | ".ruff_cache", 43 | ".svn", 44 | ".tox", 45 | ".venv", 46 | "__pypackages__", 47 | "_build", 48 | "build", 49 | "dist", 50 | "migrations", 51 | "node_modules", 52 | "static", 53 | ] 54 | # Same as Black. 55 | line-length = 120 56 | # Assume Python 3.12. 57 | target-version = "py312" 58 | 59 | [tool.ruff.lint] 60 | # Allow unused variables when underscore-prefixed. 61 | dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" 62 | # Allow autofix for all enabled rules (when `--fix`) is provided. 63 | fixable = ["A", "B", "C", "D", "E", "F"] 64 | ignore = ["E501", "E741"] # temporary 65 | per-file-ignores = {} 66 | # Enable Pyflakes `E` and `F` codes by default. 67 | select = ["E", "F"] 68 | unfixable = [] 69 | -------------------------------------------------------------------------------- /requirements.in: -------------------------------------------------------------------------------- 1 | Django<6.0 2 | django-click 3 | environs[django] 4 | psycopg[binary] 5 | whitenoise 6 | 7 | django-test-plus 8 | model-bakery 9 | pre-commit 10 | pytest 11 | pytest-cov 12 | pytest-django 13 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by uv via the following command: 2 | # uv pip compile --output-file requirements.txt requirements.in 3 | asgiref==3.8.1 4 | # via django 5 | cfgv==3.4.0 6 | # via pre-commit 7 | click==8.2.0 8 | # via django-click 9 | coverage==7.8.0 10 | # via pytest-cov 11 | distlib==0.3.9 12 | # via virtualenv 13 | dj-database-url==2.3.0 14 | # via environs 15 | dj-email-url==1.0.6 16 | # via environs 17 | django==5.2.1 18 | # via 19 | # -r requirements.in 20 | # dj-database-url 21 | # model-bakery 22 | django-cache-url==3.4.5 23 | # via environs 24 | django-click==2.4.1 25 | # via -r requirements.in 26 | django-test-plus==2.2.4 27 | # via -r requirements.in 28 | environs==14.1.1 29 | # via -r requirements.in 30 | filelock==3.18.0 31 | # via virtualenv 32 | identify==2.6.10 33 | # via pre-commit 34 | iniconfig==2.1.0 35 | # via pytest 36 | marshmallow==4.0.0 37 | # via environs 38 | model-bakery==1.20.4 39 | # via -r requirements.in 40 | nodeenv==1.9.1 41 | # via pre-commit 42 | packaging==25.0 43 | # via pytest 44 | platformdirs==4.3.8 45 | # via virtualenv 46 | pluggy==1.5.0 47 | # via pytest 48 | pre-commit==4.2.0 49 | # via -r requirements.in 50 | psycopg==3.2.8 51 | # via -r requirements.in 52 | psycopg-binary==3.2.8 53 | # via psycopg 54 | pytest==8.3.5 55 | # via 56 | # -r requirements.in 57 | # pytest-cov 58 | # pytest-django 59 | pytest-cov==6.1.1 60 | # via -r requirements.in 61 | pytest-django==4.11.1 62 | # via -r requirements.in 63 | python-dotenv==1.1.0 64 | # via environs 65 | pyyaml==6.0.2 66 | # via pre-commit 67 | sqlparse==0.5.3 68 | # via django 69 | typing-extensions==4.13.2 70 | # via dj-database-url 71 | virtualenv==20.31.2 72 | # via pre-commit 73 | whitenoise==6.9.0 74 | # via -r requirements.in 75 | --------------------------------------------------------------------------------