Make htmlfill only apply to certain named forms

Adam Batkin <[email protected]> Sun, 31 Jan 2010 20:17:15 +0000
Newsgroups gmane.comp.python.formencode
Message-ID <[email protected]>
Hi,

I have a use case where it would be good if htmlfill could operate only 
on a particular named form (<form name="foo">) and leave any other forms 
alone.

For example, if there were two forms on a page, one for logging in (with 
a username field) and one for creating a new user (also with a username 
field), the filling-in would only touch the one specified.

Attached is a proposed patch that adds this ability. My initial testing 
has found that it works pretty well.

It adds an optional argument to htmlfill.render(): form_name

If a form_name is not passed, htmlfill should act exactly the same as 
before. If a form_name IS passed however, htmlfill will only ever touch 
the named form (plus any unnamed forms, though I'm not sure if this is a 
good idea).

Thoughts? Comments? Suggestions?

Thanks,

-Adam Batkin

------------------------------------------------------------------------------
The Planet: dedicated and managed hosting, cloud storage, colocation
Stay online with enterprise data centers and the best network in the business
Choose flexible plans and management services without long-term contracts
Personal 24x7 support from experience hosting pros just a phone call away.
http://p.sf.net/sfu/theplanet-com

_______________________________________________
FormEncode-discuss mailing list
FormEncode-discuss-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f@public.gmane.org
https://lists.sourceforge.net/lists/listinfo/formencode-discuss
specify-form-name.diff (text/plain, 7.6 KB)
Index: formencode/htmlfill.py
===================================================================
--- formencode/htmlfill.py	(revision 4089)
+++ formencode/htmlfill.py	(working copy)
@@ -16,7 +16,7 @@
            auto_insert_errors=True, auto_error_formatter=None,
            text_as_default=False, listener=None, encoding=None,
            error_class='error', prefix_error=True,
-           force_defaults=True):
+           force_defaults=True, form_name=None):
     """
     Render the ``form`` (which should be a string) given the defaults
     and errors.  Defaults are the values that go in the input fields
@@ -67,6 +67,10 @@
     be cleared, radio and select controls will have no value selected,
     and textareas will be emptied. This defaults to ``True``, which is
     appropriate the defaults are the result of a form submission.
+    
+    ``form_name`` specifies the name of the form to be filled. By default,
+    all matching fields on the page are filled in, but this parameter only
+    matches fields inside forms with the given name attribute.
     """
     if defaults is None:
         defaults = {}
@@ -83,6 +87,7 @@
         prefix_error=prefix_error,
         error_class=error_class,
         force_defaults=force_defaults,
+        form_name=form_name,
         )
     p.feed(form)
     p.close()
@@ -185,7 +190,7 @@
                  add_attributes=None, listener=None,
                  auto_error_formatter=None,
                  text_as_default=False, encoding=None, prefix_error=True,
-                 force_defaults=True):
+                 force_defaults=True, form_name=None):
         RewritingParser.__init__(self)
         self.source = None
         self.lines = None
@@ -216,6 +221,8 @@
         self.encoding = encoding
         self.prefix_error = prefix_error
         self.force_defaults = force_defaults
+        self.form_name = form_name
+        self.current_form_name = None
     
     def str_compare(self, str1, str2):
         """
@@ -282,13 +289,17 @@
     def handle_starttag(self, tag, attrs, startend=False):
         self.write_pos()
         if tag == 'input':
-            self.handle_input(attrs, startend)
+            if self.can_handle_field():
+                self.handle_input(attrs, startend)
         elif tag == 'textarea':
-            self.handle_textarea(attrs)
+            if self.can_handle_field():
+                self.handle_textarea(attrs)
         elif tag == 'select':
-            self.handle_select(attrs)
+            if self.can_handle_field():
+                self.handle_select(attrs)
         elif tag == 'option':
-            self.handle_option(attrs)
+            if self.can_handle_field():
+                self.handle_option(attrs)
             return
         elif tag == 'form:error':
             self.handle_error(attrs)
@@ -296,6 +307,9 @@
         elif tag == 'form:iferror':
             self.handle_iferror(attrs)
             return
+        elif tag == 'form':
+            self.handle_form_start(attrs)
+            return
         else:
             return
         if self.listener:
@@ -304,24 +318,39 @@
     def handle_endtag(self, tag):
         self.write_pos()
         if tag == 'textarea':
-            self.handle_end_textarea()
+            if self.can_handle_field():
+                self.handle_end_textarea()
         elif tag == 'select':
-            self.handle_end_select()
+            if self.can_handle_field():
+                self.handle_end_select()
         elif tag == 'form:iferror':
             self.handle_end_iferror()
+        elif tag == 'form':
+            self.handle_form_end()
 
     def handle_startendtag(self, tag, attrs):
         return self.handle_starttag(tag, attrs, True)
 
     def handle_iferror(self, attrs):
         name = self.get_attr(attrs, 'name')
+        form_name = self.get_attr(attrs, 'form_name')
         notted = False
         if name.startswith('not '):
             notted = True
             name = name.split(None, 1)[1]
         assert name, "Name attribute in <iferror> required (%s)" % self.getpos()
         self.in_error = name
-        ok = self.errors.get(name)
+
+        if self.form_name and form_name:
+            if self.form_name == form_name:
+                ok = self.errors.get(name)
+            else:
+                ok = False
+        elif not self.can_handle_field():
+            ok = False
+        else:
+            ok = self.errors.get(name)
+
         if notted:
             ok = not ok
         if not ok:
@@ -335,18 +364,23 @@
 
     def handle_error(self, attrs):
         name = self.get_attr(attrs, 'name')
+        form_name = self.get_attr(attrs, 'form_name')
         formatter = self.get_attr(attrs, 'format') or 'default'
         if name is None:
             name = self.in_error
         assert name is not None, (
             "Name attribute in <form:error> required if not contained in "
             "<form:iferror> (%i:%i)" % self.getpos())
-        error = self.errors.get(name, '')
-        if error:
-            error = self.error_formatters[formatter](error)
-            self.write_text(error)
+        if ((form_name and self.form_name == form_name)
+            or (not form_name and (not self.current_form_name or self.form_name == self.current_form_name))
+            or not self.form_name
+            or self.in_error):
+            error = self.errors.get(name, '')
+            if error:
+                error = self.error_formatters[formatter](error)
+                self.write_text(error)
+            self.used_errors[name] = 1
         self.skip_next = True
-        self.used_errors[name] = 1
 
     def handle_input(self, attrs, startend):
         t = (self.get_attr(attrs, 'type') or 'text').lower()
@@ -522,7 +556,16 @@
                 break
         else:
             self._content.insert(0, text)
+    
+    def handle_form_start(self, attrs):
+        self.current_form_name = self.get_attr(attrs, 'name')
 
+    def handle_form_end(self):
+        self.current_form_name = None
+
+    def can_handle_field(self):
+        return not self.form_name or not self.current_form_name or self.current_form_name == self.form_name
+
 # This can potentially be extended globally
 default_formatter_dict = {'default': default_formatter,
                           'none': none_formatter,
Index: docs/htmlfill.txt
===================================================================
--- docs/htmlfill.txt	(revision 4089)
+++ docs/htmlfill.txt	(working copy)
@@ -45,15 +45,20 @@
 filling the form with error messages.  It defines two special tags for
 this purpose:
 
-``<form:error name="field_name" format="formatter">``:
+``<form:error name="field_name" format="formatter" form_name="formname">``:
     This tag is eliminated completely if there is no error for the
     named field.  Otherwise the error is passed through the given
     formatter (``"default"`` if no ``format`` attribute is given).
-``<form:iferror name="field_name">...</form:iferror>``:
+    If a ``form_name`` is specified AND a ``form_name`` is passed
+    when rendering, then this error will only be displayed if it is
+    inside the named form (or an unnamed form) or a ``<form:iferror>`
+    with the same ``form_name``.
+``<form:iferror name="field_name" form_name="formname">...</form:iferror>``:
     If the named field doesn't have an error, everything between the
     tags will be eliminated.  Use ``name="not field_name"`` to invert
     the behavior (i.e., include text only if there are no errors for
-    the field).
+    the field). If a ``form_name`` is specified AND a ``form_name``
+    is passed when rendering, the action of this tag is inverted.
 
 Formatters are functions that take the error text as a single
 argument.  (In the future they may take extra arguments as well.)