Anonymous committed ef7e1f9

unicode: Changed the way re-encoding of form field submission works so that
file uploads are no longer completely broken. Added tests for this as well.

Comments (0)

Files changed (6)


 from pprint import pformat
 from urllib import urlencode
 from django.utils.datastructures import MultiValueDict
-from django.utils.encoding import smart_str, iri_to_uri
+from django.utils.encoding import smart_str, iri_to_uri, force_unicode
     def _set_encoding(self, val):
-        Sets the encoding used for GET/POST accesses.
+        Sets the encoding used for GET/POST accesses. If the GET or POST
+        dictionary has already been created it is removed and recreated on the
+        next access (so that it is decoded correctly).
         self._encoding = val
         if hasattr(self, '_get'):
-            self.GET.encoding = val
+            del self._get
         if hasattr(self, '_post'):
-            self.POST.encoding = val
+            del self._post
     def _get_encoding(self):
         return self._encoding
             self.encoding = encoding
         self._mutable = True
         for key, value in parse_qsl((query_string or ''), True): # keep_blank_values=True
-            self.appendlist(key, value)
+            self.appendlist(force_unicode(key, errors='replace'), force_unicode(value, errors='replace'))
         self._mutable = mutable
     def _assert_mutable(self):
         if not self._mutable:
             raise AttributeError, "This QueryDict instance is immutable"
-    def __getitem__(self, key):
-        return str_to_unicode(MultiValueDict.__getitem__(self, key), self.encoding)
     def __setitem__(self, key, value):
+        key = str_to_unicode(key, self.encoding)
+        value = str_to_unicode(value, self.encoding)
         MultiValueDict.__setitem__(self, key, value)
     def __delitem__(self, key):
         super(QueryDict, self).__delitem__(key)
-    def get(self, key, default=None):
-        return str_to_unicode(MultiValueDict.get(self, key, default), self.encoding)
     def __copy__(self):
         result = self.__class__('', mutable=True)
         for key, value in dict.items(self):
             dict.__setitem__(result, copy.deepcopy(key, memo), copy.deepcopy(value, memo))
         return result
-    def getlist(self, key):
-        """
-        Returns a copy of the list associated with "key". This isn't a
-        reference to the original list because this method converts all the
-        values to unicode (without changing the original).
-        """
-        return [str_to_unicode(v, self.encoding) for v in MultiValueDict.getlist(self, key)]
     def setlist(self, key, list_):
+        key = str_to_unicode(key, self.encoding)
+        list_ = [str_to_unicode(elt, self.encoding) for elt in list_]
         MultiValueDict.setlist(self, key, list_)
     def setlistdefault(self, key, default_list=()):
     def appendlist(self, key, value):
+        key = str_to_unicode(key, self.encoding)
+        value = str_to_unicode(value, self.encoding)
         MultiValueDict.appendlist(self, key, value)
     def update(self, other_dict):
-        MultiValueDict.update(self, other_dict)
+        f = lambda s: str_to_unicode(s, self.encoding)
+        d = dict([(f(k), f(v)) for k, v in other_dict.items()])
+        MultiValueDict.update(self, d)
     def pop(self, key, *args):
-        val = MultiValueDict.pop(self, key, *args)
-        if isinstance(val, list):
-            return [str_to_unicode(v, self.encoding) for v in val]
-        return str_to_unicode(val, self.encoding)
+        return MultiValueDict.pop(self, key, *args)
     def popitem(self):
-        key, values = MultiValueDict.popitem(self)
-        return str_to_unicode(key, self.encoding), [str_to_unicode(v, self.encoding) for v in values]
-    def keys(self):
-        return [str_to_unicode(k, self.encoding) for k in MultiValueDict.keys(self)]
-    def values(self):
-        return [str_to_unicode(v, self.encoding) for v in MultiValueDict.values(self)]
-    def items(self):
-        return [(str_to_unicode(k, self.encoding), str_to_unicode(v, self.encoding)) for k, v in MultiValueDict.items(self)]
-    def lists(self):
-        return [(str_to_unicode(k, self.encoding), [str_to_unicode(v, self.encoding) for v in v_list]) for k, v_list in MultiValueDict.lists(self)]
+        return MultiValueDict.popitem(self)
     def clear(self):
-    def setdefault(self, *args):
+    def setdefault(self, key, default=None):
-        return MultiValueDict.setdefault(self, *args)
+        key = str_to_unicode(key, self.encoding)
+        default = str_to_unicode(default, self.encoding)
+        return MultiValueDict.setdefault(self, key, default)
     def copy(self):
         "Returns a mutable copy of this object."


         if isinstance(value, file):
                 '--' + boundary,
-                'Content-Disposition: form-data; name="%s"' % to_str(key),
-                '',
-                '--' + boundary,
-                'Content-Disposition: form-data; name="%s_file"; filename="%s"' % (to_str(key), to_str(,
+                'Content-Disposition: form-data; name="%s"; filename="%s"' % (to_str(key), to_str(,
                 'Content-Type: application/octet-stream',


 AttributeError: This QueryDict instance is immutable
 >>> q.get('foo', 'default')
 >>> q.getlist('foo')
 >>> q['name'] = 'john'
 >>> q.get('foo', 'default')
 >>> q.get('name', 'default')
 [u'bar', u'baz', u'another', u'hello']
 >>> q.pop('foo', 'not there')
-u'not there'
+'not there'
 >>> q.get('foo', 'not there')
-u'not there'
+'not there'
 >>> q.setdefault('foo', 'bar')
 >>> q['bar']
 Traceback (most recent call last):
-MultiValueDictKeyError: "Key 'bar' not found in <MultiValueDict: {'foo': ['bar']}>"
+MultiValueDictKeyError: "Key 'bar' not found in <MultiValueDict: {u'foo': [u'bar']}>"
 >>> q['something'] = 'bar'
 Traceback (most recent call last):
 >>> q.get('bar', 'default')
 >>> q.getlist('foo')
 >>> q.get('foo', 'default')
 >>> q.getlist('vote')
 [u'yes', u'no']


 from django.test import Client, TestCase
 from django.core import mail
+import os
 class AssertTemplateUsedTests(TestCase):
     fixtures = ['testdata.json']
         except AssertionError, e:
             self.assertEqual(str(e), "The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])")
+class AssertFileUploadTests(TestCase):
+    def test_simple_upload(self):
+        fd = open(os.path.join(os.path.dirname(__file__), ""))
+        post_data = {
+            'name': 'Ringo',
+            'file_field': fd,
+        }
+        response ='/test_client_regress/file_upload/', post_data)
+        self.assertEqual(response.status_code, 200)


 from django.conf.urls.defaults import *
-from django.views.generic.simple import redirect_to
 import views
 urlpatterns = patterns('',
     (r'^no_template_view/$', views.no_template_view),
+    (r'^file_upload/$', views.file_upload_view),


 from django.core.mail import EmailMessage, SMTPConnection
-from django.http import HttpResponse
+from django.http import HttpResponse, HttpResponseServerError
 from django.shortcuts import render_to_response
 def no_template_view(request):
     "A simple view that expects a GET request, and returns a rendered template"
     return HttpResponse("No template used")
+def file_upload_view(request):
+    """
+    Check that a file upload can be updated into the POST dictionary without
+    going pear-shaped.
+    """
+    form_data = request.POST.copy()
+    form_data.update(request.FILES)
+    if isinstance(form_data['file_field'], dict) and isinstance(form_data['name'], unicode):
+        return HttpResponse('')
+    else:
+        return HttpResponseServerError()