Returning redirects

We support returning redirects from API endpoints with RedirectTo exception with modify():

Run result

$ curl http://127.0.0.1:8000/api/user/ -D - -X GET
HTTP/1.1 302 Found
date: Sun, 26 Apr 2026 21:12:29 GMT
server: uvicorn
Location: https://example.com/api/new/user/list
Content-Type: application/json
X-Frame-Options: DENY
Vary: Accept-Language
Content-Language: en
Content-Length: 0
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin


$ curl http://127.0.0.1:8000/api/user/ -D - -X POST
HTTP/1.1 302 Found
date: Sun, 26 Apr 2026 21:12:30 GMT
server: uvicorn
Location: https://example.com/api/new/user/create
Content-Type: application/json
X-Frame-Options: DENY
Vary: Accept-Language
Content-Language: en
Content-Length: 0
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin

OpenAPI Schema

Preview openapi.json
{
  "components": {
    "schemas": {
      "ErrorDetail": {
        "description": "Base schema for error details description.",
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "string"
                }
              ]
            },
            "title": "Loc",
            "type": "array"
          },
          "msg": {
            "title": "Msg",
            "type": "string"
          },
          "type": {
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "msg"
        ],
        "title": "ErrorDetail",
        "type": "object"
      },
      "ErrorModel": {
        "description": "Default error response schema.\n\nCan be customized.\nSee :ref:`customizing-error-messages` for more details.",
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ErrorDetail"
            },
            "title": "Detail",
            "type": "array"
          }
        },
        "required": [
          "detail"
        ],
        "title": "ErrorModel",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/usercontroller/": {
      "get": {
        "deprecated": false,
        "operationId": "getUsercontrollerApiUsercontroller",
        "responses": {
          "302": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "null"
                }
              }
            },
            "description": "Found",
            "headers": {
              "Location": {
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "406": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when provided `Accept` header cannot be satisfied"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when returned response does not match the response schema"
          }
        }
      },
      "post": {
        "deprecated": false,
        "operationId": "postUsercontrollerApiUsercontroller",
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": {
                    "type": "string"
                  },
                  "type": "object"
                }
              }
            },
            "description": "Created"
          },
          "302": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "null"
                }
              }
            },
            "description": "Found",
            "headers": {
              "Location": {
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "406": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when provided `Accept` header cannot be satisfied"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when returned response does not match the response schema"
          }
        }
      }
    }
  }
}

We model RedirectTo as an exception, because you are not allowed to return HttpResponse objects from modify() endpoints.

Note

APIError does not support 3xx status codes. Redirects are different from regular errors.

The second way is to use default Django’s django.http.HttpResponseRedirect together with validate():

Run result

$ curl http://127.0.0.1:8000/api/user/ -D - -X GET
HTTP/1.1 302 Found
date: Sun, 26 Apr 2026 21:12:30 GMT
server: uvicorn
Content-Type: application/json
Location: https://example.com/api/new/user/list
X-Frame-Options: DENY
Vary: Accept-Language
Content-Language: en
Content-Length: 0
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin

OpenAPI Schema

Preview openapi.json
{
  "components": {
    "schemas": {
      "ErrorDetail": {
        "description": "Base schema for error details description.",
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "string"
                }
              ]
            },
            "title": "Loc",
            "type": "array"
          },
          "msg": {
            "title": "Msg",
            "type": "string"
          },
          "type": {
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "msg"
        ],
        "title": "ErrorDetail",
        "type": "object"
      },
      "ErrorModel": {
        "description": "Default error response schema.\n\nCan be customized.\nSee :ref:`customizing-error-messages` for more details.",
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ErrorDetail"
            },
            "title": "Detail",
            "type": "array"
          }
        },
        "required": [
          "detail"
        ],
        "title": "ErrorModel",
        "type": "object"
      }
    },
    "securitySchemes": {}
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/usercontroller/": {
      "get": {
        "deprecated": false,
        "operationId": "getUsercontrollerApiUsercontroller",
        "responses": {
          "302": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "null"
                }
              }
            },
            "description": "Found",
            "headers": {
              "Location": {
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "406": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when provided `Accept` header cannot be satisfied"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when returned response does not match the response schema"
          }
        }
      }
    }
  }
}

Note that in both cases you would need to document Location header in a response spec.

API Reference

exception dmr.response.RedirectTo(redirect_to: _StrOrPromise, *, status_code: HTTPStatus = HTTPStatus.FOUND, headers: dict[str, str] | None = None)[source]

Special class to redirect from @modify styled endpoints.

It is modeled as an exception, because you can’t return HttpResponse subclasses from modify() endpoints.

We model this class closely to match django.http.HttpResponseRedirect.

Usage:

>>> from http import HTTPStatus
>>> from dmr import (
...     RedirectTo,
...     Controller,
...     ResponseSpec,
...     modify,
...     HeaderSpec,
... )
>>> from dmr.errors import ErrorType
>>> from dmr.plugins.pydantic import PydanticSerializer

>>> class UserController(Controller[PydanticSerializer]):
...     @modify(
...         extra_responses=[
...             ResponseSpec(
...                 None,
...                 status_code=HTTPStatus.FOUND,
...                 headers={'Location': HeaderSpec()},
...             ),
...         ],
...     )
...     def get(self) -> str:
...         # This API endpoint is deprecated, use new one:
...         raise RedirectTo(
...             '/api/new/users/',
...             status_code=HTTPStatus.FOUND,
...         )