# Drf Api

> Builds DRF 3.16 REST endpoints on Django 5.2 — ModelViewSet plus DefaultRouter, a light list serializer split from a detail serializer via get_serializer_class, pagination defaults, a custom export @action, and a drf-spectacular OpenAPI schema. Use when adding or reviewing a ViewSet, serializer, router, or endpoint, splitting list vs detail payloads, wiring pagination, adding a CSV/export action,…

- **Type:** Skill
- **Install:** `agentstack add skill-deadlymind-nanolama-drf-api`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Deadlymind](https://agentstack.voostack.com/s/deadlymind)
- **Installs:** 0
- **Category:** [Data & Analytics](https://agentstack.voostack.com/c/data-and-analytics)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [Deadlymind](https://github.com/Deadlymind)
- **Source:** https://github.com/Deadlymind/nanolama/tree/main/skills/drf-api

## Install

```sh
agentstack add skill-deadlymind-nanolama-drf-api
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# DRF API (viewsets, serializers, routers, schema)

## When to use
Adding or reviewing a REST endpoint on this stack — a serializer, a `ModelViewSet`,
router wiring, pagination, an export action, or the OpenAPI schema. Tenant scoping and
RBAC are prerequisites here, not re-explained: your ViewSet subclasses the fail-closed
tenant mixin (`multi-tenancy`) and declares `{resource}_{action}` permissions
(`rbac-permissions`) before you add anything below.

## Pattern
One `ModelViewSet` per resource, registered on a single `DefaultRouter`. Serve a **light
list serializer** (few columns, no nested joins) for `list`, and a **fat detail
serializer** for `retrieve`/`create`/`update`, chosen in `get_serializer_class()` by
`self.action`. Pagination is a global default. Side-jobs (export, bulk ops) are custom
`@action`s, not new endpoints. The schema is generated, never hand-written.

## Steps / idioms
Split the serializers, pick one by `self.action`, register a single router, and inherit
the tenant + RBAC mixins. One consolidated example:

```python
# serializers.py — thin list row vs. fat detail payload
class InvoiceListSerializer(serializers.ModelSerializer):
    class Meta:
        model = Invoice
        fields = ["id", "number", "total", "status"]           # cheap list row

class InvoiceDetailSerializer(serializers.ModelSerializer):
    lines = InvoiceLineSerializer(many=True, read_only=True)    # nested, costly
    class Meta:
        model = Invoice
        fields = ["id", "number", "total", "status", "lines", "notes"]

# views.py — one ViewSet; serializer chosen by action, tenant + RBAC inherited
class InvoiceViewSet(TenantScopedViewSet):        # scopes queryset fail-closed
    queryset = Invoice.objects.all()
    permission_classes = [InvoiceRBAC]            # {resource}_{action} gate

    def get_serializer_class(self):
        return InvoiceListSerializer if self.action == "list" else InvoiceDetailSerializer

    @action(detail=False, methods=["get"])        # -> /api/invoices/export/
    def export(self, request):
        rows = self.filter_queryset(self.get_queryset())   # stays tenant-scoped
        resp = HttpResponse(content_type="text/csv")
        csv.writer(resp).writerows(rows.values_list("number", "total"))
        return resp

# urls.py — one router derives the URLConf and the browsable/OpenAPI schema
router = DefaultRouter()
router.register(r"invoices", InvoiceViewSet, basename="invoice")
urlpatterns = router.urls
```

Set a global pagination default so every list is bounded: in `settings.py`, give
`REST_FRAMEWORK` a `DEFAULT_PAGINATION_CLASS` of
`rest_framework.pagination.PageNumberPagination`, a `PAGE_SIZE` (e.g. 25), and a
`DEFAULT_SCHEMA_CLASS` of `drf_spectacular.openapi.AutoSchema`. Generate the OpenAPI
schema with `python manage.py spectacular --file schema.yml`, and annotate non-obvious
actions with `@extend_schema` so the generated types stay accurate — the frontend
consumes this schema (see `drf-zod-contract`).

Throttle every API so no client can hammer it unbounded: in `REST_FRAMEWORK` set
`DEFAULT_THROTTLE_CLASSES` (e.g. `UserRateThrottle`, `AnonRateThrottle`) and
`DEFAULT_THROTTLE_RATES` (e.g. `{"user": "1000/day", "anon": "100/day"}`), tuned to your
traffic. For a heavy `@action`, give it its own `throttle_scope` and a dedicated rate.

## Adapt to your repo
Rename `Invoice`, `InvoiceLine`, and the `entreprise` tenant FK to your models. Confirm
`TenantScopedViewSet` and your RBAC permission class exist (they are separate skills).
Pick a pagination class that fits your UI — `PageNumberPagination` for page numbers,
`CursorPagination` for infinite scroll or large offsets. Set `PAGE_SIZE` to your default.

## Gotchas
- A single fat serializer on `list` is the classic N+1 — split list vs detail and add
  `select_related`/`prefetch_related` on the detail queryset (see `perf-review`).
- Custom `@action`s bypass nothing automatically: re-run `get_queryset()` /
  `filter_queryset` so they stay tenant-scoped, and gate them under RBAC.
- No global `DEFAULT_PAGINATION_CLASS` means unbounded list responses — set it once.
- Stream large exports with `StreamingHttpResponse`; a full `HttpResponse` buffers the
  whole CSV in memory.
- `get_serializer_class` keys off `self.action` — a typo silently falls through to the
  detail serializer; assert the split in a test.
- `Model.save(update_fields=[...])` skips `clean()` / full validation — enforce business
  rules in `perform_update` or `serializer.validate`, never by relying on `Model.clean`
  firing on save.
- A serializer `source="get_x_display"` only resolves when the model field declares
  `choices=`; otherwise it silently breaks — use a `SerializerMethodField` instead.
- One `ScopedRateThrottle` scope is a single shared bucket per user across every endpoint
  using it, so a heavy action (bulk export, PDF generation) can starve unrelated
  endpoints — give it its own `throttle_scope` so the buckets don't collide.

## See also
- `multi-tenancy`
- `rbac-permissions`
- `drf-zod-contract`
- `perf-review`

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Deadlymind](https://github.com/Deadlymind)
- **Source:** [Deadlymind/nanolama](https://github.com/Deadlymind/nanolama)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-deadlymind-nanolama-drf-api
- Seller: https://agentstack.voostack.com/s/deadlymind
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
