Skip to content

Settings with a Code Field

A settings form where part of the settings is JSON, TOML or a script, typed into a text area with syntax highlighting and a Format button, and checked on the server.

models.py
import json
import tomllib
from typing import Any

from pydantic import field_validator

from pydantic_schemaforms import Field, FormModel


class ServiceSettings(FormModel):
    name: str = Field(title='Service name', pattern=r'^[a-z][a-z0-9-]*$')
    config: str = Field(
        '{\n  "replicas": 2\n}',
        title='Config (JSON)',
        ui_element='textarea',
        ui_options={'language': 'json', 'rows': 10},
    )
    build: str = Field(
        '[tool.build]\ntarget = "release"\n',
        title='Build settings (TOML)',
        ui_element='textarea',
        ui_options={'language': 'toml', 'rows': 6},
    )
    deploy_script: str = Field(
        '',
        title='Deploy script',
        ui_element='textarea',
        ui_options={'language': 'bash', 'code_highlight': False},
    )

    @field_validator('config')
    @classmethod
    def valid_json_object(cls, value: str) -> str:
        try:
            parsed: Any = json.loads(value)
        except json.JSONDecodeError as exc:
            raise ValueError(f'Not valid JSON: {exc.msg} (line {exc.lineno})') from None
        if not isinstance(parsed, dict):
            raise ValueError('The config must be a JSON object')
        return value

    @field_validator('build')
    @classmethod
    def valid_toml(cls, value: str) -> str:
        try:
            tomllib.loads(value)
        except tomllib.TOMLDecodeError as exc:
            raise ValueError(f'Not valid TOML: {exc}') from None
        return value
main.py
import json

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from models import ServiceSettings

from pydantic_schemaforms import html, render_form_html_async

app = FastAPI()
FORM_OPTIONS = {'submit_url': '/settings', 'include_framework_assets': True, 'asset_mode': 'cdn'}


@app.get('/settings', response_class=HTMLResponse)
async def show_settings():
    return await render_form_html_async(ServiceSettings, **FORM_OPTIONS)


@app.post('/settings', response_class=HTMLResponse)
async def save_settings(request: Request):
    result = ServiceSettings.validate(await request.form(), **FORM_OPTIONS)
    if not result.is_valid:
        return await result.render_with_errors_async()
    replicas = json.loads(result.data['config']).get('replicas', 1)
    return html(t'<p>Saved {result.data["name"]} with {replicas} replicas.</p>')
main.py
import json

from flask import Flask, request
from models import ServiceSettings

from pydantic_schemaforms import html, render_form_html

app = Flask(__name__)
FORM_OPTIONS = {'submit_url': '/settings', 'include_framework_assets': True, 'asset_mode': 'cdn'}


@app.get('/settings')
def show_settings():
    return render_form_html(ServiceSettings, **FORM_OPTIONS)


@app.post('/settings')
def save_settings():
    result = ServiceSettings.validate(request.form, **FORM_OPTIONS)
    if not result.is_valid:
        return result.render_with_errors()
    replicas = json.loads(result.data['config']).get('replicas', 1)
    return html(t'<p>Saved {result.data["name"]} with {replicas} replicas.</p>')

How it works

  • ui_options={"language": ...} turns a text area into a small code editor: highlighting, Tab to indent, and a Format button. It's still a plain <textarea>, so it works without JavaScript.
  • The languages are json, yaml, toml, bash and python. JSON, YAML and TOML are really reformatted. Bash and Python only get their whitespace cleaned up, and the button says so.
  • The validators parse the text and turn a parse error into a message under the field. The browser's Format button already flags bad JSON, but only the server check counts.
  • code_highlight=False, code_format=False and code_tab_indent=False turn off each part; indent_size sets the indent (default 2).

The value stays a string. Parse it again where you use it, as the route does, or add a second field typed as a model if you want the parsed data validated field by field.