Get User Input

Last Updated: September 2025

HTML forms are the primary mechanism for obtaining input from users of your app.

First, you will need to configure your inputs using Tethys Gizmos by adding them to your controller like so:

  1. Define the options for the form gizmos in the controller in controllers.py.

from tethys_sdk.routing import controller
from tethys_sdk.gizmos import TextInput, DatePicker, SelectInput, Button
from .app import App

@controller
def home(request):
    """
    Controller for the app home page.
    """
    # Define form gizmos
    name_input = TextInput(
        display_text='Name',
        name='name'
    )

    owner_name_input = TextInput(
        display_text='Owner Name',
        name='owner_name'
    )

    measurement_type_input = SelectInput(
        display_text = 'Measurement Type',
        name='measurement_type',
        options=[('Streamflow', 'streamflow'),
                ('Temperature', 'temperature'),
                ('Water Level', 'water_level')]
        select2_options={'placeholder': 'Select a measurement type'}
    )

    date_added_input = DatePicker(
        name='date_added',
        display_text='Date Added',
        autoclose=True,
        format='MM d, yyyy',
        start_view='decade',
        today_button=False,
    )

    submit_button = Button(
        display_text='Submit',
        name='submit-button',
        icon='plus-square',
        style='success',
        attributes={'form': 'add-gauge-form'},
        submit=True
    )

    context = {
        'name_input': name_input,
        'owner_name_input': owner_name_input,
        'measurement_type_input': measurement_type_input,
        'date_added_input': date_added_input,
        'submit_button': submit_button,
    }

    return App.render(request, 'home.html', context)
  1. Add a form to home.html file by replacing the contents of the app_content block with the following:

{% block app_content %}
    <h1>Add New Gauge</h1>
    <form id="add-gauge-form" method="post">
        {% csrf_token %}
        {% gizmo name_input %}
        {% gizmo owner_name_input %}
        {% gizmo measurement_type_input %}
        {% gizmo date_added_input %}
        {% gizmo submit_button %}
    </form>
{% endblock %}

The form is composed of the the HTML <form> tag and various input gizmos inside it. We'll use the submit_button gizmo to submit the form. Also note the use of the csrf_token tag in the form. This is a security precaution that is required to be included in all the forms of your app (see the Cross Site Forgery protection article in the Django documentation for more details). Also note that the method attribute of the <form> element is set to post. This means the form will use the POST HTTP method to submit and transmit the data to the server. For an introduction to HTTP methods, see The Definitive Guide to GET vs POST.

Note

In this code block the form is being added to the main content area of the page. Forms can be added anywhere you need them in your app by changing the template.

  1. update your controller to handle form submissions by adding the highlighted dependency and updating the home controller.

from django.contrib import messages
...

@controller
def home(request):
    """
    Controller for the Add Gauge page.
    """

    name_error = ''
    owner_name_error = ''
    measurement_type_error = ''
    date_added_error = ''

    # Handle form submission
    if request.POST and 'submit-button' in request.POST:
        # Get values
        has_errors = False
        name = request.POST.get('name', None)
        owner_name = request.POST.get('owner_name', None)
        measurement_type = request.POST.get('measurement_type', None)
        date_added = request.POST.get('date_added', None)

        if not name:
            has_errors = True
            name_error = 'Name is required'

        if not owner_name:
            has_errors = True
            owner_name_error = 'Owner name is required'

        if not measurement_type:
            has_errors = True
            measurement_type_error = 'Measurement type is required'

        if not date_added:
            has_errors = True
            date_added_error = 'Date added is required'

        if not has_errors:
            messages.success(request, f"Added gauge {name}!")

    name_input = TextInput(
        display_text='Name',
        name='name'
    )

    owner_name_input = TextInput(
        display_text='Owner Name',
        name='owner_name'
    )

    measurement_type_input = SelectInput(
        display_text = 'Measurement Type',
        name='measurement_type',
        options=[('Streamflow', 'streamflow'),
                ('Temperature', 'temperature'),
                ('Water Level', 'water_level')]
        select2_options={'placeholder': 'Select a measurement type'}
    )

    date_added_input = DatePicker(
        name='date_added',
        display_text='Date Added',
        autoclose=True,
        format='MM d, yyyy',
        start_view='decade',
        today_button=False,
    )

    submit_button = Button(
        display_text='Submit',
        name='submit-button',
        icon='plus-square',
        style='success',
        attributes={'form': 'add-gauge-form'},
        submit=True
    )

    context = {
        "name_input": name_input,
        "owner_name_input": owner_name_input,
        "measurement_type_input": measurement_type_input,
        "date_added_input": date_added_input,
        "submit_button": submit_button
    }

    return App.render(request, "home.html", context)

The final product should look something like this:

../_images/user_input.png

Tip

Form Validation Pattern: The example above implements a common pattern for handling and validating form input. Generally, the steps are:

  1. Define a "value" variable for each input in the form and assign it the initial value for the input

  2. Define an "error" variable for each input to handle error messages and initially set them to the empty string

  3. Check to see if the form is submitted and if the form has been submitted:
    1. Extract the value of each input from the GET or POST parameters and overwrite the appropriate value variable from step 1

    2. Validate the value of each input, assigning an error message (if any) to the appropriate error variable from step 2 for each input with errors.

    3. If there are no errors, save or process the data.

    4. If there are errors continue on and re-render the form with error messages

  4. Define all gizmos and variables used to populate the form:
    1. Pass the value variable created in step 1 to the initial argument of the corresponding gizmo

    2. Pass the error variable created in step 2 to the error argument of the corresponding gizmo

  5. Render the page, passing all gizmos to the template through the context

Tip

For more details on form Gizmos see the Gizmos Documentation.