From 541076757197d1810c744af03515e3265f2b0160 Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 19 Nov 2025 18:15:52 +0100 Subject: [PATCH 01/13] Improved django visibility --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ceeb66b..b172093 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# N&S python project/library cookiecutter template +# N&S python/django project cookiecutter template Template for [cookiecutter](https://cookiecutter.readthedocs.io) so that you can create a fresh plain python project or library. @@ -14,7 +14,7 @@ Run the following command and answer the questions: $ cookiecutter https://github.com/nens/cookiecutter-python-template -This cookiecutter can also generate a django project for you: +This cookiecutter can also generate a **django** project for you: $ cookiecutter --directory django https://github.com/nens/cookiecutter-python-template From c6411883bd7582055e680e212974dbc3ed3a1a29 Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 19 Nov 2025 18:21:39 +0100 Subject: [PATCH 02/13] Copied files from the climatescan project --- .../Dockerfile | 27 ++++++++ .../ansible/deploy.yml | 61 +++++++++++++++++++ .../ansible/inventory.txt | 6 ++ .../ansible/provision.yml | 37 +++++++++++ 4 files changed, 131 insertions(+) create mode 100644 django/{{ cookiecutter.project_name }}/Dockerfile create mode 100644 django/{{ cookiecutter.project_name }}/ansible/deploy.yml create mode 100644 django/{{ cookiecutter.project_name }}/ansible/inventory.txt create mode 100644 django/{{ cookiecutter.project_name }}/ansible/provision.yml diff --git a/django/{{ cookiecutter.project_name }}/Dockerfile b/django/{{ cookiecutter.project_name }}/Dockerfile new file mode 100644 index 0000000..3773ac7 --- /dev/null +++ b/django/{{ cookiecutter.project_name }}/Dockerfile @@ -0,0 +1,27 @@ +FROM python:3.13 + +# Install GDAL dependencies +RUN apt-get update && apt-get install -y \ + libgdal-dev \ + libsqlite3-mod-spatialite +# Set the environment variables for GDAL +ENV CPLUS_INCLUDE_PATH=/usr/include/gdal +ENV C_INCLUDE_PATH=/usr/include/gdal + +# uv installation +COPY --from=ghcr.io/astral-sh/uv:0.8.17 /uv /usr/local/bin/uv +ENV UV_LINK_MODE=copy \ + UV_PYTHON_DOWNLOADS=never \ + UV_PYTHON=python3.13 + +WORKDIR /code +COPY pyproject.toml uv.lock /code/ +RUN uv sync --no-install-project + +# We only need to copy the src/ dir. This ensures the /code/staticfiles (filled below) +# is totally left alone. +COPY src src +RUN uv sync +ENV PATH="/code/.venv/bin:$PATH" + +RUN python src/manage.py collectstatic --noinput --clear diff --git a/django/{{ cookiecutter.project_name }}/ansible/deploy.yml b/django/{{ cookiecutter.project_name }}/ansible/deploy.yml new file mode 100644 index 0000000..66746a4 --- /dev/null +++ b/django/{{ cookiecutter.project_name }}/ansible/deploy.yml @@ -0,0 +1,61 @@ +# Goal: do the day-to-day deployment of the site. This is done as the +# 'buildout' user, not as root. At least, we're going to try to do it like +# this. You need to have an ssh key configured to log in with: this is handled +# in the provision.yml playbook. + +--- +- name: Deploy new version of the project setup to the server + hosts: web + remote_user: buildout + tasks: + + - name: Checkout correct version from github + ansible.builtin.git: + accept_hostkey: true + dest: /srv/{{ site_name }} + repo: ssh://git@github.com/nens/{{ cookiecutter.project_name }}.git + version: "{{ checkout_name }}" + + - name: Link server docker-compose file + ansible.builtin.file: + state: link + src: /srv/{{ site_name }}/docker-compose.server.yml + dest: /srv/{{ site_name }}/docker-compose.yml + # docker-compose files aren't included in the cookiecutter template. + # Look at (for instance) the "climatescan" project. + + - name: Grab github docker token from server + ansible.builtin.command: # noqa: no-changed-when + cmd: "cat ~/.github-docker-pull-token.txt" + register: token + + - name: Log into github package repo + community.docker.docker_login: + registry: ghcr.io + username: nensbuildout + password: "{{ token.stdout }}" + + - name: Build our own docker + ansible.builtin.shell: # noqa: command-instead-of-shell no-changed-when + cmd: "docker-compose build" + chdir: /srv/{{ site_name }} + + - name: Pull dockers + ansible.builtin.shell: # noqa: command-instead-of-shell no-changed-when + cmd: "docker-compose pull" + chdir: /srv/{{ site_name }} + + - name: Stop dockers + ansible.builtin.shell: # noqa: command-instead-of-shell no-changed-when + cmd: "docker-compose down" + chdir: /srv/{{ site_name }} + + - name: Start/update dockers + ansible.builtin.shell: # noqa: command-instead-of-shell no-changed-when + cmd: "docker-compose up -d" + chdir: /srv/{{ site_name }} + + - name: Run migrate + ansible.builtin.command: # noqa: no-changed-when + cmd: "docker-compose run --rm web python src/manage.py migrate --noinput" + chdir: /srv/{{ site_name }} diff --git a/django/{{ cookiecutter.project_name }}/ansible/inventory.txt b/django/{{ cookiecutter.project_name }}/ansible/inventory.txt new file mode 100644 index 0000000..25fb257 --- /dev/null +++ b/django/{{ cookiecutter.project_name }}/ansible/inventory.txt @@ -0,0 +1,6 @@ +[web] +YOUR-SERVER + +[web:vars] +site_name=TODO.lizard.net +checkout_name="main" diff --git a/django/{{ cookiecutter.project_name }}/ansible/provision.yml b/django/{{ cookiecutter.project_name }}/ansible/provision.yml new file mode 100644 index 0000000..3d3bfab --- /dev/null +++ b/django/{{ cookiecutter.project_name }}/ansible/provision.yml @@ -0,0 +1,37 @@ +--- +- name: Provision server to prepare for the actual deployment + hosts: web + remote_user: provisioner + become: true + tasks: + + - name: Install various packages + ansible.builtin.apt: + name: + - docker.io + - docker-compose + state: present + + - name: Add user 'buildout' and disable its password + ansible.builtin.user: + name: buildout + password: '*' + state: present + shell: "/bin/bash" + groups: docker + + - name: Add maintainers' ssh keys so they can log in as user buildout. + ansible.posix.authorized_key: + user: buildout + key: https://github.com/{{ item }}.keys + with_items: + - reinout + # TODO: add more maintainers + + - name: Create dir inside /srv for the checkout + ansible.builtin.file: + path: /srv/{{ site_name }} + state: directory + mode: "0755" + owner: buildout + group: buildout From 801b90524f223f2cba2a80dff24e964dce4b7a7d Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 19 Nov 2025 18:40:55 +0100 Subject: [PATCH 03/13] Copied files from the climatescan project --- .../pyproject.toml | 19 ++- .../src/manage.py | 4 +- .../settings.py | 155 ++++++++++++++++-- .../{{ cookiecutter.package_name }}/wsgi.py | 4 +- 4 files changed, 155 insertions(+), 27 deletions(-) diff --git a/django/{{ cookiecutter.project_name }}/pyproject.toml b/django/{{ cookiecutter.project_name }}/pyproject.toml index dd4b67c..aadf82e 100644 --- a/django/{{ cookiecutter.project_name }}/pyproject.toml +++ b/django/{{ cookiecutter.project_name }}/pyproject.toml @@ -17,14 +17,17 @@ classifiers = ["Programming Language :: Python"] keywords = [] requires-python = ">=3.12" dependencies = [ - "django-types>=0.22.0", - "django==5.2.*", - "djangorestframework-gis>=1.2.0", - "djangorestframework-types>=0.9.0", - "djangorestframework>=3.16.1", - "nens-auth-client>=1.6.1", - "psycopg2-binary>=2.9.10", - "whitenoise>=6.10.0", + "django-types>=0.22.0", + "django==5.2.*", + "djangorestframework>=3.16.1", + "djangorestframework-dataclasses>=1.4.0", + "djangorestframework-gis>=1.2.0", + "djangorestframework-types>=0.9.0", + "drf-spectacular[sidecar]>=0.28.0", + "gdal==3.8.4", # 3.8.4 is the version in 24.04. Works with newer libs. + "psycopg2-binary>=2.9.10", + "sentry-sdk[django]>=2.38.0", + "whitenoise>=6.10.0", ] [project.urls] diff --git a/django/{{ cookiecutter.project_name }}/src/manage.py b/django/{{ cookiecutter.project_name }}/src/manage.py index 01ed797..608fe36 100755 --- a/django/{{ cookiecutter.project_name }}/src/manage.py +++ b/django/{{ cookiecutter.project_name }}/src/manage.py @@ -7,7 +7,9 @@ def main(): """Run administrative tasks.""" - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ cookiecutter.package_name }}.settings") + os.environ.setdefault( + "DJANGO_SETTINGS_MODULE", "{{ cookiecutter.package_name }}.settings" + ) try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/django/{{ cookiecutter.project_name }}/src/{{ cookiecutter.package_name }}/settings.py b/django/{{ cookiecutter.project_name }}/src/{{ cookiecutter.package_name }}/settings.py index a0fa726..1a479db 100644 --- a/django/{{ cookiecutter.project_name }}/src/{{ cookiecutter.package_name }}/settings.py +++ b/django/{{ cookiecutter.project_name }}/src/{{ cookiecutter.package_name }}/settings.py @@ -10,41 +10,112 @@ https://docs.djangoproject.com/en/5.2/ref/settings/ """ +import os from pathlib import Path +import sentry_sdk + # BASE_DIR is the root of the project (with the 'pyproject.toml'). BASE_DIR = Path(__file__).resolve().parent.parent.parent +MEDIA_ROOT = BASE_DIR / "var" / "media" +DB_ROOT = BASE_DIR / "var" / "db" +# Make sure they exist +MEDIA_ROOT.mkdir(exist_ok=True, parents=True) +DB_ROOT.mkdir(exist_ok=True, parents=True) -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ +# Environment variables, used in the docker-compose.yml +_debug_env = os.getenv("DEBUG", default="true") +DEBUG = _debug_env.lower() == "true" # default: True +DATABASE_HOST = os.getenv("DATABASE_HOST", "local") # p-service-db-01.nens +SENTRY_DSN = os.getenv("SENTRY_DSN") # Not required, only used in staging/production. +EMAIL_HOST = os.getenv("EMAIL_HOST", default="") # SECURITY WARNING: keep the secret key used in production secret! # Generate one with "uv run python": # # from django.core.management.utils import get_random_secret_key # print(get_random_secret_key()) -SECRET_KEY = "django-insecure-tqaiwwkc!13a6t)_9e1eqwu1qeolix_sel0c@w%d00bcssjqo@" +SECRET_KEY = os.getenv( + "SECRET_KEY", + default="change-this-see-above-or-pay-my-beer-on-friday", +) + +ALLOWED_HOSTS = [ + ".actual-domain.todo", + "localhost", + "127.0.0.1", +] -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True -ALLOWED_HOSTS = [] +CSRF_TRUSTED_ORIGINS = [ + "https://actual-domain.todo", + "http://localhost:8000", + "http://localhost:4200", + "http://localhost:7772", +] +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "verbose": {"format": "%(asctime)s %(name)s %(levelname)s\n %(message)s"}, + "simple": {"format": "%(levelname)s %(name)s %(message)s"}, + }, + "handlers": { + "null": {"level": "DEBUG", "class": "logging.NullHandler"}, + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "simple" if DEBUG else "verbose", + }, + }, + "loggers": { + "root": { + "handlers": ["console"], + "level": "DEBUG" if DEBUG else "INFO", + "propagate": True, + "disable_existing_loggers": False, + }, + "django.db.backends": { + "handlers": ["console"], + "propagate": False, + "level": "INFO", # Set to DEBUG to show sql queries + }, + "urllib3": { + "handlers": ["console"], + "propagate": False, + "level": "INFO", # Set to DEBUG to show all URL requests to lizard + }, + "django.request": { + "handlers": ["console"], + "propagate": False, + "level": "INFO" if DEBUG else "ERROR", # WARN also shows 404 errors + }, + }, +} # Application definition INSTALLED_APPS = [ + "{{ cookiecutter.package_name }}", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", + "django.contrib.sites", "django.contrib.messages", + "django.contrib.gis", "django.contrib.staticfiles", + "rest_framework", + "rest_framework_gis", + "drf_spectacular", + "drf_spectacular_sidecar", ] MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", + "whitenoise.middleware.WhiteNoiseMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", @@ -62,6 +133,7 @@ "APP_DIRS": True, "OPTIONS": { "context_processors": [ + "django.template.context_processors.debug", "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", @@ -76,12 +148,25 @@ # Database # https://docs.djangoproject.com/en/5.2/ref/settings/#databases -DATABASES = { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": BASE_DIR / "db.sqlite3", +if DATABASE_HOST != "local": # pragma: no cover + # Normal situation + DATABASES = { + "default": { + "ENGINE": "django.contrib.gis.db.backends.postgis", + "NAME": "{{ cookiecutter.package_name }}", + "USER": "{{ cookiecutter.package_name }}", + "PASSWORD": "TODO, see Reinout's docs", + "HOST": DATABASE_HOST, + } + } +else: + # Local development + DATABASES = { + "default": { + "ENGINE": "django.contrib.gis.db.backends.spatialite", + "NAME": DB_ROOT / "{{ cookiecutter.package_name }}.db", + } } -} # Password validation @@ -101,17 +186,12 @@ "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] - - # Internationalization # https://docs.djangoproject.com/en/5.2/topics/i18n/ LANGUAGE_CODE = "en-us" - TIME_ZONE = "UTC" - USE_I18N = True - USE_TZ = True @@ -119,8 +199,49 @@ # https://docs.djangoproject.com/en/5.2/howto/static-files/ STATIC_URL = "static/" +STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") +STORAGES = { + "staticfiles": { + "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage", + }, +} + +if SENTRY_DSN: # pragma: no cover + # SENTRY_DSN will only be set on staging/production, btw. + sentry_sdk.init(dsn=SENTRY_DSN) + +if EMAIL_HOST: # pragma: no cover + EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" +else: + EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" + +DEFAULT_FROM_EMAIL = "{{ cookiecutter.package_name }} " # Default primary key field type # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field - DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +# DRF + api docs +REST_FRAMEWORK = { + "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", + "DEFAULT_AUTHENTICATION_CLASSES": [ + # Use django's regular login mechanism, that's fine for a javascript frontend + # running on the same url. + "rest_framework.authentication.SessionAuthentication", + ], + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", + "PAGE_SIZE": 100, +} + +SPECTACULAR_SETTINGS = { + "SWAGGER_UI_DIST": "SIDECAR", # shorthand to use the sidecar instead + "SWAGGER_UI_FAVICON_HREF": "SIDECAR", + "REDOC_DIST": "SIDECAR", +} + + +# You probably have to pass a maptiler api key to the frontend +# MAPTILER_API_KEY = "TODO" + +# Understand that we're behind a https-terminating haproxy. +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") diff --git a/django/{{ cookiecutter.project_name }}/src/{{ cookiecutter.package_name }}/wsgi.py b/django/{{ cookiecutter.project_name }}/src/{{ cookiecutter.package_name }}/wsgi.py index 45fac19..3d510e8 100644 --- a/django/{{ cookiecutter.project_name }}/src/{{ cookiecutter.package_name }}/wsgi.py +++ b/django/{{ cookiecutter.project_name }}/src/{{ cookiecutter.package_name }}/wsgi.py @@ -11,6 +11,8 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ cookiecutter.package_name }}.settings") +os.environ.setdefault( + "DJANGO_SETTINGS_MODULE", "{{ cookiecutter.package_name }}.settings" +) application = get_wsgi_application() From 363aa56443e576f437438d9f7032edef0516dfa6 Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 19 Nov 2025 18:44:36 +0100 Subject: [PATCH 04/13] updates --- {{ cookiecutter.project_name }}/.pre-commit-config.yaml | 9 ++++++++- {{ cookiecutter.project_name }}/README.md | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/{{ cookiecutter.project_name }}/.pre-commit-config.yaml b/{{ cookiecutter.project_name }}/.pre-commit-config.yaml index ef4bfc3..fb8e040 100644 --- a/{{ cookiecutter.project_name }}/.pre-commit-config.yaml +++ b/{{ cookiecutter.project_name }}/.pre-commit-config.yaml @@ -14,12 +14,19 @@ repos: - id: check-added-large-files - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.12.12 + rev: v0.14.1 hooks: # Run the linter. - id: ruff args: ["--fix"] # Run the formatter. - id: ruff-format + - repo: https://github.com/tombi-toml/tombi-pre-commit + rev: v0.6.37 + hooks: + - id: tombi-format + args: ["--offline"] + - id: tombi-lint + args: ["--offline"] ### Extra lines below are preserved ### diff --git a/{{ cookiecutter.project_name }}/README.md b/{{ cookiecutter.project_name }}/README.md index 0de4233..5e8f98a 100644 --- a/{{ cookiecutter.project_name }}/README.md +++ b/{{ cookiecutter.project_name }}/README.md @@ -54,3 +54,5 @@ If you need a new dependency (like `requests`), add it in - Update this readme. - Remove this section as you've done it all :-) + +- Look at the `climatescan` project for docker-compose examples. From 1392a2b9890571df38ac6aef5f3eeee7f1d19c85 Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 19 Nov 2025 18:56:10 +0100 Subject: [PATCH 05/13] Escaped most of the jinja2 --- django/{{ cookiecutter.project_name }}/ansible/deploy.yml | 5 +++-- django/{{ cookiecutter.project_name }}/ansible/provision.yml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/django/{{ cookiecutter.project_name }}/ansible/deploy.yml b/django/{{ cookiecutter.project_name }}/ansible/deploy.yml index 66746a4..9c0fa92 100644 --- a/django/{{ cookiecutter.project_name }}/ansible/deploy.yml +++ b/django/{{ cookiecutter.project_name }}/ansible/deploy.yml @@ -12,8 +12,8 @@ - name: Checkout correct version from github ansible.builtin.git: accept_hostkey: true - dest: /srv/{{ site_name }} - repo: ssh://git@github.com/nens/{{ cookiecutter.project_name }}.git + dest: {% raw %}/srv/{{ site_name }}{% endraw %} + repo: ssh://git@github.com/nens/{{ cookiecutter.project_name }}.git{% raw %} version: "{{ checkout_name }}" - name: Link server docker-compose file @@ -59,3 +59,4 @@ ansible.builtin.command: # noqa: no-changed-when cmd: "docker-compose run --rm web python src/manage.py migrate --noinput" chdir: /srv/{{ site_name }} +{% endraw %} diff --git a/django/{{ cookiecutter.project_name }}/ansible/provision.yml b/django/{{ cookiecutter.project_name }}/ansible/provision.yml index 3d3bfab..9712a6e 100644 --- a/django/{{ cookiecutter.project_name }}/ansible/provision.yml +++ b/django/{{ cookiecutter.project_name }}/ansible/provision.yml @@ -23,7 +23,7 @@ - name: Add maintainers' ssh keys so they can log in as user buildout. ansible.posix.authorized_key: user: buildout - key: https://github.com/{{ item }}.keys + key: {% raw %}https://github.com/{{ item }}.keys{% endraw %} with_items: - reinout # TODO: add more maintainers From d2a0ad4aca67c833ce205fcb6e0fb4d18124b97a Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 19 Nov 2025 18:56:24 +0100 Subject: [PATCH 06/13] tombi reformat --- pyproject.toml | 72 ++++++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 967d894..fd4ef30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,40 +1,49 @@ [project] name = "cookiecutter-python-template" -authors = [ - {name = "Reinout van Rees", email = "reinout@vanrees.org"}, - {name = "Nelen & Schuurmans", email = "info@nelen-schuurmans.nl"}, -] +version = "0.9.dev0" description = "Cookiecutter template for a plain python project or library" readme = "README.md" -license = "MIT" -version = "0.9.dev0" requires-python = ">=3.12" +license = "MIT" +authors = [ + { name = "Reinout van Rees", email = "reinout@vanrees.org" }, + { name = "Nelen & Schuurmans", email = "info@nelen-schuurmans.nl" }, +] classifiers = [ - "Development Status :: 4 - Beta", - "Environment :: Console", - "Intended Audience :: Developers", - "Natural Language :: English", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - "Topic :: Software Development", - ] -dependencies = [ - "cookiecutter" + "Development Status :: 4 - Beta", + "Environment :: Console", + "Intended Audience :: Developers", + "Natural Language :: English", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Software Development", ] +dependencies = ["cookiecutter"] [project.urls] homepage = "https://github.com/nens/cookiecutter-python-template" +[dependency-groups] +dev = [ + "pre-commit>=4.3.0", + "pytest>=8.4.2", + "pytest-sugar>=1.1.1", +] + +[tool.pyright] +# Pyright/pylance/vscode configuration. +# Note: if you want a different setup, you can overwrite this with a +# "pyrightconfig.json", which takes precedence. +include = "src" +venvPath = "." +venv = ".venv" + [tool.pytest.ini_options] -norecursedirs="{{ cookiecutter.project_name }}" +norecursedirs = "{{ cookiecutter.project_name }}" addopts = "tests/" -[tool.zest-releaser] -# We don't need releasing as a package. -release = false - [tool.ruff] exclude = ["*cookiecutter.project_name*"] target-version = "py312" @@ -42,17 +51,6 @@ target-version = "py312" [tool.ruff.lint] select = ["E4", "E7", "E9", "F", "I", "UP", "C901"] -[tool.pyright] -# Pyright/pylance/vscode configuration. -# Note: if you want a different setup, you can overwrite this with a -# "pyrightconfig.json", which takes precedence. -include = "src" -venvPath = "." -venv = ".venv" - -[dependency-groups] -dev = [ - "pre-commit>=4.3.0", - "pytest>=8.4.2", - "pytest-sugar>=1.1.1", -] +[tool.zest-releaser] +# We don't need releasing as a package. +release = false From 8433ff96515b65b3afc0ed0305032de3c6ce0caa Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 19 Nov 2025 18:57:20 +0100 Subject: [PATCH 07/13] tombi reformat --- .../pyproject.toml | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/{{ cookiecutter.project_name }}/pyproject.toml b/{{ cookiecutter.project_name }}/pyproject.toml index 0280198..1cdbd34 100644 --- a/{{ cookiecutter.project_name }}/pyproject.toml +++ b/{{ cookiecutter.project_name }}/pyproject.toml @@ -1,34 +1,44 @@ -# See https://nens-meta.readthedocs.io/en/latest/config-files.html -[build-system] -requires = ["setuptools>=80", "setuptools-scm>=8"] -build-backend = "setuptools.build_meta" - [project] name = "{{cookiecutter.project_name}}" version = "0.1.dev0" description = "{{cookiecutter.project_short_description}}" -authors = [ - {name = "Nelen & Schuurmans", email = "info@nelen-schuurmans.nl"}, -] readme = "README.md" +requires-python = ">=3.12" license = "MIT" +authors = [ + { name = "Nelen & Schuurmans", email = "info@nelen-schuurmans.nl" }, +] +keywords = [] # Get classifier strings from http://www.python.org/pypi?%3Aaction=list_classifiers classifiers = ["Programming Language :: Python"] -keywords = [] -requires-python = ">=3.12" dependencies = [] [project.urls] homepage = "https://github.com/nens/{{ cookiecutter.project_name }}" +[dependency-groups] +# The "dev" group is automatically installed by uv. When using pip, +# do something like "pip install -e . --group test". +dev = ["pytest>=8.4.2", "pytest-cov>=6.3.0", "pytest-sugar>=1.1.1"] + +# See https://nens-meta.readthedocs.io/en/latest/config-files.html +[build-system] +requires = ["setuptools>=80", "setuptools-scm>=8"] +build-backend = "setuptools.build_meta" + +[tool.pyright] +# Pyright/pylance/vscode configuration. +# Note: if you want a different setup, you can overwrite this with a +# "pyrightconfig.json", which takes precedence. +include = "src" +venvPath = "." +venv = ".venv" + [tool.pytest.ini_options] addopts = "--cov --cov-fail-under=80" # Debugging tests in vscode? Run pytest with "--no-cov", see # https://pytest-cov.readthedocs.io/en/latest/debuggers.html -[tool.zest-releaser] -release = false - [tool.ruff] # See https://docs.astral.sh/ruff/configuration/ for defaults. target-version = "py312" @@ -40,15 +50,5 @@ select = ["E4", "E7", "E9", "F", "I", "UP", "C901"] [tool.ruff.lint.isort] known-first-party = ["{{ cookiecutter.package_name }}"] -[tool.pyright] -# Pyright/pylance/vscode configuration. -# Note: if you want a different setup, you can overwrite this with a -# "pyrightconfig.json", which takes precedence. -include = "src" -venvPath = "." -venv = ".venv" - -[dependency-groups] -# The "dev" group is automatically installed by uv. When using pip, -# do something like "pip install -e . --group test". -dev = ["pytest>=8.4.2", "pytest-cov>=6.3.0", "pytest-sugar>=1.1.1"] +[tool.zest-releaser] +release = false From 725eddd08c91548a98eb35f7d86fa7b152dd22f5 Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 19 Nov 2025 18:57:35 +0100 Subject: [PATCH 08/13] tombi reformat --- .../pyproject.toml | 57 ++++++++++--------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/django/{{ cookiecutter.project_name }}/pyproject.toml b/django/{{ cookiecutter.project_name }}/pyproject.toml index aadf82e..bd75867 100644 --- a/django/{{ cookiecutter.project_name }}/pyproject.toml +++ b/django/{{ cookiecutter.project_name }}/pyproject.toml @@ -1,24 +1,19 @@ -# See https://nens-meta.readthedocs.io/en/latest/config-files.html -[build-system] -requires = ["setuptools>=80", "setuptools-scm>=8"] -build-backend = "setuptools.build_meta" - [project] name = "{{cookiecutter.project_name}}" version = "0.1.dev0" description = "{{cookiecutter.project_short_description}}" -authors = [ - {name = "Nelen & Schuurmans", email = "info@nelen-schuurmans.nl"}, -] readme = "README.md" +requires-python = ">=3.12" license = "MIT" +authors = [ + { name = "Nelen & Schuurmans", email = "info@nelen-schuurmans.nl" }, +] +keywords = [] # Get classifier strings from http://www.python.org/pypi?%3Aaction=list_classifiers classifiers = ["Programming Language :: Python"] -keywords = [] -requires-python = ">=3.12" dependencies = [ - "django-types>=0.22.0", "django==5.2.*", + "django-types>=0.22.0", "djangorestframework>=3.16.1", "djangorestframework-dataclasses>=1.4.0", "djangorestframework-gis>=1.2.0", @@ -33,14 +28,29 @@ dependencies = [ [project.urls] homepage = "https://github.com/nens/{{ cookiecutter.project_name }}" -[tool.pytest.ini_options] -addopts = "--cov --cov-fail-under=80" +[dependency-groups] +dev = [ + "pytest>=8.4.2", + "pytest-cov>=6.3.0", + "pytest-django>=4.11", + "pytest-sugar>=1.1.1" +] -[[tool.uv.index]] -url = "https://packages.lizard.net/" +# See https://nens-meta.readthedocs.io/en/latest/config-files.html +[build-system] +requires = ["setuptools>=80", "setuptools-scm>=8"] +build-backend = "setuptools.build_meta" -[tool.zest-releaser] -release = false +[tool.pyright] +# Pyright/pylance/vscode configuration. +# Note: if you want a different setup, you can overwrite this with a +# "pyrightconfig.json", which takes precedence. +include = "src" +venvPath = "." +venv = ".venv" + +[tool.pytest.ini_options] +addopts = "--cov --cov-fail-under=80" [tool.ruff] # See https://docs.astral.sh/ruff/configuration/ for defaults. @@ -53,13 +63,8 @@ select = ["E4", "E7", "E9", "F", "I", "UP", "C901"] [tool.ruff.lint.isort] known-first-party = ["{{ cookiecutter.package_name }}"] -[tool.pyright] -# Pyright/pylance/vscode configuration. -# Note: if you want a different setup, you can overwrite this with a -# "pyrightconfig.json", which takes precedence. -include = "src" -venvPath = "." -venv = ".venv" +[[tool.uv.index]] +url = "https://packages.lizard.net/" -[dependency-groups] -dev = ["pytest>=8.4.2", "pytest-cov>=6.3.0", "pytest-sugar>=1.1.1", "pytest-django>=4.11"] +[tool.zest-releaser] +release = false From cbc93e8168634756107dc5f8e7d42ae42e616c33 Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 19 Nov 2025 19:02:58 +0100 Subject: [PATCH 09/13] Updated changelog --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index f080bd5..b9e648a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,7 +4,7 @@ ## 0.9 (unreleased) -- Nothing changed yet. +- Updated django directory with changes from a recent project. ## 0.8 (2025-09-16) From 940e2850cfb24cd49040ab60af1f0fc705311eb5 Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 19 Nov 2025 19:03:10 +0100 Subject: [PATCH 10/13] Escaping/formatting --- django/{{ cookiecutter.project_name }}/ansible/deploy.yml | 3 +-- django/{{ cookiecutter.project_name }}/ansible/provision.yml | 2 +- django/{{ cookiecutter.project_name }}/src/manage.py | 4 +--- .../src/{{ cookiecutter.package_name }}/wsgi.py | 4 +--- {{ cookiecutter.project_name }}/.nens.toml | 2 +- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/django/{{ cookiecutter.project_name }}/ansible/deploy.yml b/django/{{ cookiecutter.project_name }}/ansible/deploy.yml index 9c0fa92..4cb8c99 100644 --- a/django/{{ cookiecutter.project_name }}/ansible/deploy.yml +++ b/django/{{ cookiecutter.project_name }}/ansible/deploy.yml @@ -58,5 +58,4 @@ - name: Run migrate ansible.builtin.command: # noqa: no-changed-when cmd: "docker-compose run --rm web python src/manage.py migrate --noinput" - chdir: /srv/{{ site_name }} -{% endraw %} + chdir: /srv/{{ site_name }}{% endraw %} diff --git a/django/{{ cookiecutter.project_name }}/ansible/provision.yml b/django/{{ cookiecutter.project_name }}/ansible/provision.yml index 9712a6e..9ed7a2d 100644 --- a/django/{{ cookiecutter.project_name }}/ansible/provision.yml +++ b/django/{{ cookiecutter.project_name }}/ansible/provision.yml @@ -30,7 +30,7 @@ - name: Create dir inside /srv for the checkout ansible.builtin.file: - path: /srv/{{ site_name }} + path: {% raw %}/srv/{{ site_name }}{% endraw %} state: directory mode: "0755" owner: buildout diff --git a/django/{{ cookiecutter.project_name }}/src/manage.py b/django/{{ cookiecutter.project_name }}/src/manage.py index 608fe36..01ed797 100755 --- a/django/{{ cookiecutter.project_name }}/src/manage.py +++ b/django/{{ cookiecutter.project_name }}/src/manage.py @@ -7,9 +7,7 @@ def main(): """Run administrative tasks.""" - os.environ.setdefault( - "DJANGO_SETTINGS_MODULE", "{{ cookiecutter.package_name }}.settings" - ) + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ cookiecutter.package_name }}.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/django/{{ cookiecutter.project_name }}/src/{{ cookiecutter.package_name }}/wsgi.py b/django/{{ cookiecutter.project_name }}/src/{{ cookiecutter.package_name }}/wsgi.py index 3d510e8..45fac19 100644 --- a/django/{{ cookiecutter.project_name }}/src/{{ cookiecutter.package_name }}/wsgi.py +++ b/django/{{ cookiecutter.project_name }}/src/{{ cookiecutter.package_name }}/wsgi.py @@ -11,8 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault( - "DJANGO_SETTINGS_MODULE", "{{ cookiecutter.package_name }}.settings" -) +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ cookiecutter.package_name }}.settings") application = get_wsgi_application() diff --git a/{{ cookiecutter.project_name }}/.nens.toml b/{{ cookiecutter.project_name }}/.nens.toml index df5f9db..5114278 100644 --- a/{{ cookiecutter.project_name }}/.nens.toml +++ b/{{ cookiecutter.project_name }}/.nens.toml @@ -7,6 +7,6 @@ project_name = "{{ cookiecutter.project_name }}" [meta_workflow] # Python version to use for linting and so -python_version = '3.12' +python_version = "3.12" # Whether to run pytest in the workflow run_pytest = true From b6377857fc77d233cdb1969480baab787a99936a Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 19 Nov 2025 19:07:11 +0100 Subject: [PATCH 11/13] Small tombi fixes --- pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fd4ef30..ff74555 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,5 @@ +#:tombi schema.strict = false + [project] name = "cookiecutter-python-template" version = "0.9.dev0" @@ -36,7 +38,7 @@ dev = [ # Pyright/pylance/vscode configuration. # Note: if you want a different setup, you can overwrite this with a # "pyrightconfig.json", which takes precedence. -include = "src" +include = ["src"] venvPath = "." venv = ".venv" From 39370856c56c76ff9b5ef65d0e70e1c2406ce635 Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 19 Nov 2025 19:12:45 +0100 Subject: [PATCH 12/13] Fixed exclude --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d090a9f..d744026 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://nens-meta.readthedocs.io/en/latest/config-files.html default_language_version: python: python3 -exclude: ^{{ +exclude: ^(django/)?{{ repos: - repo: https://github.com/pre-commit/pre-commit-hooks From 4dda4eb204507240c27c7cc0daf4c29b5aa1ac98 Mon Sep 17 00:00:00 2001 From: Reinout van Rees Date: Wed, 19 Nov 2025 19:15:39 +0100 Subject: [PATCH 13/13] Commented out gdal for ease of testing the cookiecutter itself --- django/{{ cookiecutter.project_name }}/pyproject.toml | 2 +- .../src/{{ cookiecutter.package_name }}/settings.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/django/{{ cookiecutter.project_name }}/pyproject.toml b/django/{{ cookiecutter.project_name }}/pyproject.toml index bd75867..a28fcce 100644 --- a/django/{{ cookiecutter.project_name }}/pyproject.toml +++ b/django/{{ cookiecutter.project_name }}/pyproject.toml @@ -19,7 +19,7 @@ dependencies = [ "djangorestframework-gis>=1.2.0", "djangorestframework-types>=0.9.0", "drf-spectacular[sidecar]>=0.28.0", - "gdal==3.8.4", # 3.8.4 is the version in 24.04. Works with newer libs. + # "gdal==3.8.4", # 3.8.4 is the version in 24.04. Works with newer libs. "psycopg2-binary>=2.9.10", "sentry-sdk[django]>=2.38.0", "whitenoise>=6.10.0", diff --git a/django/{{ cookiecutter.project_name }}/src/{{ cookiecutter.package_name }}/settings.py b/django/{{ cookiecutter.project_name }}/src/{{ cookiecutter.package_name }}/settings.py index 1a479db..d971307 100644 --- a/django/{{ cookiecutter.project_name }}/src/{{ cookiecutter.package_name }}/settings.py +++ b/django/{{ cookiecutter.project_name }}/src/{{ cookiecutter.package_name }}/settings.py @@ -105,10 +105,10 @@ "django.contrib.sessions", "django.contrib.sites", "django.contrib.messages", - "django.contrib.gis", + # "django.contrib.gis", "django.contrib.staticfiles", "rest_framework", - "rest_framework_gis", + # "rest_framework_gis", "drf_spectacular", "drf_spectacular_sidecar", ]