diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..d3229ea6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,28 @@ +# http://editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[{,.}{j,J}ustfile] +indent_size = 4 + +[*.{py,rst,ini,md}] +indent_size = 4 + +[*.py] +line_length = 120 +multi_line_output = 3 + +[*.{css,html,js,json,jsx,sass,scss,svelte,ts,tsx,yml,yaml}] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +[{Makefile,*.bat}] +indent_style = tab diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 00000000..620689e0 --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,5 @@ +# Authors + +- Josh Thomas + +Special thanks to Jeff Triplett [@jefftriplett](https://github.com/jefftriplett) for his help. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..9ee79cd4 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,36 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + + + +## [Unreleased] + +## [2024.1] + +Initial release! 🎉 + +### Added + +- Initial project template. +- Initial documentation. +- Initial tests. +- Initial CI/CD (GitHub Actions). + +### New Contributors! + +- Josh Thomas (maintainer) + +[unreleased]: https://github.com/westerveltco/django-twc-project/compare/v2024.1...HEAD +[2024.1]: https://github.com/westerveltco/django-simple-nav/releases/tag/v2024.1 diff --git a/README.md b/README.md index 5ebc6c13..2193d084 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,90 @@ [![Rye](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/mitsuhiko/rye/main/artwork/badge.json)](https://rye-up.com) [![Copier](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/joshuadavidthomas/7c88611504b557ff7aa2a7524ad996e2/raw/4ba6834953dd8a14afc3dbb7bb41f49f181a59bf/badge.json)](https://copier.readthedocs.io) + +`django-twc-project` is the project template for all web applications at The Westervelt Company. This template is built on top of the [Django](https://www.djangoproject.com/) web framework and is designed to be a starting point for new web applications. It includes a number of best practices and tools to help you get started quickly. + +It is tailored to the needs of The Westervelt Company and is not intended for general use. However, it is open source and available for anyone take inspiration from or use and modify. [For the greater good!](https://youtu.be/5u8vd_YNbTw?si=lBqwaHdT8y8JUg9q) + +## Features + +This template is built using [Copier](https://copier.readthedocs.io) and includes the following features: + +- [Django](https://www.djangoproject.com/) + - [`django-allauth`](https://github.com/pennersr/django-allauth) for user authentication + - [`django-click`](https://github.com/GaretJax/django-click) for nicer management commands + - [`django-email-relay`](https://github.com/westerveltco/django-email-relay) for sending emails via a central database queue + - [`django-filter`](https://github.com/carltongibson/django-filter) for filtering querysets + - [`django-health-check`](https://github.com/revsys/django-health-check) for application, database, storage, and other health checks + - [`django-q2`](https://github.com/django-q/django-q) for a task queue, using the built-in database broker + - [`croniter`](https://github.com/kiorky/croniter) for cron tasks + - [`django-simple-history`](https://github.com/jazzband/django-simple-history) for tracking historical changes to models + - [`django-storages`](https://github.com/jschneier/django-storages) for file storage using S3 + - [`django-template-partials`](https://github.com/carltongibson/django-template-partials) for easy template partials + - [`environs`](https://github.com/sloria/environs) for configuration via environment variables + - [`heroicons`](https://github.com/adamchainz/heroicons) for easy access to [Heroicons](https://heroicons.com/) in Django templates + - [`httpx`](https://github.com/encode/httpx) for making HTTP requests + - [`neapolitan`](https://github.com/carltongibson/neapolitan) for quick and easy CRUD views + - [`sentry-sdk`](https://sentry.io) for error tracking + - [`whitenoise`](https://github.com/evansd/whitenoise) for serving static files from Django + - Also includes the following packages to make development easier: + - [`django-browser-reload`](https://github.com/adamchainz/django-browser-reload) for getting that HMR feeling in Django + - [`django-debug-toolbar`](https://github.com/jazzband/django-debug-toolbar), 'nuff said + - [`django-extensions`](https://github.com/django-extensions/django-extensions) for various management commands, but let's be honest -- it's mainly for `shell_plus` + - [`coverage`](https://github.com/nedbat/coveragepy) and [`django-coverage-plugin`](https://github.com/nedbat/django_coverage_plugin) for test coverage + - [`ipython`](https://github.com/ipython/ipython) for a better shell + - [`model_bakery`](https://github.com/model-bakers/model_bakery) for easy model creation in tests + - [`mypy`](https://github.com/python/mypy) and [`django-stubs`](https://github.com/typeddjango/django-stubs) for static type checking + - [`pytest`](https://github.com/pytest-dev/pytest) for testing + - [`pytest-django`](https://github.com/pytest-dev/pytest-django) for Django pytest helpers + - [`pytest-is-running`](https://github.com/adamchainz/pytest-is-running), what it says on the tin + - [`pytest-randomly`](https://github.com/pytest-dev/pytest-randomly) for keeping tests honest + - [`pytest-xdist`](https://github.com/pytest-dev/pytest-xdist) for parallel testing, because ain't nobody got time for a slow test suite +- [HTMX](https://htmx.org/) with [`django-htmx`](https://github.com/adamchainz/django-htmx) +- [Alpine.js](https://alpinejs.dev/) +- [Tailwind CSS](https://tailwindcss.com/) with [`django-tailwind-cli`](https://github.com/oliverandrich/django-tailwind-cli) +- (optional) [Vite](https://vitejs.dev/) with [`django-vite`](https://github.com/MrBin99/django-vite) +- Dependency management with [`pip-tools`](https://github.com/jazzband/pip-tools) + - [Dependabot](https://dependabot.com/) for automatic dependency updates +- Documentation built with [`Sphinx`](https://github.com/sphinx-doc/sphinx), [`MyST-Parser`](https://github.com/executablebooks/MyST-Parser), and the [`furo`](https://github.com/pradyunsg/furo) theme +- Automatic linting and formatting via [`pre-commit`](https://github.com/pre-commit/pre-commit) + - [`blacken-docs`](https://github.com/adamchainz/blacken-docs) because `ruff` doesn't + - [`django-upgrade`](https://github.com/adamchainz/django-upgrade) for keeping Django up to date automatically + - [`djlint`](https://github.com/rtts/djlint) for linting and formatting Django templates + - [`ruff`](https://github.com/astral-sh/ruff) for blazingly fast formatting and linting + - [`prettier`](https://github.com/prettier/prettier) for formatting CSS, JavaScript, TypeScript, and YAML + - [`rustywind`](https://github.com/avencera/rustywind) for sorting Tailwind CSS classes automatically + - [`validate-pyproject`](https://github.com/abravalheri/validate-pyproject) for ensuring that `pyproject.toml` is valid + - `pretty-format-toml` via [`language-formatters-pre-commit-hooks`](https://github.com/macisamuele/language-formatters-pre-commit-hooks) for TOML formatting +- [Docker](https://www.docker.com/) for local development and deployment + - A multi-stage `Dockerfile` to targeting different use cases (application/tailwind/vite/worker in development, full image in production) + - A `docker-compose.yml` file for local development, with a `docker-compose.prod.yml` to simulate production +- [`just`](https://github.com/casey/just) for running common development tasks +- Deployment to [Fly.io](https://fly.io) with a `fly.toml` file, with [`django-flyio`](https://github.com/joshuadavidthomas/django-flyio) giving some niceties specific to Fly +- CI/CD with [GitHub Actions](https://github.com/features/actions) + - Testing + - Type checking + - Django deployment checks + - Deploying to Fly.io + - [`bumpver`](https://github.com/mbarkhau/bumpver) for version bumping + - [1Password](https://1password.com) and the [`1password/load-secrets-action`](https://github.com/1password/load-secrets-action) to manage secrets + +## Usage + +To use this template, you will need to install [Copier](https://copier.readthedocs.io) and then run the following command: + +```bash +copier copy gh:westerveltco/django-twc-project +``` + +## Examples + +Examples are provided in the [`examples`](examples) directory. + +## Contributing + +As this template is mainly for internal use at The Westervelt Company, we do not generally accept contributions from external sources. However, if you have any suggestions or issues, please feel free to open an issue or pull request. + +## License + +`django-twc-project` is licensed under the MIT License. See the [LICENSE](LICENSE) file for more information. diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 00000000..e19e1fdd --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,65 @@ +# Releasing a New Version + +When it comes time to cut a new release, follow these steps: + +1. Create a new git branch off of `main` for the release. + + Prefer the convention `release-`, where `` is the next incremental version number (e.g. `release-v2024.1` for version 2024.1). + + ```shell + git checkout -b release-v + ``` + + However, the branch name is not *super* important, as long as it is not `main`. + +2. Update the version number across the project using the `bumpver` tool. + + The `pyproject.toml` in the base of the repository contains a `[tool.bumpver]` section that configures the `bumpver` tool to update the version number wherever it needs to be updated and to create a commit with the appropriate commit message. + + `bumpver` is included as a development dependency, so you should already have it installed if you have installed the development dependencies for this project. If you do not have the development dependencies installed, you can install them with the following command: + + ```shell + python -m pip install -r requirements-dev.lock + ``` + + Then, run `bumpver` to update the version number, with the appropriate command line arguments. See the [`bumpver` documentation](https://github.com/mbarkhau/bumpver) for more details. + + **Note**: For any of the following commands, you can add the command line flag `--dry` to preview the changes without actually making the changes. + + Here are the most common commands you will need to run: + + ```shell + bumpver update + ``` + + To release a tagged version, such as a beta or release candidate, you can run: + + ```shell + bumpver update --tag=beta + # or + bumpver update --tag=rc + ``` + + Running these commands on a tagged version will increment the tag appropriately, but will not increment the version number. + + To go from a tagged release to a full release, you can run: + + ```shell + bumpver update --tag=final + ``` + +3. Ensure the [CHANGELOG](https://github.com/westerveltco/django-twc-project/blob/main/CHANGELOG.md) is up to date. If updates are needed, add them now in the release branch. + +4. Create a pull request from the release branch to `main`. + +5. Once CI has passed and all the checks are green ✅, merge the pull request. + +6. Draft a [new release](https://github.com/westerveltco/django-twc-project/releases/new) on GitHub. + + Use the version number with a leading `v` as the tag name (e.g. `v2024.1`). + + Allow GitHub to generate the release title and release notes, using the 'Generate release notes' button above the text box. If this is a final release coming from a tagged release (or multiple tagged releases), make sure to copy the release notes from the previous tagged release(s) to the new release notes (after the release notes already generated for this final release). + + If this is a tagged release, make sure to check the 'Set as a pre-release' checkbox. + +7. Once you are satisfied with the release, publish the release. diff --git a/examples/default/fly.toml b/examples/default/fly.toml index bbb88d1f..631028a4 100644 --- a/examples/default/fly.toml +++ b/examples/default/fly.toml @@ -1,8 +1,8 @@ app = "default-example_owner" -primary_region = "atl" +console_command = "/app/manage.py shell_plus" kill_signal = "SIGINT" kill_timeout = "5s" -console_command = "/app/manage.py shell_plus" +primary_region = "atl" [deploy] release_command = "/release.sh" @@ -40,12 +40,12 @@ handlers = ["http"] port = 80 [[services.ports]] -port = 443 handlers = ["tls", "http"] +port = 443 [[services.tcp_checks]] -interval = "15s" grace_period = "1s" +interval = "15s" restart_limit = 0 timeout = "2s" diff --git a/examples/default/pyproject.toml b/examples/default/pyproject.toml index e48b3a90..0d9a797a 100644 --- a/examples/default/pyproject.toml +++ b/examples/default/pyproject.toml @@ -20,7 +20,7 @@ indent = 2 [tool.mypy] check_untyped_defs = true files = [ - "default", + "default" ] no_implicit_optional = true plugins = [ @@ -34,7 +34,7 @@ warn_unused_ignores = true ignore_errors = true module = [ "default.*.migrations.*", - "tests.*", + "tests.*" ] [[tool.mypy.overrides]] @@ -46,7 +46,7 @@ module = [ "django_q.*", "djclick.*", "gunicorn.*", - "health_check.*", + "health_check.*" ] [tool.mypy_django_plugin] diff --git a/examples/default/tests/conftest.py b/examples/default/tests/conftest.py index dbc17832..1d71bd8c 100644 --- a/examples/default/tests/conftest.py +++ b/examples/default/tests/conftest.py @@ -1,11 +1,10 @@ from __future__ import annotations import logging -import pytest +import pytest from django.test.utils import override_settings - pytest_plugins = [] # type: ignore diff --git a/examples/with_vite/fly.toml b/examples/with_vite/fly.toml index 69f8d462..665683ca 100644 --- a/examples/with_vite/fly.toml +++ b/examples/with_vite/fly.toml @@ -1,8 +1,8 @@ app = "with_vite-example_owner" -primary_region = "atl" +console_command = "/app/manage.py shell_plus" kill_signal = "SIGINT" kill_timeout = "5s" -console_command = "/app/manage.py shell_plus" +primary_region = "atl" [deploy] release_command = "/release.sh" @@ -40,12 +40,12 @@ handlers = ["http"] port = 80 [[services.ports]] -port = 443 handlers = ["tls", "http"] +port = 443 [[services.tcp_checks]] -interval = "15s" grace_period = "1s" +interval = "15s" restart_limit = 0 timeout = "2s" diff --git a/examples/with_vite/pyproject.toml b/examples/with_vite/pyproject.toml index 98816874..82cf526d 100644 --- a/examples/with_vite/pyproject.toml +++ b/examples/with_vite/pyproject.toml @@ -20,7 +20,7 @@ indent = 2 [tool.mypy] check_untyped_defs = true files = [ - "with_vite", + "with_vite" ] no_implicit_optional = true plugins = [ @@ -34,7 +34,7 @@ warn_unused_ignores = true ignore_errors = true module = [ "with_vite.*.migrations.*", - "tests.*", + "tests.*" ] [[tool.mypy.overrides]] @@ -46,7 +46,7 @@ module = [ "django_q.*", "djclick.*", "gunicorn.*", - "health_check.*", + "health_check.*" ] [tool.mypy_django_plugin] diff --git a/examples/with_vite/tests/conftest.py b/examples/with_vite/tests/conftest.py index dbc17832..1d71bd8c 100644 --- a/examples/with_vite/tests/conftest.py +++ b/examples/with_vite/tests/conftest.py @@ -1,11 +1,10 @@ from __future__ import annotations import logging -import pytest +import pytest from django.test.utils import override_settings - pytest_plugins = [] # type: ignore diff --git a/pyproject.toml b/pyproject.toml index a0b392ab..4af99645 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,13 +49,13 @@ testpaths = ["tests"] [tool.rye] dev-dependencies = [ - "bumpver>=2023.1129", - "djlint>=1.34.1", - "pytest>=8.0.0", - "pytest-xdist>=3.5.0", - "pytest-randomly>=3.15.0", - "pytest-reverse>=1.7.0", - "pyyaml>=6.0.1", + "bumpver>=2023.1129", + "djlint>=1.34.1", + "pytest>=8.0.0", + "pytest-xdist>=3.5.0", + "pytest-randomly>=3.15.0", + "pytest-reverse>=1.7.0", + "pyyaml>=6.0.1" ] managed = true virtual = true diff --git a/tests/test_version.py b/tests/test_version.py index 0d356053..12050d2e 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -6,6 +6,7 @@ BASE_DIR = Path(__file__).parent.parent + def test_copier_yml_version(): file = BASE_DIR / "copier.yml" with open(file, encoding="utf-8") as f: @@ -14,7 +15,7 @@ def test_copier_yml_version(): version = copier_yml["template_version"]["default"] assert version == "2024.1" - + def test_pyproject_toml_version(): file = BASE_DIR / "pyproject.toml"