Skip to content

Forms#

A small, declarative forms layer: field validation plus HTML rendering, the input-side counterpart to Entity/Column on the storage side.

Defining a form#

from hayavega.forms import BooleanField, CharField, EmailField, Form


class SignupForm(Form):
    name = CharField(max_length=150)
    email = EmailField()
    subscribed = BooleanField()

Validating a submission#

def signup(request):
    form = SignupForm(request.form(), request=request)
    if not form.is_valid():
        return Response(f"<form method='post'>{form.render()}</form>", status=400)
    name = form.cleaned_data["name"]
    ...

Form(data) is unbound (never valid, nothing to clean) when data is omitted -- use that for the initial "show the empty form" GET. Form(data, request=request) is bound; is_valid() cleans every field, populating cleaned_data on success or errors (one message per invalid field, keyed by field name) otherwise.

Rendering#

form.render() returns the <input>/<label>/error-message markup for every field (not the <form> tag itself -- wrap it yourself so you control the action/method). Passing request= to Form(...) adds a hidden csrf_token field automatically (security.csrf_token), so a bound form posted back through @csrf_protect just works.

Built-in fields#

Field Python type Notes
CharField str max_length= optional
EmailField str CharField plus an email-format check
IntegerField int
DecimalField float
BooleanField bool Not required by default -- an unchecked checkbox submits nothing at all

Every field accepts required=, label=, initial=.

Writing your own field#

Override _convert(raw) -- it's only called for a non-empty submitted value; Field.clean() already handles the required/empty case:

from hayavega.forms import Field, FieldError


class SlugField(Field):
    def _convert(self, raw):
        if not raw.replace("-", "").isalnum():
            raise FieldError("Enter a valid slug (letters, numbers, hyphens).")
        return raw.lower()

entity_form: the ModelForm equivalent#

For simple CRUD, generate a Form from an Entity's declared columns instead of hand-declaring every field:

from hayavega import Entity, Integer, Text, Toggle
from hayavega.forms import entity_form


class Product(Entity):
    name = Text(unique=True)
    stock = Integer(default=0)
    published = Toggle(default=False)


ProductForm = entity_form(Product)  # name, stock, published fields
ProductForm(request.form(), request=request)

A column with a default (or nullable=True) becomes a non-required field; everything else is required. Pass fields=("name",) to include only specific columns. entity_form builds the field types automatically (Toggle → BooleanField, Integer/Decimal → their matching numeric fields, everything else → CharField) -- the same column-to-field mapping admin.py's own CRUD forms already use internally, generalized into something your own views can call directly.