Index: django/contrib/admin/views/main.py
===================================================================
--- django/contrib/admin/views/main.py	(revision 1327)
+++ django/contrib/admin/views/main.py	(working copy)
@@ -86,7 +86,7 @@
         page_num = 0
     show_all = request.GET.has_key(ALL_VAR)
     is_popup = request.GET.has_key(IS_POPUP_VAR)
-    params = dict(request.GET.copy())
+    params = dict((key, value[-1]) for key, value in request.GET.items())
     if params.has_key(PAGE_VAR):
         del params[PAGE_VAR]
     # For ordering, first check the "ordering" parameter in the admin options,
Index: django/utils/httpwrappers.py
===================================================================
--- django/utils/httpwrappers.py	(revision 1327)
+++ django/utils/httpwrappers.py	(working copy)
@@ -1,7 +1,7 @@
 from Cookie import SimpleCookie
 from pprint import pformat
 from urllib import urlencode
-from django.utils import datastructures
+from django.utils.datastructures import MultiValueDict
 
 class HttpRequest(object): # needs to be new-style class because subclasses define "property"s
     "A basic HTTP request"
@@ -33,8 +33,8 @@
     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 = datastructures.MultiValueDict()
-    FILES = datastructures.MultiValueDict()
+    POST = MultiValueDict()
+    FILES = MultiValueDict()
     for submessage in msg.get_payload():
         if isinstance(submessage, email.Message.Message):
             name_dict = parse_header(submessage['Content-Disposition'])[1]
@@ -57,71 +57,73 @@
                 POST.appendlist(name_dict['name'], submessage.get_payload())
     return POST, FILES
 
-class QueryDict(datastructures.MultiValueDict):
+try:
+    from mod_python.util import parse_qsl
+except ImportError:
+    from cgi import parse_qsl
+class QueryDict(MultiValueDict):
     """A specialized MultiValueDict that takes a query string when initialized.
     This is immutable unless you create a copy of it."""
     def __init__(self, query_string):
-        try:
-            from mod_python.util import parse_qsl
-        except ImportError:
-            from cgi import parse_qsl
-        if not query_string:
-            self.data = {}
-            self._keys = []
-        else:
-            self.data = {}
-            self._keys = []
-            for name, value in parse_qsl(query_string, True): # keep_blank_values=True
-                if name in self.data:
-                    self.data[name].append(value)
-                else:
-                    self.data[name] = [value]
-                if name not in self._keys:
-                    self._keys.append(name)
+        MultiValueDict.__init__(self)
+        self._mutable = True
+        for key, value in parse_qsl(query_string, True): # keep_blank_values=True
+            self.appendlist(key, value)
         self._mutable = False
 
-    def __setitem__(self, key, value):
+    def _assert_mutable(self):
         if not self._mutable:
             raise AttributeError, "This QueryDict instance is immutable"
-        else:
-            self.data[key] = [value]
-            if not key in self._keys:
-                self._keys.append(key)
 
+    def _setitem_if_mutable(self, key, value):
+        self._assert_mutable()
+        MultiValueDict.__setitem__(self, key, value)
+    __setitem__ = _setitem_if_mutable
+
     def setlist(self, key, list_):
-        if not self._mutable:
-            raise AttributeError, "This QueryDict instance is immutable"
-        else:
-            self.data[key] = list_
-            if not key in self._keys:
-                self._keys.append(key)
+        self._assert_mutable()
+        MultiValueDict.setlist(self, key, list_)
 
+    def appendlist(self, key, value):
+        self._assert_mutable()
+        MultiValueDict.appendlist(self, key, value)
+
+    def update(self, other_dict):
+        self._assert_mutable()
+        MultiValueDict.update(self, other_dict)
+
+    def pop(self, key):
+        self._assert_mutable()
+        return MultiValueDict.pop(self, key)
+
+    def popitem(self):
+        self._assert_mutable()
+        return MultiValueDict.popitem(self)
+
+    def clear(self):
+        self._assert_mutable()
+        MultiValueDict.clear(self)
+
+    def setdefault(self, *args):
+        self._assert_mutable()
+        return MultiValueDict.setdefault(self, *args)
+
     def copy(self):
-        "Returns a mutable copy of this object"
-        cp = datastructures.MultiValueDict.copy(self)
+        """Returns a mutable copy of this object.  Our special __setitem__
+        must be disabled for copying machinery."""
+        QueryDict.__setitem__ = dict.__setitem__
+        import copy
+        cp = copy.deepcopy(self)
+        QueryDict.__setitem__ = QueryDict._setitem_if_mutable
         cp._mutable = True
         return cp
 
-    def assert_synchronized(self):
-        assert(len(self._keys) == len(self.data.keys())), \
-            "QueryDict data structure is out of sync: %s %s" % (str(self._keys), str(self.data))
-
     def items(self):
-        "Respect order preserved by self._keys"
-        self.assert_synchronized()
-        items = []
-        for key in self._keys:
-            if key in self.data:
-                items.append((key, self.data[key][0]))
-        return items
+        return self.lists()
 
-    def keys(self):
-        self.assert_synchronized()
-        return self._keys
-
     def urlencode(self):
         output = []
-        for k, list_ in self.data.items():
+        for k, list_ in self.items():
             output.extend([urlencode({k: v}) for v in list_])
         return '&'.join(output)
 
Index: django/utils/datastructures.py
===================================================================
--- django/utils/datastructures.py	(revision 1327)
+++ django/utils/datastructures.py	(working copy)
@@ -43,9 +43,9 @@
 class MultiValueDictKeyError(KeyError):
     pass
 
-class MultiValueDict:
+class MultiValueDict(dict):
     """
-    A dictionary-like class customized to deal with multiple values for the same key.
+    A subclass of dictionary customized to handle multiple values for the same key.
 
     >>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']})
     >>> d['name']
@@ -60,81 +60,85 @@
     which returns a list for every key, even though most Web forms submit
     single name-value pairs.
     """
-    def __init__(self, key_to_list_mapping=None):
-        self.data = key_to_list_mapping or {}
+    def __init__(self, key_to_list_mapping=()):
+        dict.__init__(self, key_to_list_mapping)
 
-    def __repr__(self):
-        return repr(self.data)
-
     def __getitem__(self, key):
-        "Returns the data value for this key; raises KeyError if not found"
-        if self.data.has_key(key):
-            try:
-                return self.data[key][-1] # in case of duplicates, use last value ([-1])
-            except IndexError:
-                return []
+        """Returns the last data value for this key or [] if empty list; raises KeyError
+        if key not found."""
+        try:
+            return dict.__getitem__(self, key)[-1]
+        except IndexError:
+            return []
         raise MultiValueDictKeyError, "Key '%s' not found in MultiValueDict %s" % (key, self.data)
 
-    def __setitem__(self, key, value):
-        self.data[key] = [value]
+    def _setitem_list(self, key, value):
+        "Items are assigned to a list."
+        dict.__setitem__(self, key, [value])
+    __setitem__ = _setitem_list
 
-    def __len__(self):
-        return len(self.data)
-
-    def get(self, key, default):
-        "Returns the default value if the requested data doesn't exist"
+    def get(self, key, *args):
+        "Returns the default value if the requested data doesn't exist."
         try:
-            val = self[key]
-        except (KeyError, IndexError):
-            return default
-        if val == []:
-            return default
-        return val
+            return self[key]
+        except KeyError:
+            if args:
+                return args[0]
+            raise
 
-    def getlist(self, key):
-        "Returns an empty list if the requested data doesn't exist"
+    def getlist(self, key, *args):
+        """Returns an empty list, or a user-specified default, if the requested data
+        doesn't exist."""
         try:
-            return self.data[key]
+            return dict.__getitem__(self, key)
         except KeyError:
+            if args:
+                return args[0]
             return []
 
     def setlist(self, key, list_):
-        self.data[key] = list_
+        "Set the list associated with a key."
+        dict.__setitem__(self, key, list_)
 
-    def appendlist(self, key, item):
-        "Appends an item to the internal list associated with key"
-        try:
-            self.data[key].append(item)
-        except KeyError:
-            self.data[key] = [item]
+    def appendlist(self, key, value):
+        "Appends an item to the internal list associated with key."
+        x = self.setdefault(key, [])
+        x.append(value)
 
-    def has_key(self, key):
-        return self.data.has_key(key)
-
     def items(self):
-        # we don't just return self.data.items() here, because we want to use
-        # self.__getitem__() to access the values as *strings*, not lists
-        return [(key, self[key]) for key in self.data.keys()]
+        """Returns a list of (key, value) pairs, where value is the last item in
+        the list associated with the key."""
+        return [(key, self[key]) for key in self.keys()]
 
-    def keys(self):
-        return self.data.keys()
+    def lists(self):
+        "Returns a list of (key, list) pairs."
+        return dict.items(self)
 
-    def update(self, other_dict):
-        if isinstance(other_dict, MultiValueDict):
-            for key, value_list in other_dict.data.items():
-                self.data.setdefault(key, []).extend(value_list)
-        elif type(other_dict) == type({}):
-            for key, value in other_dict.items():
-                self.data.setdefault(key, []).append(value)
-        else:
-            raise ValueError, "MultiValueDict.update() takes either a MultiValueDict or dictionary"
+    def values(self):
+        "Returns a list of the last value on every key list."
+        return [self[key] for key in self.keys()]
 
     def copy(self):
-        "Returns a copy of this object"
+        """Returns a copy of this object.  Our special __setitem__
+        must be disabled for copying machinery."""
         import copy
+        MultiValueDict.__setitem__ = dict.__setitem__
         cp = copy.deepcopy(self)
+        MultiValueDict.__setitem__ = MultiValueDict._setitem_list
         return cp
 
+    def update(self, other_dict):
+        "Update extends rather than replaces existing key lists."
+        if isinstance(other_dict, MultiValueDict):
+            for key, value_list in other_dict.lists():
+                self.setdefault(key, []).extend(value_list)
+        else:
+            try:
+                for key, value in other_dict.items():
+                    self.setdefault(key, []).append(value)
+            except TypeError:
+                raise ValueError, "MultiValueDict.update() takes either a MultiValueDict or dictionary"
+
 class DotExpandedDict(dict):
     """
     A special dictionary constructor that takes a dictionary in which the keys
Index: django/core/meta/__init__.py
===================================================================
--- django/core/meta/__init__.py	(revision 1327)
+++ django/core/meta/__init__.py	(working copy)
@@ -1578,7 +1578,7 @@
         # [('0', {'id': ['940'], 'choice': ['This is the first choice']}),
         #  ('1', {'id': ['941'], 'choice': ['This is the second choice']}),
         #  ('2', {'id': [''], 'choice': ['']})]
-        obj_list = DotExpandedDict(new_data.data)[rel_opts.object_name.lower()].items()
+        obj_list = DotExpandedDict(dict(new_data))[rel_opts.object_name.lower()].items()
         obj_list.sort(lambda x, y: cmp(int(x[0]), int(y[0])))
         params = {}
 
Index: tests/othertests/datastructures.py
===================================================================
--- tests/othertests/datastructures.py	(revision 0)
+++ tests/othertests/datastructures.py	(revision 0)
@@ -0,0 +1,105 @@
+"""
+>>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']})
+>>> d['name']
+'Simon'
+>>> d.get('name')
+'Simon'
+>>> d.get('nonkey')
+Traceback (most recent call last):
+    ...
+KeyError: 'nonkey'
+>>> d.get('nonkey', 'nonexistent')
+'nonexistent'
+>>> d.items()
+[('position', 'Developer'), ('name', 'Simon')]
+>>> d.values()
+['Developer', 'Simon']
+>>> d.lists()
+[('position', ['Developer']), ('name', ['Adrian', 'Simon'])]
+>>> d.appendlist('name', 'Wilson')
+>>> d.getlist('name')
+['Adrian', 'Simon', 'Wilson']
+>>> d.getlist('nonkey')
+[]
+>>> d.getlist('nonkey', 'nonexistent')
+'nonexistent'
+>>> d.setlist('lastname', ['Holovaty', 'Willison'])
+>>> d.getlist('lastname')
+['Holovaty', 'Willison']
+>>> d.update({'name':'Jacob'})
+>>> d.getlist('name')
+['Adrian', 'Simon', 'Wilson', 'Jacob']
+>>> dc = d.copy()
+>>> d is dc
+False
+>>> d == dc
+True
+
+>>> qd = QueryDict('a=1&b=2&b=3')
+>>> qd.urlencode()
+'a=1&b=2&b=3'
+>>> qd['a']
+'1'
+>>> qd.keys()
+['a', 'b']
+>>> qd.items()
+[('a', ['1']), ('b', ['2', '3'])]
+>>> qd.values()
+['1', '3']
+>>> qd['a'] = 2
+Traceback (most recent call last):
+    ...
+AttributeError: This QueryDict instance is immutable
+>>> qd.setlist('a', [2])
+Traceback (most recent call last):
+    ...
+AttributeError: This QueryDict instance is immutable
+>>> qd.appendlist('a', 2)
+Traceback (most recent call last):
+    ...
+AttributeError: This QueryDict instance is immutable
+>>> qd.pop('a')
+Traceback (most recent call last):
+    ...
+AttributeError: This QueryDict instance is immutable
+>>> qd.popitem()
+Traceback (most recent call last):
+    ...
+AttributeError: This QueryDict instance is immutable
+>>> qd.update({'a':'2'})
+Traceback (most recent call last):
+    ...
+AttributeError: This QueryDict instance is immutable
+>>> qd.setdefault('a', 2)
+Traceback (most recent call last):
+    ...
+AttributeError: This QueryDict instance is immutable
+>>> qd.clear()
+Traceback (most recent call last):
+    ...
+AttributeError: This QueryDict instance is immutable
+>>> qdc = qd.copy()
+>>> qd is qdc
+False
+>>> qd == qdc
+True
+>>> qdc.setlist('a', [1, 2])
+>>> qdc['a']
+2
+>>> qdc.appendlist('a', 3)
+>>> qdc.pop('a')
+[1, 2, 3]
+>>> qdc.clear()
+>>> qdc.setdefault('a', ['1'])
+['1']
+>>> qdc.update({'a':'2', 'b':'3'})
+>>> qdc
+{'a': ['1', '2'], 'b': ['3']}
+"""
+
+from django.utils.datastructures import MultiValueDict
+from django.utils.httpwrappers import QueryDict
+
+if __name__ == '__main__':
+    import doctest
+    doctest.testmod()
