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:
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)
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.
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:
Tip
Form Validation Pattern: The example above implements a common pattern for handling and validating form input. Generally, the steps are:
Define a "value" variable for each input in the form and assign it the initial value for the input
Define an "error" variable for each input to handle error messages and initially set them to the empty string
- Check to see if the form is submitted and if the form has been submitted:
Extract the value of each input from the GET or POST parameters and overwrite the appropriate value variable from step 1
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.
If there are no errors, save or process the data.
If there are errors continue on and re-render the form with error messages
- Define all gizmos and variables used to populate the form:
Pass the value variable created in step 1 to the
initialargument of the corresponding gizmoPass the error variable created in step 2 to the
errorargument of the corresponding gizmo
Render the page, passing all gizmos to the template through the context
Tip
For more details on form Gizmos see the Gizmos Documentation.