Index: django/test/client.py
===================================================================
--- django/test/client.py	(revision 6861)
+++ django/test/client.py	(working copy)
@@ -218,6 +218,19 @@
 
         return self.request(**r)
 
+    def delete(self, path, data={}, **extra):
+        "Request a response from the server using DELETE."
+        r = {
+            'CONTENT_LENGTH':  None,
+            'CONTENT_TYPE':    'text/html; charset=utf-8',
+            'PATH_INFO':       path,
+            'QUERY_STRING':    urlencode(data, doseq=True),
+            'REQUEST_METHOD': 'DELETE',
+        }
+        r.update(extra)
+
+        return self.request(**r)
+
     def post(self, path, data={}, content_type=MULTIPART_CONTENT, **extra):
         "Request a response from the server using POST."
 
@@ -237,6 +250,25 @@
 
         return self.request(**r)
 
+    def put(self, path, data={}, content_type=MULTIPART_CONTENT, **extra):
+        "Request a response from the server using PUT."
+
+        if content_type is MULTIPART_CONTENT:
+            put_data = encode_multipart(BOUNDARY, data)
+        else:
+            put_data = data
+
+        r = {
+            'CONTENT_LENGTH': len(put_data),
+            'CONTENT_TYPE':   content_type,
+            'PATH_INFO':      path,
+            'REQUEST_METHOD': 'PUT',
+            'wsgi.input':     StringIO(put_data),
+        }
+        r.update(extra)
+
+        return self.request(**r)
+
     def login(self, **credentials):
         """Set the Client to appear as if it has sucessfully logged into a site.
 
Index: django/http/__init__.py
===================================================================
--- django/http/__init__.py	(revision 6861)
+++ django/http/__init__.py	(working copy)
@@ -21,27 +21,27 @@
 class HttpRequest(object):
     "A basic HTTP request"
 
-    # The encoding used in GET/POST dicts. None means use default setting.
+    # The encoding used in GET/POST/PUT dicts. None means use default setting.
     _encoding = None
 
     def __init__(self):
-        self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {}
+        self.GET, self.POST, self.PUT, self.COOKIES, self.META, self.DATA, self.FILES = {}, {}, {}, {}, {}, {}, {}
         self.path = ''
         self.method = None
 
     def __repr__(self):
-        return '<HttpRequest\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \
-            (pformat(self.GET), pformat(self.POST), pformat(self.COOKIES),
-            pformat(self.META))
+        return '<HttpRequest\nGET:%s,\nPOST:%s,\nPUT:%s,\nCOOKIES:%s,\nMETA:%s>' % \
+            (pformat(self.GET), pformat(self.POST), pformat(self.PUT), 
+            pformat(self.COOKIES), pformat(self.META))
 
     def __getitem__(self, key):
-        for d in (self.POST, self.GET):
+        for d in (self.POST, self.GET, self.PUT):
             if key in d:
                 return d[key]
-        raise KeyError, "%s not found in either POST or GET" % key
+        raise KeyError, "%s not found in either POST, GET or PUT" % key
 
     def has_key(self, key):
-        return key in self.GET or key in self.POST
+        return key in self.GET or key in self.POST or key in self.PUT
 
     __contains__ = has_key
 
@@ -82,7 +82,7 @@
 
     def _set_encoding(self, val):
         """
-        Sets the encoding used for GET/POST accesses. If the GET or POST
+        Sets the encoding used for GET/POST/PUT accesses. If the GET, POST or PUT
         dictionary has already been created, it is removed and recreated on the
         next access (so that it is decoded correctly).
         """
@@ -91,6 +91,8 @@
             del self._get
         if hasattr(self, '_post'):
             del self._post
+        if hasattr(self, '_put'):
+            del self._put
 
     def _get_encoding(self):
         return self._encoding
@@ -98,13 +100,13 @@
     encoding = property(_get_encoding, _set_encoding)
 
 def parse_file_upload(header_dict, post_data):
-    "Returns a tuple of (POST QueryDict, FILES MultiValueDict)"
+    "Returns a tuple of (DATA QueryDict, FILES MultiValueDict)"
     import email, email.Message
     from cgi import parse_header
     raw_message = '\r\n'.join(['%s:%s' % pair for pair in header_dict.items()])
     raw_message += '\r\n\r\n' + post_data
     msg = email.message_from_string(raw_message)
-    POST = QueryDict('', mutable=True)
+    DATA = QueryDict('', mutable=True)
     FILES = MultiValueDict()
     for submessage in msg.get_payload():
         if submessage and isinstance(submessage, email.Message.Message):
@@ -127,8 +129,8 @@
                     'content': submessage.get_payload(),
                 }))
             else:
-                POST.appendlist(name_dict['name'], submessage.get_payload())
-    return POST, FILES
+                DATA.appendlist(name_dict['name'], submessage.get_payload())
+    return DATA, FILES
 
 class QueryDict(MultiValueDict):
     """
Index: django/core/handlers/wsgi.py
===================================================================
--- django/core/handlers/wsgi.py	(revision 6861)
+++ django/core/handlers/wsgi.py	(working copy)
@@ -91,6 +91,10 @@
         except:
             post = '<could not parse>'
         try:
+            put = pformat(self.PUT)
+        except:
+            put = '<could not parse>'
+        try:
             cookies = pformat(self.COOKIES)
         except:
             cookies = '<could not parse>'
@@ -98,8 +102,8 @@
             meta = pformat(self.META)
         except:
             meta = '<could not parse>'
-        return '<WSGIRequest\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \
-            (get, post, cookies, meta)
+        return '<WSGIRequest\nGET:%s,\nPOST:%s,\nPUT:%s,\nCOOKIES:%s,\nMETA:%s>' % \
+            (get, post, put, cookies, meta)
 
     def get_full_path(self):
         return '%s%s' % (self.path, self.environ.get('QUERY_STRING', '') and ('?' + self.environ.get('QUERY_STRING', '')) or '')
@@ -108,21 +112,27 @@
         return 'wsgi.url_scheme' in self.environ \
             and self.environ['wsgi.url_scheme'] == 'https'
 
-    def _load_post_and_files(self):
-        # Populates self._post and self._files
-        if self.method == 'POST':
+    def _load_data_and_files(self):
+        # Populates self._data and self._files
+        if self.method in ('POST', 'PUT'):
             if self.environ.get('CONTENT_TYPE', '').startswith('multipart'):
                 header_dict = dict([(k, v) for k, v in self.environ.items() if k.startswith('HTTP_')])
                 header_dict['Content-Type'] = self.environ.get('CONTENT_TYPE', '')
-                self._post, self._files = http.parse_file_upload(header_dict, self.raw_post_data)
+                self._data, self._files = http.parse_file_upload(header_dict, self.raw_post_data)
             else:
-                self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
+                self._data, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
         else:
-            self._post, self._files = http.QueryDict('', encoding=self._encoding), datastructures.MultiValueDict()
+            self._data, self._files = http.QueryDict('', encoding=self._encoding), datastructures.MultiValueDict()
 
+    def _load_post_and_files(self):
+        # Populates self._post, preserve backward compatibility.
+        if not hasattr(self, '_data'):
+            self._load_data_and_files()
+        self._post = self._data
+
     def _get_request(self):
         if not hasattr(self, '_request'):
-            self._request = datastructures.MergeDict(self.POST, self.GET)
+            self._request = datastructures.MergeDict(self.POST, self.PUT, self.GET)
         return self._request
 
     def _get_get(self):
@@ -136,12 +146,20 @@
 
     def _get_post(self):
         if not hasattr(self, '_post'):
-            self._load_post_and_files()
+            self._post = self.DATA
         return self._post
 
     def _set_post(self, post):
         self._post = post
 
+    def _get_put(self):
+        if not hasattr(self, '_put'):
+            self._put = self.DATA
+        return self._put
+
+    def _set_put(self, put):
+        self._put = put
+
     def _get_cookies(self):
         if not hasattr(self, '_cookies'):
             self._cookies = http.parse_cookie(self.environ.get('HTTP_COOKIE', ''))
@@ -152,9 +170,14 @@
 
     def _get_files(self):
         if not hasattr(self, '_files'):
-            self._load_post_and_files()
+            self._load_data_and_files()
         return self._files
 
+    def _get_data(self):
+        if not hasattr(self, '_data'):
+            self._load_data_and_files()
+        return self._data
+
     def _get_raw_post_data(self):
         try:
             return self._raw_post_data
@@ -174,11 +197,14 @@
 
     GET = property(_get_get, _set_get)
     POST = property(_get_post, _set_post)
+    PUT = property(_get_put, _set_put)
     COOKIES = property(_get_cookies, _set_cookies)
     FILES = property(_get_files)
+    DATA = property(_get_data)
     REQUEST = property(_get_request)
     raw_post_data = property(_get_raw_post_data)
 
+
 class WSGIHandler(BaseHandler):
     initLock = Lock()
     request_class = WSGIRequest
Index: django/core/handlers/modpython.py
===================================================================
--- django/core/handlers/modpython.py	(revision 6861)
+++ django/core/handlers/modpython.py	(working copy)
@@ -29,6 +29,10 @@
         except:
             post = '<could not parse>'
         try:
+            put = pformat(self.PUT)
+        except:
+            put = '<could not parse>'
+        try:
             cookies = pformat(self.COOKIES)
         except:
             cookies = '<could not parse>'
@@ -36,8 +40,8 @@
             meta = pformat(self.META)
         except:
             meta = '<could not parse>'
-        return '<ModPythonRequest\npath:%s,\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \
-            (self.path, get, post, cookies, meta)
+        return '<ModPythonRequest\npath:%s,\nGET:%s,\nPOST:%s,\nPUT:%s,\nCOOKIES:%s,\nMETA:%s>' % \
+            (self.path, get, post, put, cookies, meta)
 
     def get_full_path(self):
         return '%s%s' % (self.path, self._req.args and ('?' + self._req.args) or '')
@@ -49,16 +53,22 @@
             # mod_python < 3.2.10 doesn't have req.is_https().
             return self._req.subprocess_env.get('HTTPS', '').lower() in ('on', '1')
 
-    def _load_post_and_files(self):
+    def _load_data_and_files(self):
         "Populates self._post and self._files"
         if 'content-type' in self._req.headers_in and self._req.headers_in['content-type'].startswith('multipart'):
-            self._post, self._files = http.parse_file_upload(self._req.headers_in, self.raw_post_data)
+            self._data, self._files = http.parse_file_upload(self._req.headers_in, self.raw_post_data)
         else:
-            self._post, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
+            self._data, self._files = http.QueryDict(self.raw_post_data, encoding=self._encoding), datastructures.MultiValueDict()
 
+    def _load_post_and_files(self):
+        # Populates self._post, preserve backward compatibility.
+        if not hasattr(self, '_data'):
+            self._load_data_and_files()
+        self._post = self._data
+
     def _get_request(self):
         if not hasattr(self, '_request'):
-            self._request = datastructures.MergeDict(self.POST, self.GET)
+            self._request = datastructures.MergeDict(self.POST, self.PUT, self.GET)
         return self._request
 
     def _get_get(self):
@@ -71,12 +81,20 @@
 
     def _get_post(self):
         if not hasattr(self, '_post'):
-            self._load_post_and_files()
+            self._post = self.DATA
         return self._post
 
     def _set_post(self, post):
         self._post = post
 
+    def _get_put(self):
+        if not hasattr(self, '_put'):
+            self._put = self.DATA
+        return self._put
+
+    def _set_put(self, put):
+        self._put = put
+
     def _get_cookies(self):
         if not hasattr(self, '_cookies'):
             self._cookies = http.parse_cookie(self._req.headers_in.get('cookie', ''))
@@ -85,9 +105,14 @@
     def _set_cookies(self, cookies):
         self._cookies = cookies
 
+    def _get_data(self):
+        if not hasattr(self, '_data'):
+            self._load_data_and_files()
+        return self._data
+
     def _get_files(self):
         if not hasattr(self, '_files'):
-            self._load_post_and_files()
+            self._load_data_and_files()
         return self._files
 
     def _get_meta(self):
@@ -129,8 +147,10 @@
 
     GET = property(_get_get, _set_get)
     POST = property(_get_post, _set_post)
+    PUT = property(_get_put, _set_put)
     COOKIES = property(_get_cookies, _set_cookies)
     FILES = property(_get_files)
+    DATA = property(_get_data)
     META = property(_get_meta)
     REQUEST = property(_get_request)
     raw_post_data = property(_get_raw_post_data)
