diff --git a/django_forms_as_jsonschema/layout.py b/django_forms_as_jsonschema/layout.py index db7cb3b9e48d2b5a9a10f0639d3561cc9866f466..64ef178a3b84d3d866ae704c2351f12cee5422d6 100644 --- a/django_forms_as_jsonschema/layout.py +++ b/django_forms_as_jsonschema/layout.py @@ -1,44 +1,57 @@ -import uuid +"""Re-implementation of django-material's form layout mechanics. + +This implementation provides a drop-in replacement for django-material's +Layout, Row, and Fieldset classes, which allow rendering into a JSON schema. + +cf. http://docs.viewflow.io/material_forms.html +""" + from abc import ABC from dataclasses import dataclass -from typing import Union +from typing import Optional, Union from django.utils.text import slugify class LayoutNode(ABC): + """Abstract node in a form layout.""" def __init__(self, *elements): self.elements = elements def build_schema(self, schema, form_fields) -> dict: + """Render this node into a JSON schema fragment.""" props = {} for field in self.elements: if isinstance(field, LayoutNode): + # Recurse and add fragment of the sub-node built_schema = field.build_schema(schema, form_fields) props.update(built_schema["properties"]) else: + # Add verbatim field props[field] = schema.generate_field(form_fields[field]) + return {"properties": props} @dataclass class _Section: + """Visual section in a form.""" codename: str title: str - description: str = None + description: Optional[str] = None class Row(LayoutNode): + """Visual row in a form.""" + def build_schema(self, schema, form_fields): - row_name = "row-" + str(uuid.uuid4()) + """Render this row as a JSON schema fragment.""" + row_name = f"row-{id(self)}" fields = super().build_schema(schema, form_fields)["properties"] for k in fields.keys(): - fields[k]["x-options"] = { - "fieldColProps": { - "cols": 12, - "md": "" - } - } + # Make row responsive by extending each field to full width on smaller viewports + fields[k].setdefault("x-options", {}).setdefault("fieldColProps", {}).update({"cols": 12, "md": ""}) + return { "properties": { row_name: { @@ -50,15 +63,18 @@ class Row(LayoutNode): class Fieldset(LayoutNode): + """Visual set of fields in a form.""" def __init__(self, name: Union[tuple[str, str], str], *elements): super().__init__(*elements) codename = str(slugify(name)) - if type(name) == tuple: - self.section = _Section(codename, *map(str, name)) + + if isinstance(name, tuple): + self.section = _Section(codename, *name) else: - self.section = _Section(codename, str(name)) + self.section = _Section(codename, name) def build_schema(self, schema, form_fields) -> dict: + """Render this fieldset as a JSON schema fragment.""" section = schema.generate_section(self.section) section["properties"].update(super().build_schema(schema, form_fields)["properties"]) return { @@ -69,4 +85,4 @@ class Fieldset(LayoutNode): class Layout(LayoutNode): - ... + """Full form layout as the root node."""