eventcally/tests/form.py
2021-09-20 17:13:42 +02:00

133 lines
4.6 KiB
Python

import urllib.parse
from werkzeug.datastructures import MultiDict
# see https://www.w3.org/TR/html52/sec-forms.html
class Form:
def __init__(self, form):
assert form.name == "form" # feed me BeautifulSoup <form> tags
self.form = form
self._action = form.get("action", "")
self._method = form.get("method", "GET")
self._enctype = form.get("enctype", "")
self.fields = MultiDict()
self.buttons = {}
for field in form.find_all(("input", "button", "select", "textarea")):
name = field.get("name")
if not name:
continue
field_value = field.get("value")
if field.name == "select":
options = field.find_all("option")
selected_values = list()
for option in options:
if option.has_attr("selected"):
selected_values.append(option.get("value"))
if len(selected_values) == 0 and len(options) > 0:
selected_values.append(options[0].get("value"))
if field.has_attr("multiple"):
field_value = selected_values
elif len(selected_values) > 0:
field_value = selected_values[0]
elif field.name == "textarea":
field_value = field.text.strip()
self.fields.add(name, (field, field_value))
if field.name in ("input", "button"):
if field.get("type") == "submit":
self.buttons[name] = (
field.get("formaction"),
field.get("formmethod"),
)
def _get_default_button(self):
if self.buttons:
return next(iter(self.buttons.keys()))
def get_action(self, button=None, relative_to=""): # pragma: no cover
# Get default submit button if none specified
if button is None:
button = self._get_default_button()
# Use the submit button's formaction if available
action = None if button is None else self.buttons[button][0]
action = action or self._action
return urllib.parse.urljoin(relative_to, action)
def get_method(self, button=None): # pragma: no cover
if button is None:
button = self._get_default_button()
method = None if button is None else self.buttons[button][1]
return method or self._method
def _fill_impl(self, button, values):
filled = MultiDict()
for form_name, (field, default_value) in self.fields.items(multi=True):
# Skip disabled fields
if field.has_attr("disabled"): # pragma: no cover
continue
# Skip buttons that are not the submit button
is_button = (field.name == "button") or (
field.name == "input"
and field.get("type") in ("submit", "image", "reset", "button")
)
if is_button and form_name != button: # pragma: no cover
continue
# Skip radio buttons and checkboxes that are not checked
is_radio_or_checkbox = field.name == "input" and field.get("type") in (
"radio",
"checkbox",
)
if is_radio_or_checkbox and not field.has_attr("checked"):
continue
# Add the default value
if default_value is None: # pragma: no cover
if is_button:
default_value = "Submit"
elif is_radio_or_checkbox:
default_value = "on"
else:
default_value = ""
if type(default_value) is list:
filled.setlist(form_name, default_value)
else:
filled.add(form_name, default_value)
if self._enctype:
filled.add("content_type", self._enctype)
# Override any form values with our input
for key, value in values.items():
if key in filled:
del filled[key]
if type(value) is list:
filled.setlist(key, value)
else:
filled.add(key, value)
return filled
def fill(self, *args): # pragma: no cover
if len(args) == 0:
return self._fill_impl(self._get_default_button(), {})
elif len(args) == 1:
return self._fill_impl(self._get_default_button(), args[0])
elif len(args) == 2:
return self._fill_impl(args[0], args[1])
raise ValueError("Expected fill(values) or fill(button, values)")