Commits

Gael Pasgrimaud committed 3021857

allow to use a custom Field class in Form

Comments (0)

Files changed (1)

webtest/__init__.py

 ## Form objects
 ########################################
 
-class Form(object):
-
-    """
-    This object represents a form that has been found in a page.
-    This has a couple useful attributes:
-
-    ``text``:
-        the full HTML of the form.
-
-    ``action``:
-        the relative URI of the action.
-
-    ``method``:
-        the method (e.g., ``'GET'``).
-
-    ``id``:
-        the id, or None if not given.
-
-    ``fields``:
-        a dictionary of fields, each value is a list of fields by
-        that name.  ``<input type=\"radio\">`` and ``<select>`` are
-        both represented as single fields with multiple options.
-    """
-
-    # @@: This really should be using Mechanize/ClientForm or
-    # something...
-
-    _tag_re = re.compile(r'<(/?)([a-z0-9_\-]*)([^>]*?)>', re.I)
-
-    def __init__(self, response, text):
-        self.response = response
-        self.text = text
-        self._parse_fields()
-        self._parse_action()
-
-    def _parse_fields(self):
-        in_select = None
-        in_textarea = None
-        fields = {}
-        for match in self._tag_re.finditer(self.text):
-            end = match.group(1) == '/'
-            tag = match.group(2).lower()
-            if tag not in ('input', 'select', 'option', 'textarea',
-                           'button'):
-                continue
-            if tag == 'select' and end:
-                assert in_select, (
-                    '%r without starting select' % match.group(0))
-                in_select = None
-                continue
-            if tag == 'textarea' and end:
-                assert in_textarea, (
-                    "</textarea> with no <textarea> at %s" % match.start())
-                in_textarea[0].value = html_unquote(self.text[in_textarea[1]:match.start()])
-                in_textarea = None
-                continue
-            if end:
-                continue
-            attrs = _parse_attrs(match.group(3))
-            if 'name' in attrs:
-                name = attrs.pop('name')
-            else:
-                name = None
-            if tag == 'option':
-                in_select.options.append((attrs.get('value'),
-                                          'selected' in attrs))
-                continue
-            if tag == 'input' and attrs.get('type') == 'radio':
-                field = fields.get(name)
-                if not field:
-                    field = Radio(self, tag, name, match.start(), **attrs)
-                    fields.setdefault(name, []).append(field)
-                else:
-                    field = field[0]
-                    assert isinstance(field, Radio)
-                field.options.append((attrs.get('value'),
-                                      'checked' in attrs))
-                continue
-            tag_type = tag
-            if tag == 'input':
-                tag_type = attrs.get('type', 'text').lower()
-            if tag_type == "select" and attrs.get("multiple"):
-                FieldClass = Field.classes.get("multiple_select", Field)
-            else:
-                FieldClass = Field.classes.get(tag_type, Field)
-            field = FieldClass(self, tag, name, match.start(), **attrs)
-            if tag == 'textarea':
-                assert not in_textarea, (
-                    "Nested textareas: %r and %r"
-                    % (in_textarea, match.group(0)))
-                in_textarea = field, match.end()
-            elif tag == 'select':
-                assert not in_select, (
-                    "Nested selects: %r and %r"
-                    % (in_select, match.group(0)))
-                in_select = field
-            fields.setdefault(name, []).append(field)
-        self.fields = fields
-
-    def _parse_action(self):
-        self.action = None
-        for match in self._tag_re.finditer(self.text):
-            end = match.group(1) == '/'
-            tag = match.group(2).lower()
-            if tag != 'form':
-                continue
-            if end:
-                break
-            attrs = _parse_attrs(match.group(3))
-            self.action = attrs.get('action', '')
-            self.method = attrs.get('method', 'GET')
-            self.id = attrs.get('id')
-            self.enctype = attrs.get('enctype', 'application/x-www-form-urlencoded')
-        else:
-            assert 0, "No </form> tag found"
-        assert self.action is not None, (
-            "No <form> tag found")
-
-    def __setitem__(self, name, value):
-        """
-        Set the value of the named field.  If there is 0 or multiple
-        fields by that name, it is an error.
-
-        Setting the value of a ``<select>`` selects the given option
-        (and confirms it is an option).  Setting radio fields does the
-        same.  Checkboxes get boolean values.  You cannot set hidden
-        fields or buttons.
-
-        Use ``.set()`` if there is any ambiguity and you must provide
-        an index.
-        """
-        fields = self.fields.get(name)
-        assert fields is not None, (
-            "No field by the name %r found (fields: %s)"
-            % (name, ', '.join(map(repr, self.fields.keys()))))
-        assert len(fields) == 1, (
-            "Multiple fields match %r: %s"
-            % (name, ', '.join(map(repr, fields))))
-        fields[0].value = value
-
-    def __getitem__(self, name):
-        """
-        Get the named field object (ambiguity is an error).
-        """
-        fields = self.fields.get(name)
-        assert fields is not None, (
-            "No field by the name %r found" % name)
-        assert len(fields) == 1, (
-            "Multiple fields match %r: %s"
-            % (name, ', '.join(map(repr, fields))))
-        return fields[0]
-
-    def set(self, name, value, index=None):
-        """
-        Set the given name, using ``index`` to disambiguate.
-        """
-        if index is None:
-            self[name] = value
-        else:
-            fields = self.fields.get(name)
-            assert fields is not None, (
-                "No fields found matching %r" % name)
-            field = fields[index]
-            field.value = value
-
-    def get(self, name, index=None, default=NoDefault):
-        """
-        Get the named/indexed field object, or ``default`` if no field
-        is found.
-        """
-        fields = self.fields.get(name)
-        if fields is None and default is not NoDefault:
-            return default
-        if index is None:
-            return self[name]
-        else:
-            fields = self.fields.get(name)
-            assert fields is not None, (
-                "No fields found matching %r" % name)
-            field = fields[index]
-            return field
-
-    def select(self, name, value, index=None):
-        """
-        Like ``.set()``, except also confirms the target is a
-        ``<select>``.
-        """
-        field = self.get(name, index=index)
-        assert isinstance(field, Select)
-        field.value = value
-
-    def submit(self, name=None, index=None, **args):
-        """
-        Submits the form.  If ``name`` is given, then also select that
-        button (using ``index`` to disambiguate)``.
-
-        Any extra keyword arguments are passed to the ``.get()`` or
-        ``.post()`` method.
-
-        Returns a response object.
-        """
-        fields = self.submit_fields(name, index=index)
-        uploads = self.upload_fields()
-        if uploads:
-            args["upload_files"] = uploads
-        if self.method != "GET":
-            args.setdefault("content_type",  self.enctype)
-        return self.response.goto(self.action, method=self.method,
-                                  params=fields, **args)
-
-    def upload_fields(self):
-        """
-        Return a list of file field tuples of the form:
-            (field name, file name)
-        or
-            (field name, file name, file contents).
-        """
-        uploads = []
-        for name, fields in self.fields.items():
-            for field in fields:
-                if isinstance(field, File) and field.value:
-                    uploads.append([name] + list(field.value))
-        return uploads
-
-    def submit_fields(self, name=None, index=None):
-        """
-        Return a list of ``[(name, value), ...]`` for the current
-        state of the form.
-        """
-        submit = []
-        if name is not None:
-            field = self.get(name, index=index)
-            submit.append((field.name, field.value_if_submitted()))
-        for name, fields in self.fields.items():
-            if name is None:
-                continue
-            for field in fields:
-                value = field.value
-                if value is None:
-                    continue
-                if isinstance(field, File):
-                    # skip file uploads; they need to be accounted
-                    # for differently
-                    continue
-                if isinstance(value, list):
-                    for item in value:
-                        submit.append((name, item))
-                else:
-                    submit.append((name, value))
-        return submit
-
 
 _attr_re = re.compile(r'([^= \n\r\t]+)[ \n\r\t]*(?:=[ \n\r\t]*(?:"([^"]*)"|\'([^\']*)\'|([^"\'][^ \n\r\t>]*)))?', re.S)
 
 
 Field.classes['image'] = Submit
 
+class Form(object):
+
+    """
+    This object represents a form that has been found in a page.
+    This has a couple useful attributes:
+
+    ``text``:
+        the full HTML of the form.
+
+    ``action``:
+        the relative URI of the action.
+
+    ``method``:
+        the method (e.g., ``'GET'``).
+
+    ``id``:
+        the id, or None if not given.
+
+    ``fields``:
+        a dictionary of fields, each value is a list of fields by
+        that name.  ``<input type=\"radio\">`` and ``<select>`` are
+        both represented as single fields with multiple options.
+    """
+
+    # @@: This really should be using Mechanize/ClientForm or
+    # something...
+
+    _tag_re = re.compile(r'<(/?)([a-z0-9_\-]*)([^>]*?)>', re.I)
+
+    FieldClass = Field
+
+    def __init__(self, response, text):
+        self.response = response
+        self.text = text
+        self._parse_fields()
+        self._parse_action()
+
+    def _parse_fields(self):
+        in_select = None
+        in_textarea = None
+        fields = {}
+        for match in self._tag_re.finditer(self.text):
+            end = match.group(1) == '/'
+            tag = match.group(2).lower()
+            if tag not in ('input', 'select', 'option', 'textarea',
+                           'button'):
+                continue
+            if tag == 'select' and end:
+                assert in_select, (
+                    '%r without starting select' % match.group(0))
+                in_select = None
+                continue
+            if tag == 'textarea' and end:
+                assert in_textarea, (
+                    "</textarea> with no <textarea> at %s" % match.start())
+                in_textarea[0].value = html_unquote(self.text[in_textarea[1]:match.start()])
+                in_textarea = None
+                continue
+            if end:
+                continue
+            attrs = _parse_attrs(match.group(3))
+            if 'name' in attrs:
+                name = attrs.pop('name')
+            else:
+                name = None
+            if tag == 'option':
+                in_select.options.append((attrs.get('value'),
+                                          'selected' in attrs))
+                continue
+            if tag == 'input' and attrs.get('type') == 'radio':
+                field = fields.get(name)
+                if not field:
+                    field = self.FieldClass.classes['radio'](self, tag, name, match.start(), **attrs)
+                    fields.setdefault(name, []).append(field)
+                else:
+                    field = field[0]
+                    assert isinstance(field, self.FieldClass.classes['radio'])
+                field.options.append((attrs.get('value'),
+                                      'checked' in attrs))
+                continue
+            tag_type = tag
+            if tag == 'input':
+                tag_type = attrs.get('type', 'text').lower()
+            if tag_type == "select" and attrs.get("multiple"):
+                FieldClass = self.FieldClass.classes.get("multiple_select", self.FieldClass)
+            else:
+                FieldClass = self.FieldClass.classes.get(tag_type, self.FieldClass)
+            field = FieldClass(self, tag, name, match.start(), **attrs)
+            if tag == 'textarea':
+                assert not in_textarea, (
+                    "Nested textareas: %r and %r"
+                    % (in_textarea, match.group(0)))
+                in_textarea = field, match.end()
+            elif tag == 'select':
+                assert not in_select, (
+                    "Nested selects: %r and %r"
+                    % (in_select, match.group(0)))
+                in_select = field
+            fields.setdefault(name, []).append(field)
+        self.fields = fields
+
+    def _parse_action(self):
+        self.action = None
+        for match in self._tag_re.finditer(self.text):
+            end = match.group(1) == '/'
+            tag = match.group(2).lower()
+            if tag != 'form':
+                continue
+            if end:
+                break
+            attrs = _parse_attrs(match.group(3))
+            self.action = attrs.get('action', '')
+            self.method = attrs.get('method', 'GET')
+            self.id = attrs.get('id')
+            self.enctype = attrs.get('enctype', 'application/x-www-form-urlencoded')
+        else:
+            assert 0, "No </form> tag found"
+        assert self.action is not None, (
+            "No <form> tag found")
+
+    def __setitem__(self, name, value):
+        """
+        Set the value of the named field.  If there is 0 or multiple
+        fields by that name, it is an error.
+
+        Setting the value of a ``<select>`` selects the given option
+        (and confirms it is an option).  Setting radio fields does the
+        same.  Checkboxes get boolean values.  You cannot set hidden
+        fields or buttons.
+
+        Use ``.set()`` if there is any ambiguity and you must provide
+        an index.
+        """
+        fields = self.fields.get(name)
+        assert fields is not None, (
+            "No field by the name %r found (fields: %s)"
+            % (name, ', '.join(map(repr, self.fields.keys()))))
+        assert len(fields) == 1, (
+            "Multiple fields match %r: %s"
+            % (name, ', '.join(map(repr, fields))))
+        fields[0].value = value
+
+    def __getitem__(self, name):
+        """
+        Get the named field object (ambiguity is an error).
+        """
+        fields = self.fields.get(name)
+        assert fields is not None, (
+            "No field by the name %r found" % name)
+        assert len(fields) == 1, (
+            "Multiple fields match %r: %s"
+            % (name, ', '.join(map(repr, fields))))
+        return fields[0]
+
+    def set(self, name, value, index=None):
+        """
+        Set the given name, using ``index`` to disambiguate.
+        """
+        if index is None:
+            self[name] = value
+        else:
+            fields = self.fields.get(name)
+            assert fields is not None, (
+                "No fields found matching %r" % name)
+            field = fields[index]
+            field.value = value
+
+    def get(self, name, index=None, default=NoDefault):
+        """
+        Get the named/indexed field object, or ``default`` if no field
+        is found.
+        """
+        fields = self.fields.get(name)
+        if fields is None and default is not NoDefault:
+            return default
+        if index is None:
+            return self[name]
+        else:
+            fields = self.fields.get(name)
+            assert fields is not None, (
+                "No fields found matching %r" % name)
+            field = fields[index]
+            return field
+
+    def select(self, name, value, index=None):
+        """
+        Like ``.set()``, except also confirms the target is a
+        ``<select>``.
+        """
+        field = self.get(name, index=index)
+        assert isinstance(field, Select)
+        field.value = value
+
+    def submit(self, name=None, index=None, **args):
+        """
+        Submits the form.  If ``name`` is given, then also select that
+        button (using ``index`` to disambiguate)``.
+
+        Any extra keyword arguments are passed to the ``.get()`` or
+        ``.post()`` method.
+
+        Returns a response object.
+        """
+        fields = self.submit_fields(name, index=index)
+        uploads = self.upload_fields()
+        if uploads:
+            args["upload_files"] = uploads
+        if self.method != "GET":
+            args.setdefault("content_type",  self.enctype)
+        return self.response.goto(self.action, method=self.method,
+                                  params=fields, **args)
+
+    def upload_fields(self):
+        """
+        Return a list of file field tuples of the form:
+            (field name, file name)
+        or
+            (field name, file name, file contents).
+        """
+        uploads = []
+        for name, fields in self.fields.items():
+            for field in fields:
+                if isinstance(field, File) and field.value:
+                    uploads.append([name] + list(field.value))
+        return uploads
+
+    def submit_fields(self, name=None, index=None):
+        """
+        Return a list of ``[(name, value), ...]`` for the current
+        state of the form.
+        """
+        submit = []
+        if name is not None:
+            field = self.get(name, index=index)
+            submit.append((field.name, field.value_if_submitted()))
+        for name, fields in self.fields.items():
+            if name is None:
+                continue
+            for field in fields:
+                value = field.value
+                if value is None:
+                    continue
+                if isinstance(field, File):
+                    # skip file uploads; they need to be accounted
+                    # for differently
+                    continue
+                if isinstance(value, list):
+                    for item in value:
+                        submit.append((name, item))
+                else:
+                    submit.append((name, value))
+        return submit
+
 ########################################
 ## Utility functions
 ########################################