Skip to main content
DRF And React By Schema - Django

This package, with the corresponding npm package with the same name, enables a django headless infrastructure for running with react very easily, directly from your models.

About

React has great power in building highly responsive and dynamic interfaces for users, with great flexibility and quality.

On the other hand, Django offers a consolidated infrastructure, high performance, and a popular way to organize data models for creating, removing, and updating data (CRUD) for simple to highly complex and intricate systems.

A recognized and active library in Django, called Django Rest Framework, or simply DRF, leverages these Django characteristics to allow developers to create powerful API schemas directly related to the data model, using the concept of viewsets (analogous to views) and serializers (analogous to forms).

Building systems with a large number of forms for managing different processes is Django’s power, but its approach to the interface, based on views, forms, and especially templates, templatetags, and inclusiontags, is outdated, as every update must refresh the entire page, following a logic of HTML template rendering.

An attractive option is to use React as the client (interface) for a Django instance with DRF as the server. In other words, Django serves the data and performs operations on the data as a Headless server (i.e., without views, forms, or templates, but only through the API).

However, a common problem with this approach is that it often requires duplicating work in the client (React) and the server (Django/DRF) to handle form interactions and validations in React that are related to the server models.

As an example, let’s suppose that you want to manage products. Let’s say that each product belongs to a product category. Let’s assume we have the categories “fruits and vegetables” and “grains,” and the products lettuce, papaya, and grapes in the “fruits and vegetables” category, and beans, rice, and fava beans in the “grains” category. In this example, we could consider that each product has attributes such as “name”, “description”, “unit”, “price per unit,” and “availability”. For the categories, we could say they have attributes like “name” and “description”.

In Django, the data model is created very simply: create a ProductCategory and a Product models, with a “ForeignKey” relationship indicating a one-to-many relationship between the product and the category (one category can have many products, and one product belongs to a single category).

In React, you would need to build the form, types of components (simple text input, long text input, numeric input, password input, yes/no switch input, autocomplete multiple-choice, single-choice dropdown, date pickers, etc.), validation conditions (whether it’s mandatory or not, initial data, data format masks, etc.).

The problem is that this is redundant work because this information is already available in the Django model!

But how do you pass the data structure information to React? This is where DRF and React By Schema - Django (drf-react-by-schema-django) comes in. It creates a powerful schema on the server that can be accessed by React via the OPTION method.

And how do you build forms, validation conditions, and form components in React? This is where DRF and React By Schema - React (drf-react-by-schema-react) comes in, which consumes the schemas generated by drf-react-by-schema-django and automatically builds all of this for the developer with very few lines of code, while allowing complete customization of the interfaces (layout).

Furthermore, on the Django side, drf-react-by-schema-django automatically generates a full API endpoint structure directly from the app’s models, along with powerful search tools, filters, nested related objects, validation, and handling of object creation, updating, and removal processes, which is a challenging task to do manually in Django DRF because it typically requires in-depth knowledge of serializers and viewsets. The Django package simplifies this entire process for the developer while maintaining performance.

On the React side, drf-react-by-schema-react offers a range of powerful components that allow for the automatic construction of forms, including forms for multiple related objects (through editable tables), using the full power of MUI and MUI x-DataGrid.

The DRF and React By Schema Suite is available for React at https://www.npmjs.com/package/drf-react-by-schema and for Django at https://pypi.org/project/drf-react-by-schema/.

Pre-requisites

  • django-rest-framework

  • drf-nested-routers

  • django-cors-headers

  • djangorestframework-simplejwt

  • django-filter

Compatibility

This package supports Django 3.2 through 5.2 and Python 3.10+ from a single release line. The supported combinations are exercised in CI (see .gitlab-ci.yml / tox.ini).

Django

Python

Notes

3.2

3.10

End of upstream life. Pin drf-excel<2.6 (see below).

4.2

3.10 - 3.12

LTS

5.0

3.10 - 3.12

5.1

3.10 - 3.13

5.2

3.10 - 3.13

Optional Dependencies

For enhanced caching capabilities in the automatically generated viewsets, you can install optional dependencies:

  1. Redis Support (recommended for production caching):

    pip install drf-react-by-schema[redis]

    Then configure Redis caching in your Django settings:

    CACHES = {
        'default': {
            'BACKEND': 'django_redis.cache.RedisCache',
            'LOCATION': 'redis://127.0.0.1:6379/1',
            'OPTIONS': {
                'CLIENT_CLASS': 'django_redis.client.DefaultClient',
            }
        }
    }

    This enables high-performance caching for the global ETag system and viewset responses.

  2. Django Native Caching System:

    Django’s native cache system (without Redis) is supported out-of-the-box. You can use any of Django’s built-in cache backends:

    • In-memory cache (development):

      CACHES = {
          'default': {
              'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
          }
      }
    • File-based cache:

      CACHES = {
          'default': {
              'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
              'LOCATION': '/var/tmp/django_cache',  # Don't forget to create the folder!
          }
      }
    • Database cache:

      CACHES = {
          'default': {
              'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
              'LOCATION': 'my_cache_table',
          }
      }

    The caching features are particularly useful for performance optimization when dealing with frequently accessed data in the automatically generated viewsets and serializers.

Quick start

  1. Add and configure the pre-requisite apps.

  2. Add “drf-react-by-schema” to your INSTALLED_APPS setting like this:

    INSTALLED_APPS = [
        ...
        'drf_react_by_schema',
    ]
  3. Configure Django Rest Framework in settings, adding the drf-react-by-schema renderer, metadata and filter backend, as in the example below:

    REST_FRAMEWORK = {
        'DEFAULT_PERMISSION_CLASSES': (
            'rest_framework.permissions.IsAuthenticated',
        ),
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework_simplejwt.authentication.JWTAuthentication',
            'rest_framework.authentication.SessionAuthentication',
        ),
        "DEFAULT_RENDERER_CLASSES": [
            "rest_framework.renderers.JSONRenderer",
            "drf_react_by_schema.renderers.CustomBrowsableAPIRenderer",
        ],
        'DEFAULT_METADATA_CLASS': 'drf_react_by_schema.metadata.Metadata',
        'DEFAULT_FILTER_BACKENDS': [
            'rest_framework.filters.SearchFilter',
            'drf_react_by_schema.filters.DRFReactBySchemaOrderingFilter',
        ],
    }
  4. Configure drf-react-by-schema, for example adding the apps that should be considered for building the API endpoints. If not added to settings, the default will be ['main']. Example:

    DRF_REACT_BY_SCHEMA = {
        'APPS': [
            'main',
        ],
        'PAGINATION_MODE': 'server',
        'PAGINATION_PAGE_SIZE': 50,
        'PAGINATION_PAGE_SIZE_OPTIONS': [5, 10, 25, 50, 100, 200],
        'PAGINATION_MAX_PAGE_SIZE': 10000,
        'PAGINATION_MODE_NESTED_VIEWSETS': 'server',
        'PERMISSIONS': 'rest_framework.permissions.IsAuthenticated',
        'REACT_APP_DIR': 'frontend',
        # Expose GET /drbs/schema/ for MCP clients (default: on).
        'SCHEMA_ENDPOINT': True,
    }
  5. Include the URL configuration in your project urls.py like this:

    urlpatterns = [
        ...
        path('', include('drf_react_by_schema.urls')),
    ]
  6. Start the development server and visit http://127.0.0.1:8000/api/endpoints and you will see all endpoints available.

  7. Create or include your React app, for example in a frontend folder. You can find a quick guide on how to install the DRF And React By Schema - React package in your React app at https://www.npmjs.com/package/drf-react-by-schema.

You can customize viewsets and serializers to annotate other attributes.

Global permission-aware queryset filtering

Every generated viewset already supports a filter_by_callback (and a list-only filter_list_by_callback) that runs in get_queryset/list/excel and returns filter(**kwargs) arguments (optionally with or_kwargs).

Instead of subclassing each viewset to set them, you can provide a global fallback callback for all generated viewsets at once:

DRF_REACT_BY_SCHEMA = {
    ...
    # Dotted path to ``def callback(viewset, queryset) -> dict|None``
    # (a 3-arg ``(viewset, queryset, kwargs)`` form is also supported).
    'FILTER_BY_CALLBACK': 'myapp.filters.filter_access_by_permissions',
    'FILTER_LIST_BY_CALLBACK': 'myapp.filters.filter_list_access_by_permissions',
}

The viewset-level attribute always wins; when it is unset, the callback falls back to the global setting (gone too, no filtering happens). A viewset can opt out of the global fallback by setting use_global_filter_by_callback = False.

Because nested and autocomplete viewsets inherit from the same base, the global callback also applies to them — useful for scoping pickers and sub-resources to the rows the current user may access.

Custom model fields

This package ships drop-in replacements for the Django model fields (CharField, DecimalField, ForeignKey, DateTimeField, FileField, …) that carry the extra metadata the React widgets need. Import them from drf_react_by_schema and use them in your models whenever a matching type exists; they behave like their Django counterparts at the database/migration level and the added keyword arguments are read by metadata.py into the OPTIONS schema. This package offers special fields for added control over metadata directly from model.

Relational fields (ForeignKey, ManyToManyField, OneToOneField) have two independent, easily-confused editability flags:

  • editable — Django’s own attribute. editable=False means the relation is never written through the API: it is still listed and returned nested as a read-only {id, label} value (so forms/grids can display it), but the server ignores any submitted value, so raw request data cannot create, replace, or null it. Use it for server-managed relations such as created_by. The schema reports related_editable: false for these fields.

  • related_editable — package-specific, default True. With True the user may create the related record inline from the form’s autocomplete. With False the relation is pick-only: it can still be set to an existing record (id-based update), but no on-the-fly creation is offered.

They compose: an editable=False field is read-only regardless of related_editable; a writable field with related_editable=False can be changed to an existing record but cannot create a new one.

Conditional visibility

conditional_visible is a package-specific keyword argument available on the custom fields. It tells the React <Form>/FormBySchema> and the DataGrid inline editor to show the field only while a condition on other fields holds (and to clear its value on save while hidden). The value is passed through to the OPTIONS schema verbatim, so it can use any of the following shapes:

  • a single [source_field, source_value] pair — the simplest case, e.g. conditional_visible=("type", "book") (a Python tuple serialises to a JSON array);

  • a list of those pairs — combined with OR (show if any matches), e.g. conditional_visible=[("type", "book"), ("format", "paperback")];

  • an explicit {"operator": "AND" | "OR", "conditions": [...]} expression for arbitrary, parenthesised logic. conditions may mix pairs and nested operator expressions, e.g. show isbn only when the record type is book and the format is either hardcover or paperback:

    conditional_visible={
        "operator": "AND",
        "conditions": [
            ("type", "book"),
            {
                "operator": "OR",
                "conditions": [
                    ("format", "hardcover"),
                    ("format", "paperback"),
                ],
            },
        ],
    }

A plain list of pairs is always interpreted as OR; use an explicit {"operator": "AND", ...} expression when every condition must hold. This keyword combines with ui_required: a conditionally visible field is required only while it is actually shown.

Machine-readable schema (GET /drbs/schema/)

Besides the per-model OPTIONS schema and the sync_react TS/JSON snapshots written for the React app, the package exposes a schema-only JSON document, meant for programmatic clients such as an MCP server:

GET /drbs/schema/

It is registered at the project root (a sibling of /autocomplete/), so a model named Schema cannot collide with /api/schema/. It returns one object with:

  • stamp — a deterministic SHA-256 (16 hex chars) of the structural bundle, stable across processes and independent of row data; it changes iff the generated picture (models, fields, endpoints, system config) or the package version changes;

  • drbs_version — the installed drf-react-by-schema version;

  • system — apps, AUTH_USER_MODEL, the DRF_REACT_BY_SCHEMA summary, static permission hints (DRF defaults, global, per-model), request conventions (API base, pagination, ordering/search/sum_rows params, grid-filter operators and the relation-filter note) and about (the project’s ABOUT.md, trimmed — see below);

  • endpoints — every top-level route with its path/kind, verbose_name/verbose_name_plural and the underlying Django model and app_label (which can differ from the endpoint key when a viewset aliases another model’s);

  • models — each model’s field schema (the same OPTIONS POST payload the React app consumes), the Django model/app_label, verbose names, the model class docstring (trimmed) and the valid sum_rows measures and dimensions (medidas/dimensoes).

The bundle deliberately omits the React-only TypeScript artifacts (interfaces/enums — still written to types.ts by sync_react) and the auto-generated nested *Related* viewsets: an assistant queries /api/<model>/ and constrains relations with the grid filter instead, so those would only add noise. Each relation field in schema therefore carries a related_model naming the model it points at.

Documentation in the bundle

Free-form, non-UI documentation is folded into the bundle so an assistant can understand the system. It is kept out of the React artifacts on purpose: sync_react’s schemas/*.json stay byte-identical to before.

  • Model docstrings — a model class’s own docstring (inherited docs are ignored) is reported as models[<Model>].docstring, trimmed at a word boundary to schema_bundle.MODEL_DOCSTRING_MAX characters.

  • Field descriptions — pass description="..." to any package field (CharField, ForeignKey, DecimalField, …); it is reported as models[<Model>].schema[<field>].description (trimmed to FIELD_DESCRIPTION_MAX). This is not help_text: help_text is rendered to end users by drf-react-by-schema-js, while description documents the field for the API client / assistant. Only values passed explicitly are reported — the field type’s own description is never.

  • Project ``ABOUT.md`` — a project-root ABOUT.md (settings.BASE_DIR) is reported as system.about (trimmed to ABOUT_MAX). Override the path with DRF_REACT_BY_SCHEMA['ABOUT_PATH']; a missing file yields null. Use it for what the project is about — not deploy/contribute instructions.

All three feed the stamp, so changing them invalidates an MCP client’s cached copy; because the bundle is cached per process, a change also needs a process restart to be picked up.

The endpoint sits behind the project’s existing auth (the same permission source used by the generated viewsets — DRF_REACT_BY_SCHEMA['PERMISSIONS'], defaulting to IsAuthenticated; authentication comes from your DRF defaults, so an OAuth read token works). It sets ETag: "<stamp>", honors If-None-Match (returning 304 Not Modified) and caches the built bundle per process. The payload contains structure and static configuration only — no row data and no secrets.

It is gated by DRF_REACT_BY_SCHEMA['SCHEMA_ENDPOINT'] (default True). Set it to False to not register the route at all:

DRF_REACT_BY_SCHEMA = {
    ...
    'SCHEMA_ENDPOINT': False,
}

Pagination

Page size is a single source of truth in DRF_REACT_BY_SCHEMA:

  • PAGINATION_PAGE_SIZE — default page size, 50 if unset. The deprecated PAGINATION_PER_PAGE is still read as a fallback (with a DeprecationWarning).

  • PAGINATION_PAGE_SIZE_OPTIONS — the choices offered by the generated React client, [5, 10, 25, 50, 100, 200] if unset. sync_react writes the resolved values to src/drfReactBySchema/pagination.ts (export const paginationConfig), which the app passes to DRFReactBySchemaProvider (pagination prop).

  • PAGINATION_MAX_PAGE_SIZE — hard server-side cap, 10000 if unset.

Clients may override the size per request with the page_size query parameter (e.g. ?page=N&page_size=200); the value is capped at PAGINATION_MAX_PAGE_SIZE. The resolved configuration is reported to MCP clients under system.conventions.pagination.

Server-side totals & facets (sum_rows)

List endpoints accept a sum_rows query parameter that aggregates over the whole filtered set (not just the current page), regardless of which page is returned. It is available when server pagination is on (DRF_REACT_BY_SCHEMA['PAGINATION_MODE'] == 'server'); the totals ride in the pagination envelope as sum_rows (and facet breakdowns as facets).

The parameter is a comma-separated list of items. Each item is a measure (optionally faceted):

item := measure [ '|' dim [ '|' dim ... ] [ '|' limit ] ]
  • measure is a numeric field name, count (COUNT(*), i.e. rows), or count_distinct:<field> (COUNT(DISTINCT <field>), counting distinct non-null values).

  • dim is a facet: a ForeignKey (grouped by fk_id), a field with choices, or a BooleanField. Forward relations may be nested with __ (e.g. categoria__regiao groups by the category’s region).

  • a trailing all-digits part is the limit (top-N buckets, ordered by the measure descending). Django field names can never be all-digits, so it is unambiguous.

  • | separates the parts of an item; , separates items.

Flat totals keep their legacy shape {"<measure>_total": value} and every measure — faceted or not — also reports its grand total there. The : of a count_distinct:<field> measure becomes _ in the flat key (e.g. count_distinct_categoria_total).

GET /api/widget/?sum_rows=preco                        # {preco_total: ...}
GET /api/widget/?sum_rows=preco,count                  # two global totals
GET /api/widget/?sum_rows=count|categoria              # nº de itens por categoria
GET /api/widget/?sum_rows=count|categoria__regiao      # ... por região da categoria
GET /api/widget/?sum_rows=count_distinct:categoria     # nº de categorias distintas
GET /api/widget/?sum_rows=count_distinct:categoria|tipo  # categorias distintas por tipo
GET /api/widget/?sum_rows=preco|categoria              # preço por categoria (+ preco_total)
GET /api/widget/?sum_rows=preco|categoria|tipo         # cross-tab
GET /api/widget/?sum_rows=preco|tipo|4                 # top-4 tipos por preço

Response (the facets key is null when no facet was requested):

{
  "count": 120,
  "sum_rows": {"preco_total": 4500.0, "count_total": 120},
  "facets": [
    {
      "field": "preco",
      "by": ["categoria"],
      "limit": null,
      "buckets": [
        {"values": {"categoria": {"value": 12, "display_name": "Exemplo"}}, "total": 1200.0},
        {"values": {"categoria": {"value": null, "display_name": "—"}}, "total": 0.0}
      ]
    }
  ],
  "results": ["..."]
}

Each bucket carries values (one {value, display_name} per facet dimension, using the same convention as choice fields elsewhere) and total (the measure). Invalid measures/facets (non-numeric, unknown, non-concrete) are rejected with 400, and the error enumerates the valid options for that model (its numeric fields, its facetable foreign keys / choice / boolean fields, or its distinct-count fields). The valid options are also published per model in the schema bundle as models[<Model>].sum_rows = {"medidas": [...], "dimensoes": [...], "medidas_distintas": [...]}. The same grammar powers the drf-react-by-schema-mcp resumo tool and is understood by drf-react-by-schema-js.

Grid filters

The grid filter (field / operator / value, each a list, combined with AND) accepts the operators listed in system.conventions.grid_filter. A note on relations: equality on a foreign key compares the related object’s label (its __str__/nome/slug), not its primary key — to filter by pk use field='<fk>__id' (or field='<fk>_id' on a raw model field) with operator is or =.

Password reset & change

The package ships re-usable endpoints for the JWT + simplejwt stack. They reuse django.contrib.auth (token generator + password hashing), so no extra package (such as allauth / dj-rest-auth) is required.

Endpoints (auto-registered, no authentication needed for password_reset):

  • POST my_account/change_password/ — authenticated. Body: old_password, new_password.

  • POST password_reset/request/ — {email}. Mints a reset uid``+``token for the user (if the e-mail is registered; replies generically either way to avoid leaking accounts) and forwards them to the delivery callback.

  • POST password_reset/confirm/ — {uid, token, new_password}. Validates the token and sets the new password.

Sending the reset e-mail is pluggable. The simplest path needs no code: configure the standard Django e-mail settings and the reset-link path, and a default callback sends a plain-text message via Django’s mail backend:

# EMAIL_* (e.g. EMAIL_BACKEND, EMAIL_HOST, ...) must also be configured.
PASSWORD_RESET_CONFIRM_PATH = '/password-reset/confirm'  # your frontend page

request_password_reset looks the user up by e-mail directly (not through PasswordResetForm) so accounts with an unusable password — e.g. freshly invited users that still must set their own password — also receive a reset link.

For a customized delivery (branding, HTML, another channel), point DRF_REACT_BY_SCHEMA['PASSWORD_RESET_CALLBACK'] at a callable or a dotted path. The library already mints the uid/token before invoking your callback, so the effective change is a single small function like the one below:

# myapp/auth.py
from django.conf import settings
from django.core.mail import send_mail

def send_reset_email(request, user, uid, token):
    domain = request.get_host()
    scheme = 'https' if request.is_secure() else 'http'
    path = getattr(settings, 'PASSWORD_RESET_CONFIRM_PATH', '/password-reset/confirm')
    send_mail(
        'Redefinição de senha',
        f'Clique aqui para definir sua senha: {scheme}://{domain}{path}?uid={uid}&token={token}',
        None,
        [user.email],
    )
DRF_REACT_BY_SCHEMA = {
    ...
    'PASSWORD_RESET_CALLBACK': 'myapp.auth.send_reset_email',
    'PASSWORD_RESET_CONFIRM_PATH': '/password-reset/confirm',
}

For any questions, please add issues in the GitLab repository.

Metadata

Release files for drf-react-by-schema 0.27.9

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for drf-react-by-schema 0.27.9
File Size Uploaded
drf_react_by_schema-0.27.9.tar.gz 84.1 kB Details

Release files / drf_react_by_schema-0.27.9.tar.gz

Download URL drf_react_by_schema-0.27.9.tar.gz
Size 84.1 kB
Tags Source
SHA-256 checksum
How to use checksums
860cda3cd23a93dd5c0f03daa2e97ae88fef99c7f317a7bc830dc32ba2385f8e
BLAKE2b-256 checksum
How to use checksums
0fae823ca61e79e19a3a056ac1a941df70df9dc5db447172457f9bcacedc2d27
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/5.0.0 CPython/3.12.3

Release history Release notifications | RSS feed

This release

0.27.9 This release

1 release file

0.27.8

1 release file

0.27.7

1 release file

0.27.6

1 release file

0.27.5

1 release file

0.27.4

1 release file

0.27.3

1 release file

0.27.2

1 release file

0.27.1

1 release file

0.27.0

1 release file

0.26.17

1 release file

0.26.16

1 release file

0.26.15

1 release file

0.26.14

1 release file

0.26.13

1 release file

0.26.12

1 release file

0.26.11

1 release file

0.26.9

1 release file

0.26.8

1 release file

0.26.7

1 release file

0.26.6

1 release file

0.26.5

1 release file

0.26.4

1 release file

0.26.3

1 release file

0.26.2

1 release file

0.26.1

1 release file

0.26.0

1 release file

0.25.0

1 release file

0.24.4

1 release file

0.24.3

1 release file

0.24.2

1 release file

0.24.1

1 release file

0.24.0

1 release file

0.23.2

1 release file

0.23.1

1 release file

0.23.0

1 release file

0.21.7

1 release file

0.21.6

1 release file

0.21.5

1 release file

0.21.4

1 release file

0.21.0

1 release file

0.20.3

1 release file

0.20.2

1 release file

0.20.1

1 release file

0.20.0

1 release file

0.11.2

1 release file

0.11.1

1 release file

0.11.0

1 release file

0.10.9

1 release file

0.10.8

1 release file

0.10.7

1 release file

0.10.6

1 release file

0.10.5

1 release file

0.10.4

1 release file

0.10.3

1 release file

0.10.2

1 release file

0.10.1

1 release file

0.10.0

1 release file

0.9.7

1 release file

0.9.6

1 release file

0.9.5

1 release file

0.9.4

1 release file

0.9.3

1 release file

0.9.2

1 release file

0.9.1

1 release file

0.9.0

1 release file

0.8.9

1 release file

0.8.8

1 release file

0.8.7

1 release file

0.8.6

1 release file

0.8.5

1 release file

0.8.4

1 release file

0.8.3

1 release file

0.8.2

1 release file

0.8.1

1 release file

0.8

1 release file

0.7

1 release file

0.6

1 release file

0.5

1 release file

0.3

1 release file

0.2

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page