Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions templates/django-api/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# AGENTS.md – AI Interaction & Execution Guide (Humans: see CONTRIBUTING.md & docs/)

## Authoritative references

| Topic | Source |
|-------|--------|
| Architecture | docs/PROJECT_STRUCTURE.md |
| API | docs/API.md |
| Testing | docs/TESTING_GUIDE.md |
| Deployment | docs/DEPLOYMENT.md |
| Configuration | docs/CONFIGURATION.md |
| Typing | docs/TYPING.md |

## Key commands

| Command | Purpose |
|---------|---------|
| `uv run python manage.py runserver` | Dev server |
| `uv run gunicorn config.wsgi:application` | Prod-style WSGI |
| `uv run ruff check .` | Lint |
| `uv run pytest` | Tests |
| `uv run mypy apps config` | Types |

## Feature work protocol

1. Copy `docs/examples/feature-app/` → `apps/<feature>/`.
2. Register in `INSTALLED_APPS`.
3. Wire urls under `API_PREFIX`.
4. Add tests; keep response envelope `{data,error,meta}`.
5. Update OpenAPI-facing serializers when shapes change (`/api/.../docs/`).
24 changes: 24 additions & 0 deletions templates/django-api/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Contributing

## Setup

```bash
uv sync
uv run python manage.py migrate
uv run pytest
```

## Style

- Feature apps under `apps/`
- Copy new features from `docs/examples/feature-app/`
- Ruff for lint/format
- Typed public APIs (serializers + view annotations)

## Docs

Update the matching file under `docs/` when behaviour changes. Keep `AGENTS.md` as a pointer table, not a second docs tree.

## Extensions

Optional: `django-docker`, `postgres`, `github-setup`, `development-container`.
12 changes: 12 additions & 0 deletions templates/django-api/QUALITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Quality checklist (django-api) — M1

## Required

- [x] Feature apps under `apps/` with serializers / services / views / urls
- [x] Docs suite: PROJECT_STRUCTURE, API, CONFIGURATION, TESTING_GUIDE, DEPLOYMENT, TYPING
- [x] README, AGENTS, CONTRIBUTING present
- [x] OpenAPI via drf-spectacular (`/schema/`, `/docs/`)
- [x] pytest-django health test
- [x] Ruff + mypy/pyright configured
- [x] Feature scaffold lives under `docs/examples/` (not an installed app)
- [x] Compatible with `django-docker` and `postgres` extensions
64 changes: 35 additions & 29 deletions templates/django-api/README.md
Original file line number Diff line number Diff line change
@@ -1,45 +1,51 @@
# Django API
# Django + DRF API

Django + Django REST Framework API starter with [uv](https://docs.astral.sh/uv/), [Ruff](https://docs.astral.sh/ruff/), and [pytest-django](https://pytest-django.readthedocs.io/).
Production-oriented Django REST Framework starter with a **feature-app** layout,
uv, Ruff, pytest-django, mypy/pyright stubs, and docs that match the CPA quality bar.

## Quick start
## Quickstart

```sh
```bash
uv sync
uv run python manage.py migrate
uv run python manage.py runserver
curl -s http://127.0.0.1:8000/api/v1/healthz/
# OpenAPI UI: http://127.0.0.1:8000/api/v1/docs/
```

## Commands
Tests and lint:

| Command | Description |
|---------|-------------|
| `uv run python manage.py runserver` | Start dev server |
| `uv run python manage.py migrate` | Apply migrations |
| `uv run ruff check .` | Lint |
| `uv run pytest` | Run tests |

## Health probes
```bash
uv run pytest
uv run ruff check .
uv run mypy apps config
```

| Endpoint | Purpose |
|----------|---------|
| `GET /ping/` | Minimal probe |
| `GET {apiPrefix}/healthz` | API readiness probe |
## Architecture

## Compatible extensions
Domain code lives under `apps/<feature>/` (serializers, services, views, urls).
Project wiring lives under `config/`. Copy `docs/examples/feature-app/` when adding a feature.

| Slug | Notes |
|------|-------|
| `github-setup` | CI / Dependabot |
| `python-devcontainer` | VS Code Dev Container |
| `python-postgres` | Postgres Compose + driver (wire `DATABASES` manually) |
See [docs/PROJECT_STRUCTURE.md](./docs/PROJECT_STRUCTURE.md).

`python-docker` currently targets FastAPI (`uvicorn app.main:app`) and is not compatible yet.
## Documentation

## Configuration
| Doc | Topic |
|-----|-------|
| [docs/README.md](./docs/README.md) | Index |
| [docs/API.md](./docs/API.md) | Endpoints, envelope, OpenAPI |
| [docs/CONFIGURATION.md](./docs/CONFIGURATION.md) | Env and tooling |
| [docs/TESTING_GUIDE.md](./docs/TESTING_GUIDE.md) | pytest-django |
| [docs/DEPLOYMENT.md](./docs/DEPLOYMENT.md) | Containers and prod |
| [docs/TYPING.md](./docs/TYPING.md) | mypy / pyright |
| [AGENTS.md](./AGENTS.md) | AI assistant guide |
| [CONTRIBUTING.md](./CONTRIBUTING.md) | Human contributor guide |

Copy `.env.example` to `.env`. Scaffold option:
## Compatible extensions

| Option | Default | Description |
|--------|---------|-------------|
| `apiPrefix` | `/api/v1` | Prefix for API routes including `/healthz` |
| Slug | Purpose |
|------|---------|
| `github-setup` | CI / Dependabot / PR automation |
| `development-container` | VS Code Dev Container |
| `django-docker` | Dockerfile + Compose for Django |
| `postgres` | Postgres Compose service (wire `DATABASES` / URL) |
7 changes: 0 additions & 7 deletions templates/django-api/api/urls.py

This file was deleted.

7 changes: 0 additions & 7 deletions templates/django-api/api/urls_ping.py

This file was deleted.

25 changes: 0 additions & 25 deletions templates/django-api/api/views.py

This file was deleted.

Empty file.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.apps import AppConfig


class ApiConfig(AppConfig):
class HealthConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "api"
name = "apps.health"
label = "health"
15 changes: 15 additions & 0 deletions templates/django-api/apps/health/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Health check serializers."""

from __future__ import annotations

from rest_framework import serializers


class HealthStatusSerializer(serializers.Serializer):
status = serializers.CharField()


class HealthEnvelopeSerializer(serializers.Serializer):
data = HealthStatusSerializer() # type: ignore[assignment]
error = serializers.JSONField(allow_null=True)
meta = serializers.DictField()
7 changes: 7 additions & 0 deletions templates/django-api/apps/health/services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""Health check business logic."""

from __future__ import annotations


def get_health_status() -> dict[str, str]:
return {"status": "healthy"}
9 changes: 9 additions & 0 deletions templates/django-api/apps/health/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Health feature URLs."""

from django.urls import path

from apps.health.views import HealthzView

urlpatterns = [
path("healthz/", HealthzView.as_view(), name="healthz"),
]
22 changes: 22 additions & 0 deletions templates/django-api/apps/health/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Health check HTTP endpoints."""

from __future__ import annotations

from drf_spectacular.utils import extend_schema
from rest_framework.permissions import AllowAny
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView

from apps.health.serializers import HealthEnvelopeSerializer, HealthStatusSerializer
from apps.health.services import get_health_status


class HealthzView(APIView):
authentication_classes = []
permission_classes = [AllowAny]

@extend_schema(responses={200: HealthEnvelopeSerializer})
def get(self, request: Request) -> Response:
serializer = HealthStatusSerializer(get_health_status())
return Response({"data": serializer.data, "error": None, "meta": {}})
13 changes: 12 additions & 1 deletion templates/django-api/config/settings.py.template
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ INSTALLED_APPS = [
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
"api",
"drf_spectacular",
"apps.health",
]

MIDDLEWARE = [
Expand Down Expand Up @@ -66,6 +67,9 @@ DATABASES = {
}
}

# When using the `postgres` extension, prefer DATABASE_URL / psycopg and replace
# the sqlite engine above. See docs/CONFIGURATION.md and docs/DEPLOYMENT.md.

AUTH_PASSWORD_VALIDATORS = [
{"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
Expand All @@ -90,4 +94,11 @@ REST_FRAMEWORK = {
"DEFAULT_PARSER_CLASSES": [
"rest_framework.parsers.JSONParser",
],
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}

SPECTACULAR_SETTINGS = {
"TITLE": "Django API",
"DESCRIPTION": "API scaffolded with create-awesome-python-app",
"VERSION": "0.1.0",
}
10 changes: 8 additions & 2 deletions templates/django-api/config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
from django.conf import settings
from django.contrib import admin
from django.urls import include, path
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView

api_prefix = getattr(settings, "API_PREFIX", "/api/v1").lstrip("/")

urlpatterns = [
path("admin/", admin.site.urls),
path("ping/", include("api.urls_ping")),
path(f"{api_prefix}/", include("api.urls")),
path(f"{api_prefix}/schema/", SpectacularAPIView.as_view(), name="schema"),
path(
f"{api_prefix}/docs/",
SpectacularSwaggerView.as_view(url_name="schema"),
name="swagger-ui",
),
path(f"{api_prefix}/", include("apps.health.urls")),
]
23 changes: 23 additions & 0 deletions templates/django-api/docs/API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# API

## Health

`GET {{ apiPrefix }}/healthz/`

```json
{
"data": { "status": "healthy" },
"error": null,
"meta": {}
}
```

## OpenAPI

| Path | Purpose |
|------|---------|
| `{{ apiPrefix }}/schema/` | OpenAPI 3 schema (drf-spectacular) |
| `{{ apiPrefix }}/docs/` | Swagger UI |

Keep DRF serializers as the source of truth for request/response shapes; Spectacular
reads them via `DEFAULT_SCHEMA_CLASS`.
24 changes: 24 additions & 0 deletions templates/django-api/docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Configuration

## Environment

| Variable | Default | Purpose |
|----------|---------|---------|
| `DJANGO_SECRET_KEY` | `dev-only-change-me` | Django secret |
| `DJANGO_DEBUG` | `true` | Debug mode |
| `DJANGO_ALLOWED_HOSTS` | `localhost,127.0.0.1` | Comma-separated hosts |

Scaffold option `apiPrefix` (default `/api/v1`) becomes `API_PREFIX` in settings.

## Tooling

| Tool | Command |
|------|---------|
| Ruff | `uv run ruff check .` |
| mypy | `uv run mypy apps config` |
| pyright | `uv run pyright` |
| pytest | `uv run pytest` |

## Database

Default is SQLite. With the `postgres` extension, add `psycopg` (merged via extension `pyproject.toml`) and point Django `DATABASES` (or a URL helper) at the Compose service hostname `db`.
22 changes: 22 additions & 0 deletions templates/django-api/docs/DEPLOYMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Deployment

## Local process

```bash
uv run python manage.py migrate
uv run python manage.py runserver
```

## Containers

Add the `django-docker` extension for `Dockerfile` + Compose (`runserver` locally, `gunicorn config.wsgi` in prod overlay).

Pair with `postgres` when you need a database service. Inside Compose, use hostname `db` instead of `localhost`.

## Checklist

- [ ] Set a strong `DJANGO_SECRET_KEY`
- [ ] `DJANGO_DEBUG=false` in production
- [ ] Restrict `DJANGO_ALLOWED_HOSTS`
- [ ] Run migrations before traffic
- [ ] Terminate TLS at the proxy / platform
Loading
Loading