Ticket #736: mvd.patch

File mvd.patch, 13.5 KB (added by django@…, 18 years ago)

[patch] QueryDict fix + doctests

  • django/utils/httpwrappers.py

     
    11from Cookie import SimpleCookie
    22from pprint import pformat
    33from urllib import urlencode
    4 from django.utils import datastructures
     4from django.utils.datastructures import MultiValueDict
    55
    66class HttpRequest(object): # needs to be new-style class because subclasses define "property"s
    77    "A basic HTTP request"
     
    3333    raw_message = '\r\n'.join(['%s:%s' % pair for pair in header_dict.items()])
    3434    raw_message += '\r\n\r\n' + post_data
    3535    msg = email.message_from_string(raw_message)
    36     POST = datastructures.MultiValueDict()
    37     FILES = datastructures.MultiValueDict()
     36    POST = MultiValueDict()
     37    FILES = MultiValueDict()
    3838    for submessage in msg.get_payload():
    3939        if isinstance(submessage, email.Message.Message):
    4040            name_dict = parse_header(submessage['Content-Disposition'])[1]
     
    5757                POST.appendlist(name_dict['name'], submessage.get_payload())
    5858    return POST, FILES
    5959
    60 class QueryDict(datastructures.MultiValueDict):
     60try:
     61    from mod_python.util import parse_qsl
     62except ImportError:
     63    from cgi import parse_qsl
     64class QueryDict(MultiValueDict):
    6165    """A specialized MultiValueDict that takes a query string when initialized.
    6266    This is immutable unless you create a copy of it."""
    6367    def __init__(self, query_string):
    64         try:
    65             from mod_python.util import parse_qsl
    66         except ImportError:
    67             from cgi import parse_qsl
    68         if not query_string:
    69             self.data = {}
    70             self._keys = []
    71         else:
    72             self.data = {}
    73             self._keys = []
    74             for name, value in parse_qsl(query_string, True): # keep_blank_values=True
    75                 if name in self.data:
    76                     self.data[name].append(value)
    77                 else:
    78                     self.data[name] = [value]
    79                 if name not in self._keys:
    80                     self._keys.append(name)
     68        MultiValueDict.__init__(self)
     69        self._mutable = True
     70        for key, value in parse_qsl(query_string, True): # keep_blank_values=True
     71            self.appendlist(key, value)
    8172        self._mutable = False
    8273
    83     def __setitem__(self, key, value):
     74    def _assert_mutable(self):
    8475        if not self._mutable:
    8576            raise AttributeError, "This QueryDict instance is immutable"
    86         else:
    87             self.data[key] = [value]
    88             if not key in self._keys:
    89                 self._keys.append(key)
    9077
     78    def _setitem_if_mutable(self, key, value):
     79        self._assert_mutable()
     80        MultiValueDict.__setitem__(self, key, value)
     81    __setitem__ = _setitem_if_mutable
     82
    9183    def setlist(self, key, list_):
    92         if not self._mutable:
    93             raise AttributeError, "This QueryDict instance is immutable"
    94         else:
    95             self.data[key] = list_
    96             if not key in self._keys:
    97                 self._keys.append(key)
     84        self._assert_mutable()
     85        MultiValueDict.setlist(self, key, list_)
    9886
     87    def appendlist(self, key, value):
     88        self._assert_mutable()
     89        MultiValueDict.appendlist(self, key, value)
     90
     91    def update(self, other_dict):
     92        self._assert_mutable()
     93        MultiValueDict.update(self, other_dict)
     94
     95    def pop(self, key):
     96        self._assert_mutable()
     97        return MultiValueDict.pop(self, key)
     98
     99    def popitem(self):
     100        self._assert_mutable()
     101        return MultiValueDict.popitem(self)
     102
     103    def clear(self):
     104        self._assert_mutable()
     105        MultiValueDict.clear(self)
     106
     107    def setdefault(self, *args):
     108        self._assert_mutable()
     109        return MultiValueDict.setdefault(self, *args)
     110
    99111    def copy(self):
    100         "Returns a mutable copy of this object"
    101         cp = datastructures.MultiValueDict.copy(self)
     112        """Returns a mutable copy of this object.  Our special __setitem__
     113        must be disabled for copying machinery."""
     114        QueryDict.__setitem__ = dict.__setitem__
     115        import copy
     116        cp = copy.deepcopy(self)
     117        QueryDict.__setitem__ = QueryDict._setitem_if_mutable
    102118        cp._mutable = True
    103119        return cp
    104120
    105     def assert_synchronized(self):
    106         assert(len(self._keys) == len(self.data.keys())), \
    107             "QueryDict data structure is out of sync: %s %s" % (str(self._keys), str(self.data))
    108 
    109121    def items(self):
    110         "Respect order preserved by self._keys"
    111         self.assert_synchronized()
    112         items = []
    113         for key in self._keys:
    114             if key in self.data:
    115                 items.append((key, self.data[key][0]))
    116         return items
     122        return self.lists()
    117123
    118     def keys(self):
    119         self.assert_synchronized()
    120         return self._keys
    121 
    122124    def urlencode(self):
    123125        output = []
    124         for k, list_ in self.data.items():
     126        for k, list_ in self.items():
    125127            output.extend([urlencode({k: v}) for v in list_])
    126128        return '&'.join(output)
    127129
  • django/utils/datastructures.py

     
    4343class MultiValueDictKeyError(KeyError):
    4444    pass
    4545
    46 class MultiValueDict:
     46class MultiValueDict(dict):
    4747    """
    48     A dictionary-like class customized to deal with multiple values for the same key.
     48    A subclass of dictionary customized to handle multiple values for the same key.
    4949
    5050    >>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']})
    5151    >>> d['name']
     
    6060    which returns a list for every key, even though most Web forms submit
    6161    single name-value pairs.
    6262    """
    63     def __init__(self, key_to_list_mapping=None):
    64         self.data = key_to_list_mapping or {}
     63    def __init__(self, key_to_list_mapping=()):
     64        dict.__init__(self, key_to_list_mapping)
    6565
    66     def __repr__(self):
    67         return repr(self.data)
    68 
    6966    def __getitem__(self, key):
    70         "Returns the data value for this key; raises KeyError if not found"
    71         if self.data.has_key(key):
    72             try:
    73                 return self.data[key][-1] # in case of duplicates, use last value ([-1])
    74             except IndexError:
    75                 return []
     67        """Returns the last data value for this key or [] if empty list; raises KeyError
     68        if key not found."""
     69        try:
     70            return dict.__getitem__(self, key)[-1]
     71        except IndexError:
     72            return []
    7673        raise MultiValueDictKeyError, "Key '%s' not found in MultiValueDict %s" % (key, self.data)
    7774
    78     def __setitem__(self, key, value):
    79         self.data[key] = [value]
     75    def _setitem_list(self, key, value):
     76        "Items are assigned to a list."
     77        dict.__setitem__(self, key, [value])
     78    __setitem__ = _setitem_list
    8079
    81     def __len__(self):
    82         return len(self.data)
    83 
    84     def get(self, key, default):
    85         "Returns the default value if the requested data doesn't exist"
     80    def get(self, key, *args):
     81        "Returns the default value if the requested data doesn't exist."
    8682        try:
    87             val = self[key]
    88         except (KeyError, IndexError):
    89             return default
    90         if val == []:
    91             return default
    92         return val
     83            return self[key]
     84        except KeyError:
     85            if args:
     86                return args[0]
     87            raise
    9388
    94     def getlist(self, key):
    95         "Returns an empty list if the requested data doesn't exist"
     89    def getlist(self, key, *args):
     90        """Returns an empty list, or a user-specified default, if the requested data
     91        doesn't exist."""
    9692        try:
    97             return self.data[key]
     93            return dict.__getitem__(self, key)
    9894        except KeyError:
     95            if args:
     96                return args[0]
    9997            return []
    10098
    10199    def setlist(self, key, list_):
    102         self.data[key] = list_
     100        "Set the list associated with a key."
     101        dict.__setitem__(self, key, list_)
    103102
    104     def appendlist(self, key, item):
    105         "Appends an item to the internal list associated with key"
    106         try:
    107             self.data[key].append(item)
    108         except KeyError:
    109             self.data[key] = [item]
     103    def appendlist(self, key, value):
     104        "Appends an item to the internal list associated with key."
     105        x = self.setdefault(key, [])
     106        x.append(value)
    110107
    111     def has_key(self, key):
    112         return self.data.has_key(key)
    113 
    114108    def items(self):
    115         # we don't just return self.data.items() here, because we want to use
    116         # self.__getitem__() to access the values as *strings*, not lists
    117         return [(key, self[key]) for key in self.data.keys()]
     109        """Returns a list of (key, value) pairs, where value is the last item in
     110        the list associated with the key."""
     111        return [(key, self[key]) for key in self.keys()]
    118112
    119     def keys(self):
    120         return self.data.keys()
     113    def lists(self):
     114        "Returns a list of (key, list) pairs."
     115        return dict.items(self)
    121116
    122     def update(self, other_dict):
    123         if isinstance(other_dict, MultiValueDict):
    124             for key, value_list in other_dict.data.items():
    125                 self.data.setdefault(key, []).extend(value_list)
    126         elif type(other_dict) == type({}):
    127             for key, value in other_dict.items():
    128                 self.data.setdefault(key, []).append(value)
    129         else:
    130             raise ValueError, "MultiValueDict.update() takes either a MultiValueDict or dictionary"
     117    def values(self):
     118        "Returns a list of the last value on every key list."
     119        return [self[key] for key in self.keys()]
    131120
    132121    def copy(self):
    133         "Returns a copy of this object"
     122        """Returns a copy of this object.  Our special __setitem__
     123        must be disabled for copying machinery."""
    134124        import copy
     125        MultiValueDict.__setitem__ = dict.__setitem__
    135126        cp = copy.deepcopy(self)
     127        MultiValueDict.__setitem__ = MultiValueDict._setitem_list
    136128        return cp
    137129
     130    def update(self, other_dict):
     131        "Update extends rather than replaces existing key lists."
     132        if isinstance(other_dict, MultiValueDict):
     133            for key, value_list in other_dict.lists():
     134                self.setdefault(key, []).extend(value_list)
     135        else:
     136            try:
     137                for key, value in other_dict.items():
     138                    self.setdefault(key, []).append(value)
     139            except TypeError:
     140                raise ValueError, "MultiValueDict.update() takes either a MultiValueDict or dictionary"
     141
    138142class DotExpandedDict(dict):
    139143    """
    140144    A special dictionary constructor that takes a dictionary in which the keys
  • tests/othertests/datastructures.py

     
     1"""
     2>>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']})
     3>>> d['name']
     4'Simon'
     5>>> d.get('name')
     6'Simon'
     7>>> d.get('nonkey')
     8Traceback (most recent call last):
     9    ...
     10KeyError: 'nonkey'
     11>>> d.get('nonkey', 'nonexistent')
     12'nonexistent'
     13>>> d.items()
     14[('position', 'Developer'), ('name', 'Simon')]
     15>>> d.values()
     16['Developer', 'Simon']
     17>>> d.lists()
     18[('position', ['Developer']), ('name', ['Adrian', 'Simon'])]
     19>>> d.appendlist('name', 'Wilson')
     20>>> d.getlist('name')
     21['Adrian', 'Simon', 'Wilson']
     22>>> d.getlist('nonkey')
     23[]
     24>>> d.getlist('nonkey', 'nonexistent')
     25'nonexistent'
     26>>> d.setlist('lastname', ['Holovaty', 'Willison'])
     27>>> d.getlist('lastname')
     28['Holovaty', 'Willison']
     29>>> d.update({'name':'Jacob'})
     30>>> d.getlist('name')
     31['Adrian', 'Simon', 'Wilson', 'Jacob']
     32>>> dc = d.copy()
     33>>> d is dc
     34False
     35>>> d == dc
     36True
     37
     38>>> qd = QueryDict('a=1&b=2&b=3')
     39>>> qd.urlencode()
     40'a=1&b=2&b=3'
     41>>> qd['a']
     42'1'
     43>>> qd.keys()
     44['a', 'b']
     45>>> qd.items()
     46[('a', ['1']), ('b', ['2', '3'])]
     47>>> qd.values()
     48['1', '3']
     49>>> qd['a'] = 2
     50Traceback (most recent call last):
     51    ...
     52AttributeError: This QueryDict instance is immutable
     53>>> qd.setlist('a', [2])
     54Traceback (most recent call last):
     55    ...
     56AttributeError: This QueryDict instance is immutable
     57>>> qd.appendlist('a', 2)
     58Traceback (most recent call last):
     59    ...
     60AttributeError: This QueryDict instance is immutable
     61>>> qd.pop('a')
     62Traceback (most recent call last):
     63    ...
     64AttributeError: This QueryDict instance is immutable
     65>>> qd.popitem()
     66Traceback (most recent call last):
     67    ...
     68AttributeError: This QueryDict instance is immutable
     69>>> qd.update({'a':'2'})
     70Traceback (most recent call last):
     71    ...
     72AttributeError: This QueryDict instance is immutable
     73>>> qd.setdefault('a', 2)
     74Traceback (most recent call last):
     75    ...
     76AttributeError: This QueryDict instance is immutable
     77>>> qd.clear()
     78Traceback (most recent call last):
     79    ...
     80AttributeError: This QueryDict instance is immutable
     81>>> qdc = qd.copy()
     82>>> qd is qdc
     83False
     84>>> qd == qdc
     85True
     86>>> qdc.setlist('a', [1, 2])
     87>>> qdc['a']
     882
     89>>> qdc.appendlist('a', 3)
     90>>> qdc.pop('a')
     91[1, 2, 3]
     92>>> qdc.clear()
     93>>> qdc.setdefault('a', ['1'])
     94['1']
     95>>> qdc.update({'a':'2', 'b':'3'})
     96>>> qdc
     97{'a': ['1', '2'], 'b': ['3']}
     98"""
     99
     100from django.utils.datastructures import MultiValueDict
     101from django.utils.httpwrappers import QueryDict
     102
     103if __name__ == '__main__':
     104    import doctest
     105    doctest.testmod()
Back to Top