Skip to main content

WTForms form generator and renderer for UOFastORM-backed UniData/U2 models

Project description

UOFastForms

WTForms form generator and renderer for UOFastORM-backed UniData/U2 models.

A RokiPark open-source project.

UOFastForms automatically builds WTForms Form classes from your UopyModel field metadata, handles U2 multi-value (MV) data, integrates lookup popups, and supports server-side validation via cataloged U2 BASIC subroutines — all with Bootstrap 5-ready Jinja2 macros.


Features

  • Auto-generated forms from UopyModel._field_map and _mv_fields metadata
  • FieldConfig enrichment — override types, labels, validators, choices, and render attributes per field
  • Multi-value field supportMVTextAreaField (newline-separated) and MVFieldList (dynamic repeater)
  • Lookup popups — declarative LookupModel generates data-lookup-* HTML attributes for JS integration
  • Server-side validationRemoteValidator calls cataloged U2 BASIC subroutines and maps errors back to form fields
  • Flask Blueprintregister_forms(app) wires up Jinja2 template globals in one line
  • Bootstrap 5 macrosrender_form, render_field, render_mv_fieldlist, render_form_errors

Installation

pip install uofastforms

With Flask rendering support:

pip install uofastforms[flask]

With U2 server-side validation:

pip install uofastforms[all]

Quick Start

1. Define a FormModel

from uofastforms import FormModel, FieldConfig, RemoteValidator, register_forms
from wtforms.validators import DataRequired, Length

class ClientFormModel(FormModel):
    model = ClientModel          # your UopyModel subclass

    remote_validator = RemoteValidator(
        subroutine_name="VALIDATE.CLIENT",
        arg_count=3,
        input_arg=0,
        output_arg=1,
        fatal_arg=2,
        session_factory=lambda: uopy.connect(**cfg),
    )

    fields = {
        "company":     FieldConfig(label="Company", required=True,
                                   validators=[Length(max=100)]),
        "fname":       FieldConfig(label="First Name"),
        "lname":       FieldConfig(label="Last Name"),
        "phone":       FieldConfig(label="Phones", description="One per line"),
        "contact_ids": FieldConfig(exclude=True),
    }

2. Register with Flask (once, in app factory)

from uofastforms import register_forms

def create_app():
    app = Flask(__name__)
    register_forms(app)
    return app

3. Use in a view

@bp.route('/clients/<id>/edit', methods=['GET', 'POST'])
def edit_client(id):
    client = ClientModel(session, id)
    form = ClientFormModel.get_form(
        obj=client          if request.method == 'GET'  else None,
        data=request.form   if request.method == 'POST' else None,
    )
    if request.method == 'POST' and form.validate():
        ClientFormModel.populate_obj(form, client)
        client.update()
        return redirect(url_for('.list'))
    return render_template('client_edit.html', form=form)

4. Render in a Jinja2 template

{% from 'uofast_forms/form_macros.html' import render_form %}

{{ render_form(form, action=url_for('.edit_client', id=client.record_id)) }}

API Reference

FormModel

Base class for all form definitions. Subclass it and declare model, fields, and optionally remote_validator.

Class attribute Type Description
model type[UopyModel] Required. The UopyModel subclass this form is based on.
fields dict[str, FieldConfig] Per-field enrichment overrides.
remote_validator RemoteValidator Optional. Runs after local validation.

Class methods:

FormModel.get_form_class(base_class=Form) -> type[Form]

Returns (and caches) the generated WTForms Form subclass.

FormModel.get_form(obj=None, data=None, base_class=Form, **kwargs) -> Form

Creates a form instance, optionally pre-populated from a UopyModel instance (obj) or raw POST data (data).

FormModel.populate_obj(form, obj) -> None

Writes validated form data back to a UopyModel instance.

FormModel.get_lookup_attrs(prop_name) -> dict[str, str]

Returns data-lookup-* HTML attributes for a field that has a LookupModel or linked_file configured.


FieldConfig

Per-field enrichment dataclass.

@dataclass
class FieldConfig:
    field_type: str | None = None       # "string", "integer", "float", "boolean",
                                        # "date", "datetime", "select", "textarea",
                                        # "email", "url", "password", "hidden"
    label: str | None = None            # Human-readable label (auto-prettified if None)
    description: str | None = None      # Help text shown below the field
    required: bool = False              # Prepends DataRequired validator
    validators: list = field(default_factory=list)
    choices: list | None = None         # [(value, label), ...] for SelectField
    render_kw: dict = field(default_factory=dict)
    exclude: bool = False               # Omit this field from the form entirely
    mv_style: str = "textarea"          # "textarea" or "fieldlist" for MV fields
    default: Any = None
    linked_file: str | None = None      # U2 file name for lookup popup
    linked_display_field: str | None = None
    linked_fill: dict | None = None     # {db_field: form_prop} auto-fill map
    lookup: LookupModel | None = None   # Full LookupModel (overrides linked_* attrs)

LookupModel

Declarative configuration for field search-and-select popups.

class ProductLookup(LookupModel):
    model = ProductModel
    display_fields = ["code", "description", "price"]
    search_field = "description"
    fill = {"code": "product_code", "description": "product_desc"}

Use with FieldConfig(lookup=ProductLookup). The get_lookup_attrs() method returns the full data-lookup-* attribute dict for HTML rendering.


RemoteValidator

WTForms validator that calls a cataloged U2 BASIC subroutine for server-side business-rule validation.

RemoteValidator(
    subroutine_name="VALIDATE.CLIENT",
    arg_count=3,
    input_arg=0,          # index of arg receiving JSON form data
    output_arg=1,         # index of arg returning JSON error dict
    fatal_arg=2,          # index of arg returning a fatal error string
    session_factory=...,  # callable returning a live uopy.Session
    field_name_map=None,  # optional {subroutine_name: form_prop_name} mapping
)

Subroutine contract:

Arg index Direction Content
input_arg IN JSON string of form data, e.g. {"company": "Acme", "phone": ["555-1234"]}
output_arg OUT JSON string of field errors, e.g. {"company": "Name already taken"} or ""
fatal_arg OUT Fatal (non-field) error string; empty string = success

MVTextAreaField / MVFieldList

WTForms field types for U2 multi-value data.

  • MVTextAreaField — Renders as a <textarea> with one value per line. Use to_list() / populate_from_list(values) for programmatic access.
  • MVFieldList — Renders as a dynamic repeater (requires companion JS). Use make_mv_fieldlist(field_type, label, ...) factory to create instances.

Flask Integration

from uofastforms import register_forms, forms_bp

# Option A — app factory helper (recommended)
register_forms(app)

# Option B — manual Blueprint registration
app.register_blueprint(forms_bp)

register_forms also adds uofast_form_errors(form) as a Jinja2 global — a helper that returns a flat list of (field_label, error_message) tuples.


Jinja2 Macros

Import from uofast_forms/form_macros.html:

{% from 'uofast_forms/form_macros.html' import render_form, render_field,
                                                render_mv_fieldlist, render_form_errors %}
Macro Description
render_form(form, action, method, submit_label, css_class, enctype) Full <form> with all fields and submit button
render_field(field, label_class, input_class, wrapper_class) Single Bootstrap 5 form-group
render_mv_fieldlist(field, add_label, input_class, wrapper_class) Dynamic MV repeater
render_form_errors(form) Dismissable Bootstrap 5 alert listing all errors

All macros use Bootstrap 5 CSS classes. Browser-native validation is disabled (novalidate) — WTForms and RemoteValidator handle all validation.


Requirements

Dependency Required Notes
WTForms >= 3.0 Yes Core form framework
Flask >= 2.0 Optional Required for register_forms, forms_bp, and macros
uopy Optional Required for RemoteValidator
UOFastORM Runtime UopyModel subclasses must come from your UOFastORM models

License

MIT — see LICENSE.


Maintained by RokiPark · github.com/RokiPark/UOFastForms

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

uofastforms-0.1.0.tar.gz (24.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

uofastforms-0.1.0-py3-none-any.whl (25.3 kB view details)

Uploaded Python 3

File details

Details for the file uofastforms-0.1.0.tar.gz.

File metadata

  • Download URL: uofastforms-0.1.0.tar.gz
  • Upload date:
  • Size: 24.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.9

File hashes

Hashes for uofastforms-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f0e55974e9348953a6bc4783307967ad08fb74cdcba84ed359e6e10414d46aa4
MD5 59014941b254cc5df5411bf237caaeed
BLAKE2b-256 2eb5b98dc73e837f8382f617e5a4f9d939a5f7ff3180b411cdd18953807ff052

See more details on using hashes here.

File details

Details for the file uofastforms-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: uofastforms-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 25.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.9

File hashes

Hashes for uofastforms-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bdd9be380559914ef667b3eae958d90bf8c0602dec5701b3ea0a5218f318b22f
MD5 4b362956656c789cf831d2582af654ff
BLAKE2b-256 4b8f154335717e3750b8b83e3a1d4521b521638f3c846f2577c9b32031fb9507

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page