Ejemplo n.º 1
0
class NapalmForm(BaseForm):
    form_type = HiddenField(default="napalm")
    abstract_service = True
    driver = SelectField(choices=controller.NAPALM_DRIVERS)
    use_device_driver = BooleanField(default=True)
    optional_args = DictField()
    group = ["driver", "use_device_driver", "optional_args"]
Ejemplo n.º 2
0
class RestCallForm(ServiceForm, ValidationForm):
    form_type = HiddenField(default="RestCallService")
    has_targets = BooleanField("Has Target Devices")
    call_type = SelectField(choices=(("GET", "GET"), ("POST", "POST"),
                                     ("PUT", "PUT"), ("DELETE", "DELETE")))
    rest_url = SubstitutionField()
    payload = DictSubstitutionField()
    params = DictSubstitutionField()
    headers = DictSubstitutionField()
    verify_ssl_certificate = BooleanField("Verify SSL Certificate")
    timeout = IntegerField(default=15)
    username = StringField()
    password = StringField()
    pass_device_properties = BooleanField()
    options = DictField()
    groups = {
        "Main Parameters": [
            "call_type",
            "rest_url",
            "payload",
            "params",
            "headers",
            "verify_ssl_certificate",
            "timeout",
            "username",
            "password",
            "pass_device_properties",
            "options",
        ],
        "Validation Parameters":
        ValidationForm.group,
    }
Ejemplo n.º 3
0
class TaskForm(BaseForm):
    template = "object"
    form_type = HiddenField(default="task")
    id = HiddenField()
    is_active = BooleanField("Is Active")
    name = StringField("Name")
    description = StringField("Description")
    start_date = DateField("Start Date")
    end_date = DateField("End Date")
    frequency = IntegerField("Frequency", default=0)
    frequency_unit = SelectField(
        "Frequency Unit",
        choices=(
            ("seconds", "Seconds"),
            ("minutes", "Minutes"),
            ("hours", "Hours"),
            ("days", "Days"),
        ),
    )
    crontab_expression = StringField("Crontab Expression")
    job = InstanceField("Job", instance_type="Job")
    scheduling_mode = SelectField(
        "Scheduling Mode",
        choices=(("standard", "Standard Scheduling"), ("cron",
                                                       "Crontab Scheduling")),
    )
    devices = MultipleInstanceField("Devices", instance_type="Device")
    pools = MultipleInstanceField("Pools", instance_type="Pool")
    payload = DictField("Payload")
Ejemplo n.º 4
0
class RestCallForm(ServiceForm):
    form_type = HiddenField(default="rest_call_service")
    call_type = SelectField(choices=(
        ("GET", "GET"),
        ("POST", "POST"),
        ("PUT", "PUT"),
        ("DELETE", "DELETE"),
        ("PATCH", "PATCH"),
    ))
    rest_url = StringField(substitution=True)
    payload = DictField(json_only=True, substitution=True)
    params = DictField(substitution=True)
    headers = DictField(substitution=True)
    verify_ssl_certificate = BooleanField("Verify SSL Certificate")
    timeout = IntegerField(default=15)
    username = StringField()
    password = PasswordField()
Ejemplo n.º 5
0
class NapalmForm(BaseForm):
    form_type = HiddenField(default="napalm")
    abstract_service = True
    driver = SelectField(choices=app.NAPALM_DRIVERS)
    use_device_driver = BooleanField(default=True)
    optional_args = DictField()
    group = {
        "commands": ["driver", "use_device_driver", "optional_args"],
        "default": "expanded",
    }
Ejemplo n.º 6
0
class AnsiblePlaybookForm(ServiceForm):
    form_type = HiddenField(default="ansible_playbook_service")
    playbook_path = SelectField("Playbook Path", choices=(), validation=False)
    arguments = StringField("Arguments (Ansible command line options)",
                            substitution=True)
    pass_device_properties = BooleanField(
        "Pass Device Inventory Properties (to be used "
        "in the playbook as {{name}} or {{ip_address}})")
    options = DictField("Options (passed to ansible as -e extra args)",
                        substitution=True)
Ejemplo n.º 7
0
class TaskForm(BaseForm):
    action = "eNMS.base.processData"
    form_type = HiddenField(default="task")
    id = HiddenField()
    name = StringField("Name", [InputRequired()])
    default_access = SelectField(choices=(
        ("creator", "Creator only"),
        ("public", "Public (all users)"),
        ("admin", "Admin Users only"),
    ))
    scheduling_mode = SelectField(
        "Scheduling Mode",
        choices=(("cron", "Crontab Scheduling"), ("standard",
                                                  "Standard Scheduling")),
    )
    description = StringField("Description")
    start_date = StringField("Start Date", type="date")
    end_date = StringField("End Date", type="date")
    frequency = IntegerField("Frequency", default=0)
    frequency_unit = SelectField(
        "Frequency Unit",
        choices=(
            ("seconds", "Seconds"),
            ("minutes", "Minutes"),
            ("hours", "Hours"),
            ("days", "Days"),
        ),
    )
    crontab_expression = StringField("Crontab Expression")
    initial_payload = DictField("Payload")

    @classmethod
    def form_init(cls):
        cls.configure_relationships("devices", "pools", "service")

    def validate(self):
        valid_form = super().validate()
        if self.name.data == "Bulk Edit":
            return valid_form
        no_date = self.scheduling_mode.data == "standard" and not self.start_date.data
        if no_date:
            self.start_date.errors.append("A start date must be set.")
        no_cron_expression = (self.scheduling_mode.data == "cron"
                              and not self.crontab_expression.data)
        if no_cron_expression:
            self.crontab_expression.errors.append(
                "A crontab expression must be set.")
        no_service = not self.service.data
        if no_service:
            self.service.errors.append("No service set.")
        return valid_form and not any(
            [no_date, no_cron_expression, no_service])
Ejemplo n.º 8
0
class NapalmForm(ConnectionForm):
    form_type = HiddenField(default="napalm")
    abstract_service = True
    driver = SelectField(choices=app.NAPALM_DRIVERS)
    use_device_driver = BooleanField(default=True)
    timeout = IntegerField(default=10)
    optional_args = DictField()
    groups = {
        "Napalm Parameters": {
            "commands": ["driver", "use_device_driver", "timeout", "optional_args"],
            "default": "expanded",
        },
        **ConnectionForm.groups,
    }
Ejemplo n.º 9
0
class IterationForm(ServiceForm):
    form_type = HiddenField(default="IterationService")
    has_targets = BooleanField("Has Target Devices")
    origin_of_values = SelectField(
        "Where Values come from",
        choices=(
            ("user_provided_values", "User-provided Values (dictionary)"),
            ("yaql_query", "YaQL Query on the Payload"),
        ),
    )
    user_provided_values = DictField(
        "Iteration Values for Iteration: User provided "
        "(Expect dictionary {'all' : [...]} or {'device-name' : [...], ...})"
    )
    yaql_query_values = SubstitutionField(
        "Iteration Values for Iteration: YaQL query on the payload"
    )
    variable_name = StringField("Iteration Variable Name")
    iterated_job = InstanceField("Job to run for each Value", instance_type="Job")
Ejemplo n.º 10
0
class AnsiblePlaybookForm(ServiceForm, ValidationForm):
    form_type = HiddenField(default="AnsiblePlaybookService")
    has_targets = BooleanField("Has Target Devices")
    playbook_path = NoValidationSelectField("Playbook Path", choices=())
    arguments = SubstitutionField("Arguments (Ansible command line options)")
    pass_device_properties = BooleanField(
        "Pass Device Inventory Properties (to be used "
        "in the playbook as {{name}} or {{ip_address}})")
    options = DictField("Options (passed to ansible as -e extra args)")
    groups = {
        "Main Parameters": [
            "playbook_path",
            "arguments",
            "pass_device_properties",
            "options",
        ],
        "Validation Parameters":
        ValidationForm.group,
    }
Ejemplo n.º 11
0
class TaskForm(BaseForm):
    template = "object"
    form_type = HiddenField(default="task")
    id = HiddenField()
    scheduling_mode = SelectField(
        "Scheduling Mode",
        choices=(("cron", "Crontab Scheduling"), ("standard",
                                                  "Standard Scheduling")),
    )
    name = StringField("Name", [InputRequired()])
    description = StringField("Description")
    start_date = DateField("Start Date")
    end_date = DateField("End Date")
    frequency = IntegerField("Frequency", default=0)
    frequency_unit = SelectField(
        "Frequency Unit",
        choices=(
            ("seconds", "Seconds"),
            ("minutes", "Minutes"),
            ("hours", "Hours"),
            ("days", "Days"),
        ),
    )
    crontab_expression = StringField("Crontab Expression")
    initial_payload = DictField("Payload")

    def validate(self):
        valid_form = super().validate()
        no_date = self.scheduling_mode.data == "standard" and not self.start_date.data
        if no_date:
            self.start_date.errors.append("A start date must be set.")
        no_cron_expression = (self.scheduling_mode.data == "cron"
                              and not self.crontab_expression.data)
        if no_cron_expression:
            self.crontab_expression.errors.append(
                "A crontab expression must be set.")
        no_service = not self.service.data
        if no_service:
            self.service.errors.append("No service set.")
        return valid_form and not any(
            [no_date, no_cron_expression, no_service])
Ejemplo n.º 12
0
class ServiceForm(BaseForm):
    template = "service"
    form_type = HiddenField(default="service")
    id = HiddenField()
    name = StringField("Name")
    type = StringField("Service Type")
    shared = BooleanField("Shared Service")
    scoped_name = StringField("Scoped Name", [InputRequired()])
    description = StringField("Description")
    device_query = StringField("Device Query")
    device_query_property = SelectField("Query Property Type",
                                        choices=(("name", "Name"),
                                                 ("ip_address", "IP address")))
    devices = MultipleInstanceField("Devices")
    pools = MultipleInstanceField("Pools")
    workflows = MultipleInstanceField("Workflows")
    waiting_time = IntegerField("Waiting time (in seconds)", default=0)
    send_notification = BooleanField("Send a notification")
    send_notification_method = SelectField(
        "Notification Method",
        choices=(("mail", "Mail"), ("slack", "Slack"), ("mattermost",
                                                        "Mattermost")),
    )
    notification_header = StringField(widget=TextArea(), render_kw={"rows": 5})
    include_device_results = BooleanField("Include Device Results")
    include_link_in_summary = BooleanField("Include Result Link in Summary")
    display_only_failed_nodes = BooleanField("Display only Failed Devices")
    mail_recipient = StringField("Mail Recipients (separated by comma)")
    number_of_retries = IntegerField("Number of retries", default=0)
    time_between_retries = IntegerField("Time between retries (in seconds)",
                                        default=10)
    maximum_runs = IntegerField("Maximum number of runs", default=1)
    skip = BooleanField("Skip")
    skip_query = StringField("Skip Query (Python)")
    vendor = StringField("Vendor")
    operating_system = StringField("Operating System")
    initial_payload = DictField()
    iteration_values = StringField("Iteration Values (Python Query)")
    iteration_variable_name = StringField("Iteration Variable Name",
                                          default="iteration_value")
    iteration_devices = StringField("Iteration Devices (Python Query)")
    iteration_devices_property = SelectField(
        "Iteration Devices Property",
        choices=(("name", "Name"), ("ip_address", "IP address")),
    )
    result_postprocessing = StringField(widget=TextArea(),
                                        render_kw={"rows": 7})
    multiprocessing = BooleanField("Multiprocessing")
    max_processes = IntegerField("Maximum number of processes", default=50)
    conversion_method = SelectField(choices=(
        ("none", "No conversion"),
        ("text", "Text"),
        ("json", "Json dictionary"),
        ("xml", "XML dictionary"),
    ))
    validation_method = SelectField(
        "Validation Method",
        choices=(
            ("none", "No validation"),
            ("text", "Validation by text match"),
            ("dict_included", "Validation by dictionary inclusion"),
            ("dict_equal", "Validation by dictionary equality"),
        ),
    )
    content_match = SubstitutionField("Content Match",
                                      widget=TextArea(),
                                      render_kw={"rows": 8})
    content_match_regex = BooleanField("Match content with Regular Expression")
    dict_match = DictSubstitutionField("Dictionary to Match Against")
    negative_logic = BooleanField("Negative logic")
    delete_spaces_before_matching = BooleanField(
        "Delete Spaces before Matching")
    query_fields = [
        "device_query",
        "skip_query",
        "iteration_values",
        "result_postprocessing",
    ]

    def validate(self):
        valid_form = super().validate()
        no_recipient_error = (self.send_notification.data
                              and self.send_notification_method.data == "mail"
                              and not self.mail_recipient.data
                              and not app.config["mail"]["recipients"])
        if no_recipient_error:
            self.mail_recipient.errors.append(
                "Please add at least one recipient for the mail notification.")
        bracket_error = False
        for query_field in self.query_fields:
            field = getattr(self, query_field)
            try:
                parse(field.data)
            except Exception as exc:
                bracket_error = True
                field.errors.append(f"Wrong python expression ({exc}).")
            if "{{" in field.data and "}}" in field.data:
                bracket_error = True
                field.errors.append(
                    "You cannot use variable substitution "
                    "in a field expecting a python expression.")
        conversion_validation_mismatch = (
            self.conversion_method.data == "text"
            and "dict" in self.validation_method.data
            or self.conversion_method.data in ("xml", "json")
            and "dict" not in self.validation_method.data)
        if conversion_validation_mismatch:
            self.conversion_method.errors.append(
                f"The conversion method is set to '{self.conversion_method.data}'"
                f" and the validation method to '{self.validation_method.data}' :"
                " these do not match.")
        return (valid_form and not no_recipient_error and not bracket_error
                and not conversion_validation_mismatch)
Ejemplo n.º 13
0
class JobForm(BaseForm):
    template = "job"
    form_type = HiddenField(default="job")
    id = HiddenField()
    type = StringField("Service Type")
    name = StringField("Name")
    description = StringField("Description")
    python_query = StringField("Python Query")
    query_property_type = SelectField("Query Property Type",
                                      choices=(("name", "Name"),
                                               ("ip_address", "IP address")))
    devices = MultipleInstanceField("Devices", instance_type="Device")
    pools = MultipleInstanceField("Pools", instance_type="Pool")
    waiting_time = IntegerField("Waiting time (in seconds)", default=0)
    send_notification = BooleanField("Send a notification")
    send_notification_method = SelectField(
        "Notification Method",
        choices=(
            ("mail_feedback_notification", "Mail"),
            ("slack_feedback_notification", "Slack"),
            ("mattermost_feedback_notification", "Mattermost"),
        ),
    )
    notification_header = StringField(widget=TextArea(), render_kw={"rows": 5})
    include_link_in_summary = BooleanField("Include Result Link in Summary")
    display_only_failed_nodes = BooleanField("Display only Failed Devices")
    mail_recipient = StringField("Mail Recipients (separated by comma)")
    number_of_retries = IntegerField("Number of retries", default=0)
    time_between_retries = IntegerField("Time between retries (in seconds)",
                                        default=10)
    start_new_connection = BooleanField("Start New Connection")
    skip = BooleanField("Skip")
    skip_python_query = StringField("Skip (Python Query)")
    vendor = StringField("Vendor")
    operating_system = StringField("Operating System")
    shape = SelectField(
        "Shape",
        choices=(
            ("box", "Box"),
            ("circle", "Circle"),
            ("square", "Square"),
            ("diamond", "Diamond"),
            ("triangle", "Triangle"),
            ("ellipse", "Ellipse"),
            ("database", "Database"),
        ),
    )
    size = IntegerField("Size", default=40)
    color = StringField("Color", default="#D2E5FF")
    initial_payload = DictField()
    iteration_targets = StringField("Iteration Targets (Python Query)")
    query_fields = ["python_query", "skip_python_query", "iteration_targets"]

    def validate(self) -> bool:
        valid_form = super().validate()
        no_recipient_error = (self.send_notification.data
                              and self.send_notification_method.data
                              == "mail_feedback_notification"
                              and not self.mail_recipient.data
                              and not controller.mail_recipients)
        bracket_error = False
        for query_field in self.query_fields:
            field = getattr(self, query_field)
            try:
                parse(field.data)
            except Exception as exc:
                bracket_error = True
                field.errors.append(f"Wrong python expression ({exc}).")
            if "{{" in field.data and "}}" in field.data:
                bracket_error = True
                field.errors.append(
                    "You cannot use variable substitution "
                    "in a field expecting a python expression.")
        if no_recipient_error:
            self.mail_recipient.errors.append(
                "Please add at least one recipient for the mail notification.")
        return valid_form and not no_recipient_error and not bracket_error
Ejemplo n.º 14
0
class ServiceForm(BaseForm):
    template = "service"
    form_type = HiddenField(default="service")
    id = HiddenField()
    name = StringField("Name")
    type = StringField("Service Type")
    shared = BooleanField("Shared Service")
    scoped_name = StringField("Scoped Name", [InputRequired()])
    description = StringField("Description")
    device_query = StringField(
        "Device Query", python=True, widget=TextArea(), render_kw={"rows": 2}
    )
    device_query_property = SelectField(
        "Query Property Type", choices=(("name", "Name"), ("ip_address", "IP address"))
    )
    devices = MultipleInstanceField("Devices")
    disable_result_creation = BooleanField("Disable Result Creation")
    pools = MultipleInstanceField("Pools")
    update_pools = BooleanField("Update pools before running")
    workflows = MultipleInstanceField("Workflows")
    waiting_time = IntegerField(
        "Time to Wait before next service is started (in seconds)", default=0
    )
    send_notification = BooleanField("Send a notification")
    send_notification_method = SelectField(
        "Notification Method",
        choices=(("mail", "Mail"), ("slack", "Slack"), ("mattermost", "Mattermost")),
    )
    notification_header = StringField(widget=TextArea(), render_kw={"rows": 5})
    include_device_results = BooleanField("Include Device Results")
    include_link_in_summary = BooleanField("Include Result Link in Summary")
    display_only_failed_nodes = BooleanField("Display only Failed Devices")
    mail_recipient = StringField("Mail Recipients (separated by comma)")
    reply_to = StringField("Reply-to Email Address")
    number_of_retries = IntegerField("Number of retries", default=0)
    time_between_retries = IntegerField("Time between retries (in seconds)", default=10)
    max_number_of_retries = IntegerField("Maximum number of retries", default=100)
    maximum_runs = IntegerField("Maximum number of runs", default=1)
    skip = BooleanField("Skip")
    skip_query = StringField(
        "Skip Query (Python)", python=True, widget=TextArea(), render_kw={"rows": 2}
    )
    skip_value = SelectField(
        "Skip Value",
        choices=(("True", "True"), ("False", "False")),
    )
    vendor = StringField("Vendor")
    operating_system = StringField("Operating System")
    iteration_values = StringField("Iteration Values", python=True)
    initial_payload = DictField()
    iteration_variable_name = StringField(
        "Iteration Variable Name", default="iteration_value"
    )
    iteration_devices = StringField("Iteration Devices", python=True)
    iteration_devices_property = SelectField(
        "Iteration Devices Property",
        choices=(("name", "Name"), ("ip_address", "IP address")),
    )
    preprocessing = StringField(type="code", python=True, widget=TextArea())
    postprocessing = StringField(type="code", python=True, widget=TextArea())
    postprocessing_mode = SelectField(
        choices=(
            ("always", "Always run"),
            ("success", "Run on success only"),
            ("failure", "Run on failure only"),
        )
    )
    public = BooleanField("Public")
    log_level = SelectField(
        "Logging",
        choices=((0, "Disable logging"), *enumerate(app.log_levels, 1)),
        default=1,
        validation=False,
    )
    multiprocessing = BooleanField("Multiprocessing")
    max_processes = IntegerField("Maximum number of processes", default=15)
    validation_condition = SelectField(
        choices=(
            ("success", "Run on success only"),
            ("failure", "Run on failure only"),
            ("always", "Always run"),
        )
    )
    conversion_method = SelectField(
        choices=(
            ("none", "No conversion"),
            ("text", "Text"),
            ("json", "Json dictionary"),
            ("xml", "XML dictionary"),
        )
    )
    validation_method = SelectField(
        "Validation Method",
        choices=(
            ("none", "No validation"),
            ("text", "Validation by text match"),
            ("dict_included", "Validation by dictionary inclusion"),
            ("dict_equal", "Validation by dictionary equality"),
        ),
    )
    content_match = StringField(
        "Content Match", widget=TextArea(), render_kw={"rows": 8}, substitution=True
    )
    content_match_regex = BooleanField("Match content with Regular Expression")
    dict_match = DictField("Dictionary to Match Against", substitution=True)
    negative_logic = BooleanField("Negative logic")
    delete_spaces_before_matching = BooleanField("Delete Spaces before Matching")
    run_method = SelectField(
        "Run Method",
        choices=(
            ("per_device", "Run the service once per device"),
            ("once", "Run the service once"),
        ),
    )

    def validate(self):
        valid_form = super().validate()
        no_recipient_error = (
            self.send_notification.data
            and self.send_notification_method.data == "mail"
            and not self.mail_recipient.data
        )
        if no_recipient_error:
            self.mail_recipient.errors.append(
                "Please add at least one recipient for the mail notification."
            )
        forbidden_name_error = self.scoped_name.data in ("Start", "End", "Placeholder")
        if forbidden_name_error:
            self.name.errors.append("This name is not allowed.")
        conversion_validation_mismatch = (
            self.conversion_method.data == "text"
            and "dict" in self.validation_method.data
            or self.conversion_method.data in ("xml", "json")
            and "dict" not in self.validation_method.data
        )
        if conversion_validation_mismatch:
            self.conversion_method.errors.append(
                f"The conversion method is set to '{self.conversion_method.data}'"
                f" and the validation method to '{self.validation_method.data}' :"
                " these do not match."
            )
        return (
            valid_form
            and not no_recipient_error
            and not conversion_validation_mismatch
            and not forbidden_name_error
        )
Ejemplo n.º 15
0
class UpdateInventoryForm(ServiceForm):
    form_type = HiddenField(default="update_inventory_service")
    update_dictionary = DictField()
Ejemplo n.º 16
0
class ExampleForm(ServiceForm):
    # Each service model must have an corresponding form.
    # The purpose of a form is twofold:
    # - Define how the service is displayed in the UI
    # - Check for each field that the user input is valid.
    # A service cannot be created/updated until all fields are validated.

    # The following line is mandatory: the default value must point
    # to the service.
    form_type = HiddenField(default="ExampleService")

    # string1 is defined as a "SelectField": it will be displayed as a
    # drop-down list in the UI.
    string1 = SelectField(choices=[("cisco",
                                    "Cisco"), ("juniper",
                                               "Juniper"), ("arista",
                                                            "Arista")])

    # String2 is a StringField, which is displayed as a standard textbox.
    # The "InputRequired" validator is used: this field is mandatory.
    string2 = StringField("String 2 (required)", [InputRequired()])

    # The main address field uses two validators:
    # - The input length must be comprised between 7 and 25 characters
    # - The input syntax must match that of an email address.
    mail_address = StringField(
        "Mail address", [Length(min=7, max=25), Email()])

    # This IP address validator will ensure the user input is a valid IPv4 address.
    # If it isn't, you can set the error message to be displayed in the GUI.
    ip_address = StringField(
        "IP address",
        [
            IPAddress(
                ipv4=True,
                message="Please enter an IPv4 address for the IP address field",
            )
        ],
    )

    # MAC address validator
    mac_address = StringField("MAC address", [MacAddress()])

    # The NumberRange validator will ensure the user input is an integer
    # between 3 and 8.
    number_in_range = IntegerField("Number in range",
                                   [NumberRange(min=3, max=8)])

    # The Regexp field will ensure the user input matches the regular expression.
    regex = StringField("Regular expression", [Regexp(r".*")])

    # URL validation, with or without TLD.
    url = StringField(
        "URL",
        [
            URL(
                require_tld=True,
                message="An URL with TLD is required for the url field",
            )
        ],
    )

    # The NoneOf validator lets you define forbidden value for a field.
    exclusion_field = StringField(
        "Exclusion field",
        [
            NoneOf(
                ("a", "b", "c"),
                message=("'a', 'b', and 'c' are not valid "
                         "inputs for the exclusion field"),
            )
        ],
    )
    an_integer = IntegerField()
    a_float = FloatField()

    # If validator the user input is more complex, you can create a python function
    # to implement the validation mechanism.
    # Here, the custom_integer field will be validated by the "validate_custom_integer"
    # function below.
    # That function will check that the custom integer value is superior to the product
    # of "an_integer" and "a_float".
    # You must raise a "ValidationError" when the validation fails.
    custom_integer = IntegerField("Custom Integer")

    # A SelectMultipleField will be displayed as a drop-down list that allows
    # multiple selection.
    a_list = SelectMultipleField(
        choices=[("value1", "Value 1"), ("value2",
                                         "Value 2"), ("value3", "Value 3")])
    a_dict = DictField()

    # A BooleanField is displayed as a check box.
    boolean1 = BooleanField()
    boolean2 = BooleanField("Boolean N°1")

    def validate_custom_integer(self, field: IntegerField) -> None:
        product = self.an_integer.data * self.a_float.data
        if field.data > product:
            raise ValidationError("Custom integer must be less than the "
                                  "product of 'An integer' and 'A float'")