Python Developer Guide
Other documentationsDemoCommunityGitHub
  • Forest Admin
  • Getting started
    • How it works
    • Quick start
      • Flask
      • Django
    • Create your agent
    • Troubleshooting
    • Migrating legacy agents
      • Pre-requisites
      • Recommendations
      • Migration steps
      • Code transformations
        • API Charts
        • Live Queries
        • Smart Charts
        • Route overrides
        • Smart Actions
        • Smart Fields
        • Smart Relationships
        • Smart Segments
  • Data Sources
    • Getting Started
      • Collection selection
      • Naming conflicts
      • Query interface and Native Queries
        • Fields and projections
        • Filters
        • Aggregations
    • Provided data sources
      • SQLAlchemy
      • Django
        • Polymorphic relationships
    • Write your own
      • Translation strategy
        • Structure declaration
        • Capabilities declaration
        • Read implementation
        • Write implementation
        • Intra-data source Relationships
      • Contribute
  • Agent customization
    • Getting Started
    • Actions
      • Scope and context
      • Result builder
      • Static Forms
      • Widgets in Forms
      • Dynamic Forms
      • Form layout customization
      • Related data invalidation
    • Charts
      • Value
      • Objective
      • Percentage
      • Distribution
      • Leaderboard
      • Time-based
    • Fields
      • Add fields
      • Move, rename and remove fields
      • Override binary field mode
      • Override writing behavior
      • Override filtering behavior
      • Override sorting behavior
      • Validation
    • Hooks
      • Collection hook
      • Collection override
    • Pagination
    • Plugins
      • Write your own
    • Relationships
      • To a single record
      • To multiple records
      • Computed foreign keys
      • Under the hood
    • Search
    • Segments
  • Frontend customization
    • Smart Charts
      • Create a table chart
      • Create a bar chart
      • Create a cohort chart
      • Create a density map
    • Smart Views
      • Create a Map view
      • Create a Calendar view
      • Create a Shipping view
      • Create a Gallery view
      • Create a custom tinder-like validation view
      • Create a custom moderation view
  • Deploying to production
    • Environments
      • Deploy on AWS
      • Deploy on Heroku
      • Deploy on GCP
      • Deploy on Ubuntu
    • Development workflow
    • Using branches
    • Deploying your changes
    • Forest Admin CLI commands
      • init
      • login
      • branch
      • switch
      • set-origin
      • push
      • environments:create
      • environments:reset
      • deploy
  • Under the hood
    • .forestadmin-schema.json
    • Data Model
      • Typing
      • Relationships
    • Security & Privacy
Powered by GitBook
On this page
  • Form field properties
  • Examples
  • Example 1: Dynamic fields based on form values
  • Example 2: Conditional field display based on record data
  • Example 3: Conditional enum values based on both record data and form values
  • Example 4: Using has_field_changed to reset value
  • Example 5: Setting up default value based on the record
  • Example 6: Block field edition

Was this helpful?

  1. Agent customization
  2. Actions

Dynamic Forms

PreviousWidgets in FormsNextForm layout customization

Last updated 4 months ago

Was this helpful?

This is the official documentation of the forestadmin-agent-django and forestadmin-agent-flask Python agents.

Business logic often requires your forms to adapt to their context. Forest Admin makes this possible through a powerful way to extend your form's logic.

To make an action form dynamic, simply use functions instead of a static value on the compatible properties.

Note that:

  • Both synchronous and asynchronous functions are supported

  • The functions take the as the execute handler.

    • As such, they have access to the current values of the form.

    • And the records that the action will be applied to.

Form field properties

functions can be used for the following properties which are also available as static values:

Property
Description

is_required

Make the field required.

is_read_only

Make the field read-only.

default_value

Set the default value of the field.

In addition, depending on the field type, you can also use functions for the following properties:

Property
Description

enum_values

Change the list of possible values of the field when type == 'Enum'.

collection_name

Change the target collection of the field when type: 'Collection'.

And finally, those two extra properties are available and can only be used as functions:

Property
Description

if_

Only display the field if the function returns true.

value

Set the current value of the field.

Examples

Example 1: Dynamic fields based on form values

In this example we make a field required only if the user enters a value greater than 1000 in another field.

from typing import Union
from forestadmin.datasource_toolkit.decorators.action.context.single import ActionContextSingle
from forestadmin.datasource_toolkit.decorators.action.result_builder import ResultBuilder
from forestadmin.datasource_toolkit.interfaces.actions import ActionResult

async def execute(
    context: ActionContextSingle, result_builder: ResultBuilder
) -> Union[None, ActionResult]:
    pass

agent.customize_collection("customer").add_action("Charge credit card", {
    "form": [
        {
          "label": 'amount',
          "type": "Number",
        },
        {
          "label": 'description',
          "type": "String",
          # The field will only be required if the function returns true.
          "is_required": lambda context: context.form_values.get('amount', 0) > 1000,

        },
    ],
    "scope": "Single",
    "execute": execute,
})

Example 2: Conditional field display based on record data

Unlike the previous example, this one will only display the field if the record has a specific value.

It is still a dynamic field, but this time, the condition does not depend on the form values but on the record data.

from typing import Union
from forestadmin.datasource_toolkit.decorators.action.context.single import ActionContextSingle
from forestadmin.datasource_toolkit.decorators.action.result_builder import ResultBuilder
from forestadmin.datasource_toolkit.interfaces.actions import ActionResult

async def should_display_comment(context: ActionContextSingle):
    existing_comment = await context.get_record(['comment']).comment
    return existing_comment is None

async def execute(
    context: ActionContextSingle, result_builder: ResultBuilder
) -> Union[None, ActionResult]:
    # perform work here

agent.customize_collection("Product").add_action("Leave a review", {
    "form": [
        {
            "label": "Rating",
            "type": "Enum",
            "enum_values": ["1", "2", "3", "4", "5"]
        },
        {
            "label": "Put a comment",
            "type": "String",
            # Only display this field the data a does not have comment already
            "if_": should_display_comment,
        },
    ],
})

Example 3: Conditional enum values based on both record data and form values

You can mix and match the previous examples to create complex forms.

In this example, the form will display a different set of enum values depending on both the record data and the value of the form field.

The first field displays different denominations that can be used to address the customer, depending on the full name and gender of the customer.

The second field displays different levels of loudness depending on if the customer is Morgan Freeman, as to ensure that we never speak Very Loudly at him, for the sake of politeness.

from typing import Union
from forestadmin.datasource_toolkit.decorators.action.context.single import ActionContextSingle
from forestadmin.datasource_toolkit.decorators.action.result_builder import ResultBuilder
from forestadmin.datasource_toolkit.interfaces.actions import ActionResult

def get_loudly_enum_values(context: ActionContextSingle):
    # Enum values are computed based on the record data
    # Because we need to fetch the record data, we need to use an async function
    denomination = context.form_values.get("How should we refer to you?")
    if denomination == "Morgan Freeman":
      return ['Whispering', 'Softly', 'Loudly']
    else:
      return ['Softly', 'Loudly', 'Very Loudly']

def get_denomination_enum_values(context: ActionContextSingle):
    # Enum values are computed based on another form field value
    # (no need to use an async function here, but doing so would not be a problem)
    person = context.get_record(['firstName', 'lastName', 'gender'])
    base = [person['firstName'], f"{person['firstName']} {person['lastName']}"]
    if (person['gender'] == 'Female'):
      base.append(f"Mrs.  {person['lastName']}")
      base.append(f"Miss.  {person['lastName']}")
    else:
      base.append(f"Mr.  {person['lastName']}")
    return base

async def execute(
    context: ActionContextSingle, result_builder: ResultBuilder
) -> Union[None, ActionResult]:
    denomination = context.form_values["How should we refer to you?"]
    loudness = context.form_values["How loud should we say it?"]

    text = f"Hello {denomination}"
    if loudness == 'Whispering':
      text = text.lower()
    elif loudness == 'Loudly':
      text = text.upper()
    elif loudness == 'Very Loudly:
      text = f"{text.upper()}!!!"

    return result_builder.success(text)

agent.customize_collection("customer").add_action("Tell me a greeting", {
    "form": [
        {
            "label": "How should we refer to you?",
            "type": "Enum",
            "enum_values": get_denomination_enum_values
        }
        {
            "label": "How should we refer to you?",
            "type": "Enum",
            "enum_values": get_loudly_enum_values
        }
    ],
    "scope": "Single",
    "execute": execute,
})

Example 4: Using has_field_changed to reset value

In this example we reset a field based on change on another one.

from forestadmin.datasource_toolkit.decorators.action.context.single import ActionContextSingle
from forestadmin.datasource_toolkit.decorators.action.result_builder import ResultBuilder
from forestadmin.datasource_toolkit.interfaces.actions import ActionResult

def bank_name_value(context: ActionContextSingle):
    if context.has_field_changed('Bank Name'):
        if context.form_value["Bank Name"] == "CE":
            return "CEPAFRPPXXX"
        else:
            return "CCBPFRPPXXX"

agent.customize_collection("customer").add_action("Create banking identity", {
    "form": [
        {
            "label": "Bank Name",
            "type": "Enum",
            "is_required": True,
            "enum_values":  ['CE', 'BP'],
        },
        {
            "label": "BIC",
            "type": "String",
            "value": bank_name_value,
        }
    ],
    "scope": "Single",
    "execute": lambda context, result_builder: pass,
})

Example 5: Setting up default value based on the record

In this example we setup a the default value of a field based on the current record.

from forestadmin.datasource_toolkit.decorators.action.context.single import ActionContextSingle
from forestadmin.datasource_toolkit.decorators.action.result_builder import ResultBuilder
from forestadmin.datasource_toolkit.interfaces.actions import ActionResult

async def new_price_default_value(context: ActionContextSingle):
    return await context.get_record(['amount'])['amount']

agent.customize_collection("customer").add_action("Change order price", {
    "form": [
        {
            "label": 'New Price',
            "type": "Number",
            "default_value": new_price_default_value,
        }
    ],
    "scope": "Single",
    "execute": lambda context, result_builder: #...
})

Example 6: Block field edition

In this example we make a field read only based on a field from the current record.

from forestadmin.datasource_toolkit.decorators.action.context.single import ActionContextSingle
from forestadmin.datasource_toolkit.decorators.action.result_builder import ResultBuilder
from forestadmin.datasource_toolkit.interfaces.actions import ActionResult

async def new_price_default_value(context: ActionContextSingle):
    return await context.get_record(['amount'])['amount']

async def new_price_is_read_only(context: ActionContextSingle):
    return await context.get_record(['status'])['status'] == 'paid'

agent.customize_collection("customer").add_action("Change order price", {
    "form":  [
        {
            "label": 'New Price',
            "type": "Number",
            "default_value": new_price_default_value,
            "is_read_only": new_price_is_read_only
        }
    ],
    "scope": "Single",
    "execute": lambda context, result_builder: #...
})
same context object