This guide covers two field types added in v0.33 that extend the form data model beyond simple scalar inputs.
A calculated field (field_type = "calculated") produces a read-only value that is computed from other field values using a simple template formula.
Formula: {first_name} {last_name}
Result: "Jane Smith" (auto-updated as the user types)
Calculated fields are:
- Read-only — displayed as plain text; not editable by the submitter.
- Live-updating — a small vanilla-JS snippet injected by
form_submit.htmlwatches forinput/changeevents on dependency fields and updates the calculated field instantly. - Server-validated —
_re_evaluate_calculated_fields()is called afterserialize_form_dataat submit time so the stored value is always the authoritative server-computed result, regardless of what the client sent.
Formulas use Python str.format_map style placeholders:
{field_name}
Multiple placeholders are supported:
| Formula | Description |
|---|---|
{first_name} {last_name} |
Concatenate two text fields |
{dept_code}-{job_code} |
Build a composite key |
{year}/{month}/{day} |
Compose a date string from separate fields |
Placeholders that reference fields not yet filled in are left as empty strings (no error).
- Open Django Admin → Form Definitions → [your form] → Form Fields inline.
- Click Add another Form Field.
- Set Field Type to Calculated / Formula.
- In the Formula text box (in the "Choices & Defaults" fieldset), enter your formula using
{field_name}placeholders. - Set Required to unchecked (the field is computed, not user-supplied).
- Save.
Alternatively, open the field directly via Admin → Form Fields where the Formula fieldset is in its own collapsed section.
# forms.py — called inside serialize_form_data()
def _re_evaluate_calculated_fields(fields, form_data):
for field in fields.filter(field_type="calculated"):
formula = field.formula or ""
try:
form_data[field.field_name] = formula.format_map(
defaultdict(str, form_data)
)
except (KeyError, ValueError):
pass # leave existing value unchanged on errordefaultdict(str, form_data) ensures that missing keys resolve to "" rather than raising KeyError.
Calculated fields are also rendered (read-only) inside ApprovalStepForm, so approvers can see computed values when reviewing stage-specific fields.
A spreadsheet field (field_type = "spreadsheet") accepts a .csv, .xls, or .xlsx file uploaded by the submitter. The file is parsed server-side and stored as structured JSON in form_data.
{
"headers": ["Employee ID", "Name", "Hours"],
"rows": [
{"Employee ID": "E001", "Name": "Alice", "Hours": "40"},
{"Employee ID": "E002", "Name": "Bob", "Hours": "38"}
]
}| Format | Parser | Requirement |
|---|---|---|
.csv |
stdlib csv |
No extra dependency |
.xls / .xlsx |
openpyxl |
pip install django-forms-workflows[excel] |
- Open the form's Form Fields inline.
- Add a field with Field Type = Spreadsheet Upload (CSV / Excel).
- Optionally add help text:
"Upload a CSV or Excel file. First row must be the header."
The parsed rows list is available in form_data under the field's field_name:
# In a custom post-submission action handler:
spreadsheet = submission.form_data.get("employees_upload", {})
for row in spreadsheet.get("rows", []):
employee_id = row.get("Employee ID")
hours = row.get("Hours")
# ... write to HR database ...Spreadsheet fields are also parsed and stored when uploaded during an approval step, consistent with normal submission behaviour.