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
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 value is None: continue elif 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)")