djmvc_api

djmvc_api is an optional package that adds:

  • A JSON API on the same URLs as the HTML CRUD views (REST verbs, no DRF).

  • Bearer token authentication (no session cookie, no CSRF on token requests).

  • Swagger 2.0 schema, Swagger UI, login, and token management under /api/.

Core JSON handlers live in djmvc; this package adds discovery, schema/UI, tokens, and auth middleware.

JSON API (core djmvc)

Every ModelController exposes JSON on its existing routes. HTML behaviour is unchanged.

Content negotiation

ViewMixin checks wants_json() in dispatch():

  • GET / POST — JSON when Accept: application/json or Content-Type: application/json.

  • PUT / PATCH / DELETE — always routed to json_* handlers (HTML only uses GET and POST).

REST mapping

Operation

HTTP method

Example (Item model)

List

GET + JSON accept

GET /item/

Detail

GET + JSON accept

GET /item/<pk>/detail/

Create

POST + JSON body

POST /item/create/

Update

PUT or PATCH + JSON body

PUT|PATCH /item/<pk>/update/

Delete

DELETE

DELETE /item/<pk>/delete/

POST to update or delete URLs with a JSON body returns 405 with an allowed_methods hint — use the proper verb instead.

Anonymous JSON requests receive 401 with {"detail": "Authentication required"} (not an HTML login redirect).

Session clients (browser or test client with cookies) still use the session and must send a CSRF token on mutating requests, same as standard Django.

Routes

djmvc_api.djmvc registers a single ApiController on djmvc.site. All package URLs live under /api/:

URL

View

/api/

SwaggerUIView — Swagger UI

/api/schema/

SchemaView — Swagger 2.0 JSON document

/api/login/

ApiLoginView — username/password → 1-hour token

/api/token/

TokenController — manage API tokens (HTML + JSON)

Enable the package

Add djmvc_api to INSTALLED_APPS and register the Bearer middleware (required for token auth and CSRF exemption):

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "djmvc_api.middleware.BearerCsrfMiddleware",   # before CSRF
    "django.middleware.locale.LocaleMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "djmvc_api.middleware.BearerUserMiddleware",     # after session auth
    "django.middleware.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

Run migrations after enabling the app:

python manage.py migrate

The example project already includes both; see src/djmvc_example/settings.py.

Optional install extra (placeholder for future dependencies):

pip install djmvc[api]

Bearer authentication

How it works

Two middleware classes cooperate (all logic stays in djmvc_api):

  1. BearerCsrfMiddleware (before CsrfViewMiddleware) — if Authorization: Bearer <token> is valid, sets request._dont_enforce_csrf_checks and stashes the token.

  2. BearerUserMiddleware (after AuthenticationMiddleware) — sets request.user and request.auth from the stashed token.

HTML forms on the same URLs still require CSRF when using session cookies.

Obtaining a token

Quick 1-hour tokenPOST /api/login/ with JSON credentials (no CSRF, no prior session):

curl -X POST http://localhost:8000/api/login/ \
  -H 'Content-Type: application/json' \
  -d '{"username": "su", "password": "su"}'

Response:

{
  "token": "<raw-key-shown-once>",
  "expires": "2026-06-29T14:00:00+00:00",
  "prefix": "AbCdEfGh"
}

Named / long-lived token — log in via HTML, open /api/token/create/, submit the form (session + CSRF). The raw key is shown once in a success message; only the prefix is stored for display in the list.

Using a token

curl http://localhost:8000/item/ \
  -H 'Accept: application/json' \
  -H 'Authorization: Bearer <token>'

Mutating requests (POST, PUT, PATCH, DELETE) work without a CSRF header when the Bearer token is valid.

Token model

Token stores a SHA-256 hash of the key (never the plaintext). Fields: user, name, prefix, created, optional expires, last_used.

  • Non-superusers see only their own tokens (list, detail, delete).

  • Superusers see all tokens.

  • Django permissions: view_token, add_token, delete_token (no change — tokens are immutable).

  • Revoke via HTML delete or DELETE /api/token/<pk>/delete/ with a Bearer token.

Swagger

  • GET /api/schema/ — OpenAPI/Swagger 2.0 JSON listing every JSON-capable route (not filtered by the current user’s permissions). Bearer auth is required to execute protected operations.

  • GET /api/ — Swagger UI (vendored static assets). Use Try it out on POST /api/login/ to obtain a token; Swagger UI stores it automatically (persistAuthorization) and sends Authorization: Bearer on other operations. You can also click Authorize and paste a token manually.

  • CRUD operations document BearerAuth in securityDefinitions.

  • POST /api/login/ is documented without Bearer security (it issues tokens).

Serialization

ModelController provides serialize() and json_fields on each controller. Override json_fields or add get_<field>_json hooks to control the JSON shape.

API reference (modules)

class djmvc_api.views.ApiLoginView(**kwargs)[source]

Bases: View

Exchange username/password for a short-lived Bearer token.

Constructor. Called in the URLconf; can contain helpful extra keyword arguments, and other things.

has_permission()[source]

Return whether the current user may access this view.

dispatch(request, *args, **kwargs)

Redirect anonymous users to login; return 403 when permission denied.

class djmvc_api.views.TokenCreateView(**kwargs)[source]

Bases: CreateView

Create a named API token via HTML form (raw key shown once).

Constructor. Called in the URLconf; can contain helpful extra keyword arguments, and other things.

form_valid(form)[source]

Log creation when LogMixin is active.

get_success_url()[source]

Redirect target after successful submit (next POST field or /).

class djmvc_api.views.TokenController[source]

Bases: ModelController

model

alias of Token

get_queryset(view)[source]

Return all rows for view; override to scope per user or role.

class djmvc_api.views.SchemaView(**kwargs)[source]

Bases: View

Serve a Swagger 2.0 schema for JSON-capable routes.

Constructor. Called in the URLconf; can contain helpful extra keyword arguments, and other things.

has_permission()[source]

Return whether the current user may access this view.

class djmvc_api.views.SwaggerUIView(**kwargs)[source]

Bases: TemplateView

Render Swagger UI for the generated schema.

Constructor. Called in the URLconf; can contain helpful extra keyword arguments, and other things.

has_permission()[source]

Return whether the current user may access this view.

get_context_data(**kwargs)[source]

Add view to the template context.

class djmvc_api.views.ApiController[source]

Bases: Controller

class djmvc_api.models.Token(*args, **kwargs)[source]

Bases: Model

Bearer token for JSON API access without session or CSRF.

classmethod generate(user, name, expires=None)[source]

Create a token row and return (instance, raw_key) (shown once).

classmethod authenticate(raw_key)[source]

Return a valid token for raw_key, or None.

exception DoesNotExist

Bases: ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: MultipleObjectsReturned

exception NotUpdated

Bases: ObjectNotUpdated, DatabaseError

Bearer token middleware for djmvc_api.

BearerCsrfMiddleware must run before django.middleware.csrf.CsrfViewMiddleware. BearerUserMiddleware must run after django.contrib.auth.middleware.AuthenticationMiddleware.

djmvc_api.middleware.parse_bearer_header(request)[source]

Return the Bearer token from Authorization, or None.

djmvc_api.middleware.lookup_token(raw_key)[source]

Validate raw_key and return the Token, or None.

class djmvc_api.middleware.BearerCsrfMiddleware(get_response)[source]

Bases: object

Skip CSRF when a valid Bearer token is present.

class djmvc_api.middleware.BearerUserMiddleware(get_response)[source]

Bases: object

Authenticate the request from a stashed Bearer token.