Integrations#

Warning

In the future - some integrations from this list might be included into the core of django-modern-rest package. Or ship as plugins.

If you are interested in something: open an issue.

django-modern-rest must be compatible with the most packages from the django ecosystem. To do so, we support:

CSRF#

Django supports Cross Site Request Forgery protection.

We support both ways of enforcing CSRF checks:

By default we exempt all controllers from CSRF checks, unless:

  1. csrf_exempt is set to False for a specific controller

  2. Endpoints protected by DjangoSessionSyncAuth, DjangoSessionAsyncAuth or CookieJWTSyncAuth, CookieJWTAsyncAuth will require CSRF as well. Because using cookies for auth with explicit CSRF is not secure

Note

Detailed CSRF failure reason on response content will be exposed only in debug mode for security reasons.

Here’s an example of how you can re-enable CSRF for a specific controller:

Run result

$ curl http://127.0.0.1:8000/api/example/ -D - -X POST -H 'X-CSRFToken: dmrDocsExampleCsrfTokenValue0001' --cookie csrftoken=dmrDocsExampleCsrfTokenValue0001
HTTP/1.1 201 Created
date: Fri, 16 Oct 2026 22:53:52 GMT
server: uvicorn
Content-Type: application/json
X-Frame-Options: DENY
Vary: Accept-Language
Content-Language: en
Content-Length: 4
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin

"ok"

$ curl http://127.0.0.1:8000/api/example/ -D - -X POST
HTTP/1.1 403 Forbidden
date: Fri, 16 Oct 2026 22:53:52 GMT
server: uvicorn
Content-Type: application/json
X-Frame-Options: DENY
Vary: Accept-Language
Content-Language: en
Content-Length: 35
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin

{"detail":[{"msg":"CSRF Failed."}]}

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": {
      "csrf": {
        "description": "CSRF protection",
        "in": "cookie",
        "name": "csrftoken",
        "type": "apiKey"
      }
    }
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/examplecontroller/": {
      "post": {
        "operationId": "postExampleControllerApiExamplecontroller",
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "Created"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when CSRF check failed"
          },
          "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"
          }
        },
        "security": [
          {
            "csrf": []
          }
        ]
      }
    }
  }
}

Next: see CSRF_FAILURE_VIEW Django setting value to configure how CSRF failure view will work. To do so, we use build_csrf_handler() function:

settings.py#
>>> from dmr.security.csrf import build_csrf_handler
>>> from dmr.plugins.pydantic import PydanticSerializer

>>> CSRF_FAILURE_VIEW = build_csrf_handler(
...     'api/',
...     serializer=PydanticSerializer,
... )

This configuration will work similarly to build_404_handler():

  • It will check the request path, if it starts with 'api/', we will negotiate the renderer and return the expected error in the default format

  • If not, it will return the regular Django HTML page

We automatically inject CSRF response specs and security schemes in endpoints for controllers with csrf_exempt = False and unsafe HTTP methods like POST, etc. We do so with the help of semantic_schema_providers setting and CSRFSemanticSchemaProvider response spec provider.

Note

csrf security requirement spec is injected with AND logic. So, if you have existing auth instances, you will have to satisfy both this auth and csrf requirements. Just like in the runtime.

Customizing error model#

Since we support full error customization we also support this feature for CSRF responses.

To change the error message format you will need:

Bring your own DI#

We don’t have any opinions about any DI that you can potentially use. Because django-modern-rest is compatible with any of the existing ones.

Use any DI that you already have or want to use with django.

Try any of these officially recommended tools:

Or any other one that suits your needs :)

Typing#

Django does not have type annotations, by default, so mypy won’t type check Django apps by default. But, when django-stubs is installed, type checking starts to shine.

So, when you use mypy, you will need to install django-stubs together with django-modern-rest to have the best type checking experience.

This package is included in pyright by default. No actions are required.

We check django-modern-rest code with mypy and pyright strict modes in CI, so be sure to have the best typing possible.

See our project template to learn how typing works, how mypy is configured, how django-stubs is used.

Pagination#

See Pagination — we support both limit/offset and cursor-based pagination out of the box.

Filters#

No special integration with django-filter is required.

Everything just works:

Run result

$ curl 'http://127.0.0.1:8000/api/users/?is_active=1' -X GET
[{"username":"test_user","email":"test@example.com","is_active":true}]

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"
      },
      "UserModel": {
        "properties": {
          "email": {
            "title": "Email",
            "type": "string"
          },
          "is_active": {
            "title": "Is Active",
            "type": "boolean"
          },
          "username": {
            "title": "Username",
            "type": "string"
          }
        },
        "required": [
          "username",
          "email",
          "is_active"
        ],
        "title": "UserModel",
        "type": "object"
      }
    }
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/userscontroller/": {
      "get": {
        "operationId": "getUsersControllerApiUserscontroller",
        "parameters": [
          {
            "in": "query",
            "name": "is_active",
            "required": true,
            "schema": {
              "title": "Is Active",
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/UserModel"
                  },
                  "type": "array"
                }
              }
            },
            "description": "OK"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when request components cannot be parsed"
          },
          "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"
          }
        }
      }
    }
  }
}

Health Checks#

We recommend using django-health-check for monitoring your application’s health.

No special integration is required — the package works out-of-the-box with django-modern-rest. Simply install it, include its URLs in your main urlconf, and add the desired check apps to INSTALLED_APPS.

For advanced configuration, please refer to the django-health-check documentation.

CORS Headers#

No special integration with django-cors-headers is required.

Everything just works.

Content Security Policy (CSP)#

No special integration with django-csp is required.

Everything just works, but there is one important nuance: django-modern-rest itself only controls Django templates and local initialization files. If you use OpenAPI UI renderers, final CSP compatibility still depends on the upstream frontend bundle you choose.

The OpenAPI UI templates shipped by django-modern-rest avoid inline <script> blocks and pass schema data via Django’s django.utils.html.json_script(), so DMR’s own templates work well with stricter CSP setups.

Known caveats:

  • Some upstream OpenAPI bundles inject styles at runtime, so a very strict policy can still break the page.

  • When CSP is a hard requirement, start with local bundled assets and test the exact renderer and version you plan to deploy.

Example django-csp setup can be found in wemake-django-template.

If you use OpenAPI UIs, see OpenAPI for renderer-specific guidance.

Conditional requests (ETag)#

Django has built-in support for conditional request processing (If-None-Match, If-Modified-Since, 304 Not Modified):

With django-modern-rest you can integrate it via wrap_middleware() and django.views.decorators.http.condition().

Run result

$ curl http://127.0.0.1:8000/api/example/1/ -D - -X GET
HTTP/1.1 200 OK
date: Fri, 16 Oct 2026 22:53:53 GMT
server: uvicorn
Content-Type: application/json
ETag: "user-1-2026-03-23T12:30:00+00:00"
X-Frame-Options: DENY
Vary: Accept-Language
Content-Language: en
Content-Length: 80
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin

{"message":"Fresh content for user #1","updated_at":"2026-03-23T12:30:00+00:00"}

$ curl http://127.0.0.1:8000/api/example/1/ -D - -X GET -H 'If-None-Match: "user-1-2026-03-23T12:30:00+00:00"'
HTTP/1.1 304 Not Modified
date: Fri, 16 Oct 2026 22:53:54 GMT
server: uvicorn
ETag: "user-1-2026-03-23T12:30:00+00:00"
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/example/2/ -D - -X GET
HTTP/1.1 200 OK
date: Fri, 16 Oct 2026 22:53:54 GMT
server: uvicorn
Content-Type: application/json
ETag: "user-2-2026-03-24T09:15:00+00:00"
X-Frame-Options: DENY
Vary: Accept-Language
Content-Language: en
Content-Length: 80
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin

{"message":"Fresh content for user #2","updated_at":"2026-03-24T09:15:00+00:00"}

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"
      },
      "_ResponseModel": {
        "properties": {
          "message": {
            "title": "Message",
            "type": "string"
          },
          "updated_at": {
            "title": "Updated At",
            "type": "string"
          }
        },
        "required": [
          "message",
          "updated_at"
        ],
        "title": "_ResponseModel",
        "type": "object"
      }
    }
  },
  "info": {
    "title": "Django Modern Rest",
    "version": "0.1.0"
  },
  "openapi": "3.2.0",
  "paths": {
    "/api/example/{user_id}/": {
      "get": {
        "operationId": "getConditionalETagControllerApiExampleUserId",
        "parameters": [
          {
            "in": "path",
            "name": "user_id",
            "required": true,
            "schema": {
              "title": "User Id",
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/_ResponseModel"
                }
              }
            },
            "description": "OK"
          },
          "304": {
            "description": "Not Modified",
            "headers": {
              "ETag": {
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when request components cannot be parsed"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Raised when path parameters do not match"
          },
          "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"
          }
        }
      }
    }
  }
}

HTMX#

Works with django-htmx out of the box.

dj-rest-auth#

Added in version 0.15.0.

We support dj-rest-auth in two different ways:

  1. By using it directly and integrating into external_path() as DRF views

  2. By using django-allauth with headless mode and external_path()

We also have a skill for the migration from dj-rest-auth: dj-rest-auth migration.