Ticket #11797: test_client_form_parsing.diff

File test_client_form_parsing.diff, 11.0 KB (added by Joshua Russo, 14 years ago)

updated to the current trunk and improved

  • django/test/client.py

     
    11import urllib
    22from urlparse import urlparse, urlunparse, urlsplit
     3from HTMLParser import HTMLParser
    34import sys
    45import os
    56import re
     
    1314from django.core.handlers.base import BaseHandler
    1415from django.core.handlers.wsgi import WSGIRequest
    1516from django.core.signals import got_request_exception
    16 from django.http import SimpleCookie, HttpRequest, QueryDict
     17from django.http import SimpleCookie, HttpRequest, HttpResponse, QueryDict
    1718from django.template import TemplateDoesNotExist
    1819from django.test import signals
    1920from django.utils.functional import curry
     
    8485
    8586        return response
    8687
     88
     89class FormHTMLParser(HTMLParser):
     90    """
     91    Exctracts the list of forms and fields with their values to
     92    facilitate re-submission
     93    """
     94
     95    _tag_list = ['form', 'input', 'textarea', 'select', 'option']
     96
     97    def __init__(self):
     98        self._curTagName = None
     99        self._curTagType = None
     100        self.forms = None
     101        self.forms_count = 0
     102        HTMLParser.__init__(self)
     103
     104    def handle_starttag(self, tag, attrs):
     105        if tag in self._tag_list:
     106            attrs = dict(attrs)
     107            self._curTagType = tag
     108
     109            if tag == 'form':
     110                if self.forms is None:
     111                    self.forms = {}
     112                    self.forms_count = 0
     113                # We can use either the name or id here because it will
     114                # not be passed back to the server. It's only used for
     115                # easier (more plain) reference in testing code.
     116                if attrs.has_key('name'):
     117                    self._curForm = attrs['name']
     118                elif attrs.has_key('id'):
     119                    self._curForm = attrs['id']
     120                else:
     121                    # ? Should this be a string (for consistancy) or a real number?
     122                    self._curForm = str(self.forms_count)
     123                self.forms[self._curForm] = {}
     124                self.forms_count = self.forms_count + 1
     125
     126            elif tag == 'input' and attrs['type'] not in ('checkbox', 'radio'):
     127                if attrs.has_key('name'):
     128                    if not attrs.has_key('value'):
     129                        attrs['value'] = ''
     130                    self.forms[self._curForm][attrs['name']] = attrs['value']
     131                    self._curTagName = attrs['name']
     132
     133            elif tag == 'input' and attrs['type'] in ('checkbox', 'radio'):
     134                if attrs.has_key('checked'):
     135                    if not attrs.has_key('value'):
     136                        attrs['value'] = 'on'
     137                    if self.forms[self._curForm].has_key(attrs['name']):
     138                        self.forms[self._curForm][attrs['name']].append(attrs['value'])
     139                    else:
     140                        self.forms[self._curForm][attrs['name']] = [attrs['value']]
     141                    self._curTagName = attrs['name']
     142
     143            elif tag == 'textarea':
     144                self.forms[self._curForm][attrs['name']] = ''
     145                self._curTagName = attrs['name']
     146
     147            elif tag == 'select':
     148                self.forms[self._curForm][attrs['name']] = []
     149                self._curTagName = attrs['name']
     150
     151            elif tag == 'option':
     152                if attrs.has_key('selected'):
     153                    self.forms[self._curForm][self._curTagName].append(attrs['value'])
     154
     155    def handle_data(self, data):
     156        if self._curTagName:
     157            if self._curTagType == 'textarea':
     158                # This is to ensure that there is always just a \r\n, as is the observed
     159                # behavior of web browsers.
     160                self.forms[self._curForm][self._curTagName] = data.replace('\r\n', '\n').replace('\n', '\r\n')
     161
     162    def handle_endtag(self, tag):
     163        if self._curTagName and tag != 'option':
     164            self._curTagName = None
     165
     166class FieldNotFound(KeyError):
     167    "Raised when a requested field or form is not found"
     168    pass
     169
     170def castHttpResponse(curResponse):
     171
     172    class _TestHttpResponse(curResponse.__class__):
     173
     174        def form_data(self, search_name):
     175            """
     176            Returns the entire set of form values associated with form of the given field or form name
     177            """
     178            if getattr(self, "_form_data", None) is None:
     179                self._form_data = self._get_form_data()
     180            if self._form_data.has_key(search_name):
     181                return self._form_data[search_name]
     182            for form_name, data in self._form_data.items():
     183                if data.has_key(search_name):
     184                    return data
     185            raise FieldNotFound("A field or form with the name %s was not found." % search_name)
     186
     187        def _get_form_data(self):
     188            curParser = FormHTMLParser()
     189            curParser.feed(self.content)
     190            curParser.close()
     191            return curParser.forms
     192   
     193    curResponse.__class__ = _TestHttpResponse
     194   
     195    return curResponse
     196
     197
    87198def store_rendered_templates(store, signal, sender, template, context, **kwargs):
    88199    """
    89200    Stores templates and contexts that are rendered.
     
    246357                exc_info = self.exc_info
    247358                self.exc_info = None
    248359                raise exc_info[1], None, exc_info[2]
     360           
     361            # Add the form field processing to response object
     362            response = castHttpResponse(response)
    249363
    250364            # Save the client and request that stimulated the response.
    251365            response.client = self
  • docs/topics/testing.txt

     
    836836            >>> response = client.get('/foo/')
    837837            >>> response.context['name']
    838838            'Arthur'
     839   
     840    .. method:: form_data(name)
     841   
     842        .. versionadded:: 1.2
     843       
     844        Returns a dictionary of values for a given form. If you give a name to your
     845        buttons their values will also be returned. You can supply either the name or
     846        id of the form or the name of any field or button and you will receive all
     847        values for the form.
     848       
     849            >>> response = client.get('/foo/')
     850            >>> response.form_data('subject_field')
     851            {'subject_field': 'Please visit my site', 'email_field': 'joe@xyz.com'}
     852       
     853        The resulting dictionary can then be used to easily change form data and
     854        submit the form.
     855       
     856            >>> my_form_data = response.form_data('subject_field')
     857            >>> my_form_data['email_field'] = 'mark@mycompany.com'
     858            >>> response = client.post('/foo/', my_form_data)
     859 
    839860
    840861    .. attribute:: request
    841862
  • tests/regressiontests/test_client_regress/models.py

     
    821821        response = self.client.post("/test_client_regress/parse_unicode_json/", json,
    822822                                    content_type="application/json; charset=koi8-r")
    823823        self.assertEqual(response.content, json.encode('koi8-r'))
     824
     825class FormProcessingTests(TestCase):
     826    def test_extraction(self):
     827        "The form values can be extracted, submitted, and re-extracted reliably."
     828        # Regression test for #11797
     829        from django.test.client import FieldNotFound
     830
     831        response = self.client.get('/test_client_regress/form_processing/')
     832        self.failUnlessEqual(response.status_code, 200, 'Failed to retrieve the form test page. (Status code: %s)' % response.status_code)
     833
     834        curVals = response.form_data('frmTest')
     835        curVals2 = response.form_data('choiceFld')
     836
     837        self.assert_(curVals == curVals2, 'The form name did not return the same data as the field name.')
     838
     839        self.assertRaises(FieldNotFound, response.form_data, 'badField')
     840       
     841        response = self.client.post('/test_client_regress/form_processing/', curVals)
     842        self.failUnlessEqual(response.status_code, 200, 'Failed to retrieve the form test page. (Status code: %s)' % response.status_code)
     843        self.assert_(response.context['form'].errors == {}, 'There was a problem detected in the view: %s' % response.context['form'].errors)
     844
     845        curVals2 = response.form_data('charFld')
     846               
     847        self.assert_(curVals == curVals2, 'The test client did not return the same data after submission.')
     848
     849        curVals2['MultiChoiceFld'] = ['pt', 'es']
     850        response = self.client.post('/test_client_regress/form_processing/', curVals2)
     851        self.failUnlessEqual(response.status_code, 200, 'Failed to retrieve the form test page. (Status code: %s)' % response.status_code)
     852        self.assert_(response.context['form'].errors == {}, 'There was a problem detected in the view: %s' % response.context['form'].errors)
     853
     854        curVals3 = response.form_data('rChoiceFld')
     855        self.assert_(curVals != curVals3 and curVals3 == curVals2, 'The test client did not properly return the modification data after submission.')
  • tests/regressiontests/test_client_regress/urls.py

     
    2424    (r'^request_methods/$', views.request_methods_view),
    2525    (r'^check_unicode/$', views.return_unicode),
    2626    (r'^parse_unicode_json/$', views.return_json_file),
     27    (r'^form_processing/$', views.test_form_proc),
    2728)
  • tests/regressiontests/test_client_regress/views.py

     
    8686                            mimetype='application/json; charset=' + charset)
    8787    response['Content-Disposition'] = 'attachment; filename=testfile.json'
    8888    return response
     89
     90def test_form_proc(request):
     91    "A view that captures and returns the form fields processed by the test client"       
     92    from forms import TestFormProcForm
     93       
     94    if request.method == 'POST':
     95        form = TestFormProcForm(request.POST)       
     96        form.is_valid()       
     97    else:
     98        form = TestFormProcForm()       
     99    return render_to_response('form_view.html', {'form': form})
  • tests/templates/form_view.html

     
    22{% block title %}Submit data{% endblock %}
    33{% block content %}
    44<h1>{{ message }}</h1>
    5 <form method='post' action='.'>
     5<form method='post' action='.' name="frmTest">
    66{% if form.errors %}
    77<p class='warning'>Please correct the errors below:</p>
    88{% endif %}
Back to Top