Source code for main.forms
"""
This module contains form classes for the web application. It includes:
1. ContactForm - for sending messages through the contact page.
2. UserProfileForm - for updating user profile information.
3. SimulationForm - for selecting simulation parameters.
4. TimeseriesForm - for selecting timeseries analysis parameters.
Each form class is built with the help of the Django Forms library and the
Crispy Forms package.
"""
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from django import forms
from .models import UserProfile
[docs]
class ContactForm(forms.Form):
"""
A form for sending messages through the contact page.
Attributes:
email: A field for the sender's email address.
title: A field for the message's title.
content: A field for the message's content.
send_to_me: A boolean field to indicate whether to send the message to the sender.
"""
email = forms.EmailField(label="Adres email")
title = forms.CharField(label="Tytuł")
content = forms.CharField(widget=forms.Textarea, label="Treść")
send_to_me = forms.BooleanField(required=False, label="Prześlij")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = "post"
self.helper.form_action = "contact"
self.helper.add_input(Submit("submit", "Wyślij"))
[docs]
class UserProfileForm(forms.ModelForm):
"""
A form for updating user profile information.
Attributes:
user: A field for the related user object.
bio: A field for the user's biography.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = "post"
self.helper.form_action = "userprofile"
self.helper.add_input(Submit("submit", "Wyślij"))
[docs]
class SimulationForm(forms.Form):
"""
A form for selecting simulation parameters.
Supports both range-based methods (requiring start/stop/step) and
predefined methods (using literature values, no range parameters).
Attributes:
option: A choice field for selecting the parameter to simulate.
start: An integer field for the starting value of the parameter range.
stop: An integer field for the ending value of the parameter range.
step: An integer field for the step size in the parameter range.
catchment_name: A field for the name of the catchment.
"""
OPTIONS = (
(
"Range-based Parameters",
(
("simulate_percent_slope", "Slope (%)"),
("simulate_area", "Area (ha)"),
("simulate_width", "Width (m)"),
("simulate_percent_impervious", "Impervious (%)"),
("simulate_percent_zero_imperv", "Zero-Imperv (%)"),
("simulate_curb_length", "Curb Length (m)"),
),
),
(
"Predefined Literature Values",
(
("simulate_n_imperv", "Manning's n - Impervious"),
("simulate_n_perv", "Manning's n - Pervious"),
("simulate_s_imperv", "Depression Storage - Impervious"),
("simulate_s_perv", "Depression Storage - Pervious"),
),
),
)
PREDEFINED_METHODS = frozenset(
{
"simulate_n_imperv",
"simulate_n_perv",
"simulate_s_imperv",
"simulate_s_perv",
}
)
MAX_SWEEP_STEPS = 100
option = forms.ChoiceField(choices=OPTIONS, widget=forms.Select(attrs={"class": "form-select"}))
start = forms.IntegerField(
min_value=0,
max_value=10000,
initial=1,
required=False,
widget=forms.NumberInput(attrs={"class": "form-control"}),
)
stop = forms.IntegerField(
min_value=0,
max_value=10000,
initial=10,
required=False,
widget=forms.NumberInput(attrs={"class": "form-control"}),
)
step = forms.IntegerField(
min_value=1,
max_value=10000,
initial=1,
required=False,
widget=forms.NumberInput(attrs={"class": "form-control"}),
)
catchment_name = forms.CharField(
widget=forms.Select(
choices=[("", "--- Upload a file first ---")],
attrs={"class": "form-select"},
),
)
def __init__(self, *args, catchment_choices=None, **kwargs):
super().__init__(*args, **kwargs)
if catchment_choices is not None:
self.fields["catchment_name"].widget.choices = catchment_choices
self.helper = FormHelper()
self.helper.form_method = "post"
self.helper.form_action = "simulation_view"
self.helper.add_input(Submit("submit", "Run Simulation"))
[docs]
def clean(self):
cleaned_data = super().clean()
option = cleaned_data.get("option")
if option not in self.PREDEFINED_METHODS:
for field in ("start", "stop", "step"):
if cleaned_data.get(field) is None:
self.add_error(field, "This field is required for the selected method.")
start = cleaned_data.get("start")
stop = cleaned_data.get("stop")
step = cleaned_data.get("step")
if start is not None and stop is not None and start > stop:
self.add_error("stop", "Stop must be >= start.")
if start is not None and stop is not None and step and step > 0:
if (stop - start) / step >= self.MAX_SWEEP_STEPS:
self.add_error(
"step",
f"Too many steps (max {self.MAX_SWEEP_STEPS}). "
"Increase step size or reduce range.",
)
return cleaned_data
[docs]
class TimeseriesForm(forms.Form):
"""
A form for timeseries analysis. Supports single-run and parameter sweep modes.
Attributes:
mode: Choice between single timeseries or parameter sweep.
feature: The subcatchment feature to vary (sweep mode only).
start: Start value for parameter sweep.
stop: Stop value for parameter sweep.
step: Step size for parameter sweep.
catchment_name: The subcatchment identifier.
"""
MODE_CHOICES = (
("single", "Single Timeseries"),
("sweep", "Parameter Sweep Timeseries"),
)
FEATURE_CHOICES = (
("PercSlope", "Slope (%)"),
("Area", "Area (ha)"),
("Width", "Width (m)"),
("PercImperv", "Impervious (%)"),
("CurbLength", "Curb Length (m)"),
)
mode = forms.ChoiceField(
choices=MODE_CHOICES,
widget=forms.Select(attrs={"class": "form-select"}),
)
feature = forms.ChoiceField(
choices=FEATURE_CHOICES,
required=False,
widget=forms.Select(attrs={"class": "form-select"}),
)
start = forms.FloatField(
min_value=0,
max_value=10000,
initial=0,
required=False,
widget=forms.NumberInput(attrs={"class": "form-control"}),
)
stop = forms.FloatField(
min_value=0,
max_value=10000,
initial=100,
required=False,
widget=forms.NumberInput(attrs={"class": "form-control"}),
)
step = forms.FloatField(
min_value=0.1,
initial=10,
required=False,
widget=forms.NumberInput(attrs={"class": "form-control"}),
)
catchment_name = forms.CharField(
widget=forms.Select(
choices=[("", "--- Upload a file first ---")],
attrs={"class": "form-select"},
),
)
def __init__(self, *args, catchment_choices=None, **kwargs):
super().__init__(*args, **kwargs)
if catchment_choices is not None:
self.fields["catchment_name"].widget.choices = catchment_choices
self.helper = FormHelper()
self.helper.form_method = "post"
self.helper.form_action = "timeseries"
self.helper.add_input(Submit("submit", "Run Analysis"))
MAX_SWEEP_STEPS = 100
[docs]
def clean(self):
cleaned_data = super().clean()
if cleaned_data.get("mode") == "sweep":
if not cleaned_data.get("feature"):
self.add_error("feature", "Required for parameter sweep mode.")
for field in ("start", "stop", "step"):
if cleaned_data.get(field) is None:
self.add_error(field, "Required for parameter sweep mode.")
start = cleaned_data.get("start")
stop = cleaned_data.get("stop")
step = cleaned_data.get("step")
if start is not None and stop is not None and start > stop:
self.add_error("stop", "Stop must be >= start.")
if start is not None and stop is not None and step and step > 0:
if (stop - start) / step >= self.MAX_SWEEP_STEPS:
self.add_error(
"step",
f"Too many steps (max {self.MAX_SWEEP_STEPS}). "
"Increase step size or reduce range.",
)
return cleaned_data