Index: django/test/client.py
===================================================================
--- django/test/client.py	(revision 13063)
+++ django/test/client.py	(working copy)
@@ -1,5 +1,6 @@
 import urllib
 from urlparse import urlparse, urlunparse, urlsplit
+from HTMLParser import HTMLParser
 import sys
 import os
 import re
@@ -13,7 +14,7 @@
 from django.core.handlers.base import BaseHandler
 from django.core.handlers.wsgi import WSGIRequest
 from django.core.signals import got_request_exception
-from django.http import SimpleCookie, HttpRequest, QueryDict
+from django.http import SimpleCookie, HttpRequest, HttpResponse, QueryDict
 from django.template import TemplateDoesNotExist
 from django.test import signals
 from django.utils.functional import curry
@@ -84,6 +85,116 @@
 
         return response
 
+
+class FormHTMLParser(HTMLParser):
+    """
+    Exctracts the list of forms and fields with their values to
+    facilitate re-submission
+    """
+
+    _tag_list = ['form', 'input', 'textarea', 'select', 'option']
+
+    def __init__(self):
+        self._curTagName = None
+        self._curTagType = None
+        self.forms = None
+        self.forms_count = 0
+        HTMLParser.__init__(self)
+
+    def handle_starttag(self, tag, attrs):
+        if tag in self._tag_list:
+            attrs = dict(attrs)
+            self._curTagType = tag
+
+            if tag == 'form':
+                if self.forms is None:
+                    self.forms = {}
+                    self.forms_count = 0
+                # We can use either the name or id here because it will
+                # not be passed back to the server. It's only used for
+                # easier (more plain) reference in testing code.
+                if attrs.has_key('name'):
+                    self._curForm = attrs['name']
+                elif attrs.has_key('id'):
+                    self._curForm = attrs['id']
+                else:
+                    # ? Should this be a string (for consistancy) or a real number?
+                    self._curForm = str(self.forms_count)
+                self.forms[self._curForm] = {}
+                self.forms_count = self.forms_count + 1
+
+            elif tag == 'input' and attrs['type'] not in ('checkbox', 'radio'):
+                if attrs.has_key('name'):
+                    if not attrs.has_key('value'):
+                        attrs['value'] = ''
+                    self.forms[self._curForm][attrs['name']] = attrs['value']
+                    self._curTagName = attrs['name']
+
+            elif tag == 'input' and attrs['type'] in ('checkbox', 'radio'):
+                if attrs.has_key('checked'):
+                    if not attrs.has_key('value'):
+                        attrs['value'] = 'on'
+                    if self.forms[self._curForm].has_key(attrs['name']):
+                        self.forms[self._curForm][attrs['name']].append(attrs['value'])
+                    else:
+                        self.forms[self._curForm][attrs['name']] = [attrs['value']]
+                    self._curTagName = attrs['name']
+
+            elif tag == 'textarea':
+                self.forms[self._curForm][attrs['name']] = ''
+                self._curTagName = attrs['name']
+
+            elif tag == 'select':
+                self.forms[self._curForm][attrs['name']] = []
+                self._curTagName = attrs['name']
+
+            elif tag == 'option':
+                if attrs.has_key('selected'):
+                    self.forms[self._curForm][self._curTagName].append(attrs['value'])
+
+    def handle_data(self, data):
+        if self._curTagName:
+            if self._curTagType == 'textarea':
+                # This is to ensure that there is always just a \r\n, as is the observed
+                # behavior of web browsers.
+                self.forms[self._curForm][self._curTagName] = data.replace('\r\n', '\n').replace('\n', '\r\n')
+
+    def handle_endtag(self, tag):
+        if self._curTagName and tag != 'option':
+            self._curTagName = None
+
+class FieldNotFound(KeyError):
+    "Raised when a requested field or form is not found"
+    pass
+
+def castHttpResponse(curResponse):
+
+    class _TestHttpResponse(curResponse.__class__):
+
+        def form_data(self, search_name):
+            """
+            Returns the entire set of form values associated with form of the given field or form name
+            """
+            if getattr(self, "_form_data", None) is None:
+                self._form_data = self._get_form_data()
+            if self._form_data.has_key(search_name):
+                return self._form_data[search_name]
+            for form_name, data in self._form_data.items():
+                if data.has_key(search_name):
+                    return data
+            raise FieldNotFound("A field or form with the name %s was not found." % search_name)
+
+        def _get_form_data(self):
+            curParser = FormHTMLParser()
+            curParser.feed(self.content)
+            curParser.close()
+            return curParser.forms
+    
+    curResponse.__class__ = _TestHttpResponse
+    
+    return curResponse
+
+
 def store_rendered_templates(store, signal, sender, template, context, **kwargs):
     """
     Stores templates and contexts that are rendered.
@@ -246,6 +357,9 @@
                 exc_info = self.exc_info
                 self.exc_info = None
                 raise exc_info[1], None, exc_info[2]
+            
+            # Add the form field processing to response object
+            response = castHttpResponse(response)
 
             # Save the client and request that stimulated the response.
             response.client = self
Index: docs/topics/testing.txt
===================================================================
--- docs/topics/testing.txt	(revision 13063)
+++ docs/topics/testing.txt	(working copy)
@@ -836,6 +836,27 @@
             >>> response = client.get('/foo/')
             >>> response.context['name']
             'Arthur'
+    
+    .. method:: form_data(name)
+    
+    	.. versionadded:: 1.2
+    	
+    	Returns a dictionary of values for a given form. If you give a name to your 
+    	buttons their values will also be returned. You can supply either the name or 
+    	id of the form or the name of any field or button and you will receive all 
+    	values for the form. 
+    	
+    	    >>> response = client.get('/foo/')
+    	    >>> response.form_data('subject_field')
+    	    {'subject_field': 'Please visit my site', 'email_field': 'joe@xyz.com'}
+    	
+    	The resulting dictionary can then be used to easily change form data and
+    	submit the form.
+    	
+    	    >>> my_form_data = response.form_data('subject_field')
+    	    >>> my_form_data['email_field'] = 'mark@mycompany.com'
+    	    >>> response = client.post('/foo/', my_form_data)
+ 
 
     .. attribute:: request
 
Index: tests/regressiontests/test_client_regress/models.py
===================================================================
--- tests/regressiontests/test_client_regress/models.py	(revision 13063)
+++ tests/regressiontests/test_client_regress/models.py	(working copy)
@@ -821,3 +821,35 @@
         response = self.client.post("/test_client_regress/parse_unicode_json/", json,
                                     content_type="application/json; charset=koi8-r")
         self.assertEqual(response.content, json.encode('koi8-r'))
+
+class FormProcessingTests(TestCase):
+    def test_extraction(self):
+        "The form values can be extracted, submitted, and re-extracted reliably."
+        # Regression test for #11797
+        from django.test.client import FieldNotFound
+
+        response = self.client.get('/test_client_regress/form_processing/')
+        self.failUnlessEqual(response.status_code, 200, 'Failed to retrieve the form test page. (Status code: %s)' % response.status_code)
+
+        curVals = response.form_data('frmTest')
+        curVals2 = response.form_data('choiceFld')
+
+        self.assert_(curVals == curVals2, 'The form name did not return the same data as the field name.')
+
+        self.assertRaises(FieldNotFound, response.form_data, 'badField')
+	
+        response = self.client.post('/test_client_regress/form_processing/', curVals)
+        self.failUnlessEqual(response.status_code, 200, 'Failed to retrieve the form test page. (Status code: %s)' % response.status_code)
+	self.assert_(response.context['form'].errors == {}, 'There was a problem detected in the view: %s' % response.context['form'].errors)
+
+	curVals2 = response.form_data('charFld')
+		
+        self.assert_(curVals == curVals2, 'The test client did not return the same data after submission.')
+
+	curVals2['MultiChoiceFld'] = ['pt', 'es']
+        response = self.client.post('/test_client_regress/form_processing/', curVals2)
+        self.failUnlessEqual(response.status_code, 200, 'Failed to retrieve the form test page. (Status code: %s)' % response.status_code)
+	self.assert_(response.context['form'].errors == {}, 'There was a problem detected in the view: %s' % response.context['form'].errors)
+
+	curVals3 = response.form_data('rChoiceFld')
+	self.assert_(curVals != curVals3 and curVals3 == curVals2, 'The test client did not properly return the modification data after submission.')
Index: tests/regressiontests/test_client_regress/urls.py
===================================================================
--- tests/regressiontests/test_client_regress/urls.py	(revision 13063)
+++ tests/regressiontests/test_client_regress/urls.py	(working copy)
@@ -24,4 +24,5 @@
     (r'^request_methods/$', views.request_methods_view),
     (r'^check_unicode/$', views.return_unicode),
     (r'^parse_unicode_json/$', views.return_json_file),
+    (r'^form_processing/$', views.test_form_proc),
 )
Index: tests/regressiontests/test_client_regress/views.py
===================================================================
--- tests/regressiontests/test_client_regress/views.py	(revision 13063)
+++ tests/regressiontests/test_client_regress/views.py	(working copy)
@@ -86,3 +86,14 @@
                             mimetype='application/json; charset=' + charset)
     response['Content-Disposition'] = 'attachment; filename=testfile.json'
     return response
+
+def test_form_proc(request):
+    "A view that captures and returns the form fields processed by the test client"        
+    from forms import TestFormProcForm
+       
+    if request.method == 'POST':
+        form = TestFormProcForm(request.POST)        
+        form.is_valid()       
+    else:
+        form = TestFormProcForm()        
+    return render_to_response('form_view.html', {'form': form})
Index: tests/templates/form_view.html
===================================================================
--- tests/templates/form_view.html	(revision 13063)
+++ tests/templates/form_view.html	(working copy)
@@ -2,7 +2,7 @@
 {% block title %}Submit data{% endblock %}
 {% block content %}
 <h1>{{ message }}</h1>
-<form method='post' action='.'>
+<form method='post' action='.' name="frmTest">
 {% if form.errors %}
 <p class='warning'>Please correct the errors below:</p>
 {% endif %}
