Django

Code

Changeset 1504

Show
Ignore:
Timestamp:
11/29/05 22:08:46 (3 years ago)
Author:
adrian
Message:

Fixed #736 -- Changed behavior of QueryDict? items() to be more consistent, fixed mutability holes, gave MultiValueDict? many more dictionary methods and added unit tests. Thanks, Kieran Holland. This is slightly backwards-incompatible if you happened to rely on the behavior of QueryDict?.items(), which is highly unlikely.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/contrib/admin/views/main.py

    r1489 r1504  
    5858        self.get_search_parameters(request) 
    5959        self.get_ordering() 
    60         self.query = request.GET.get(SEARCH_VAR,'') 
     60        self.query = request.GET.get(SEARCH_VAR, '') 
    6161        self.get_lookup_params() 
    6262        self.get_results(request) 
     
    101101        # Get search parameters from the query string. 
    102102        try: 
    103             self.req_get = request.GET 
    104103            self.page_num = int(request.GET.get(PAGE_VAR, 0)) 
    105104        except ValueError: 
     
    107106        self.show_all = request.GET.has_key(ALL_VAR) 
    108107        self.is_popup = request.GET.has_key(IS_POPUP_VAR) 
    109         self.params = dict(request.GET.copy()) 
     108        self.params = dict((k, v) for k, v in request.GET.items()) 
    110109        if self.params.has_key(PAGE_VAR): 
    111110            del self.params[PAGE_VAR] 
  • django/trunk/django/core/meta/__init__.py

    r1485 r1504  
    16791679        params[f.attname] = param 
    16801680 
    1681  
    16821681    if change: 
    16831682        params[opts.pk.attname] = self.obj_key 
     
    17111710                    self.fields_changed.append(f.verbose_name) 
    17121711 
    1713     expanded_data = DotExpandedDict(new_data.data
     1712    expanded_data = DotExpandedDict(dict(new_data)
    17141713    # Save many-to-one objects. Example: Add the Choice objects for a Poll. 
    17151714    for related in opts.get_all_related_objects(): 
     
    17241723            obj_list.sort(lambda x, y: cmp(int(x[0]), int(y[0]))) 
    17251724            params = {} 
    1726  
    17271725 
    17281726            # For each related item... 
     
    17701768                       params[f.attname] = param 
    17711769 
    1772  
    17731770                    # Related links are a special case, because we have to 
    17741771                    # manually set the "content_type_id" and "object_id" fields. 
     
    17801777                # Create the related item. 
    17811778                new_rel_obj = related.opts.get_model_module().Klass(**params) 
    1782  
    1783  
    17841779 
    17851780                # If all the core fields were provided (non-empty), save the item. 
     
    18141809                    new_rel_obj.delete() 
    18151810                    self.fields_deleted.append('%s "%s"' % (related.opts.verbose_name, old_rel_obj)) 
    1816  
    18171811 
    18181812    # Save the order, if applicable. 
  • django/trunk/django/utils/datastructures.py

    r1498 r1504  
    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']}) 
     
    6161    single name-value pairs. 
    6262    """ 
    63     def __init__(self, key_to_list_mapping=None): 
    64         self.data = key_to_list_mapping or {} 
    65  
    66     def __repr__(self): 
    67         return repr(self.data) 
     63    def __init__(self, key_to_list_mapping=()): 
     64        dict.__init__(self, key_to_list_mapping) 
    6865 
    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 [] 
    76         raise MultiValueDictKeyError, "Key '%s' not found in MultiValueDict %s" % (key, self.data) 
     67        """ 
     68        Returns the last data value for this key, or [] if it's an empty list; 
     69        raises KeyError if not found. 
     70        """ 
     71        try: 
     72            list_ = dict.__getitem__(self, key) 
     73        except KeyError: 
     74            raise MultiValueDictKeyError, "Key %r not found in MultiValueDict %r" % (key, self) 
     75        try: 
     76            return list_[-1] 
     77        except IndexError: 
     78            return [] 
    7779 
    78     def __setitem__(self, key, value): 
    79         self.data[key] = [value] 
     80    def _setitem_list(self, key, value): 
     81        dict.__setitem__(self, key, [value]) 
     82    __setitem__ = _setitem_list 
    8083 
    81     def __len__(self): 
    82         return len(self.data) 
    83  
    84     def __contains__(self, key): 
    85         return self.data.has_key(key) 
    86  
    87     def get(self, key, default): 
     84    def get(self, key, default=None): 
    8885        "Returns the default value if the requested data doesn't exist" 
    8986        try: 
    9087            val = self[key] 
    91         except (KeyError, IndexError)
     88        except KeyError
    9289            return default 
    9390        if val == []: 
     
    9895        "Returns an empty list if the requested data doesn't exist" 
    9996        try: 
    100             return self.data[key] 
     97            return dict.__getitem__(self, key) 
    10198        except KeyError: 
    10299            return [] 
    103100 
    104101    def setlist(self, key, list_): 
    105         self.data[key] = list_ 
     102        dict.__setitem__(self, key, list_) 
    106103 
    107     def appendlist(self, key, item): 
     104    def setdefault(self, key, default=None): 
     105        if key not in self: 
     106            self[key] = default 
     107        return self[key] 
     108 
     109    def setlistdefault(self, key, default_list=()): 
     110        if key not in self: 
     111            self.setlist(key, default_list) 
     112        return self.getlist(key) 
     113 
     114    def appendlist(self, key, value): 
    108115        "Appends an item to the internal list associated with key" 
    109         try: 
    110             self.data[key].append(item) 
    111         except KeyError: 
    112             self.data[key] = [item] 
    113  
    114     def has_key(self, key): 
    115         return self.data.has_key(key) 
     116        self.setlistdefault(key, []) 
     117        dict.__setitem__(self, key, self.getlist(key) + [value]) 
    116118 
    117119    def items(self): 
    118         # we don't just return self.data.items() here, because we want to use 
    119         # self.__getitem__() to access the values as *strings*, not lists 
    120         return [(key, self[key]) for key in self.data.keys()] 
     120        """ 
     121        Returns a list of (key, value) pairs, where value is the last item in 
     122        the list associated with the key. 
     123        """ 
     124        return [(key, self[key]) for key in self.keys()] 
    121125 
    122     def keys(self): 
    123         return self.data.keys() 
     126    def lists(self): 
     127        "Returns a list of (key, list) pairs." 
     128        return dict.items(self) 
     129 
     130    def values(self): 
     131        "Returns a list of the last value on every key list." 
     132        return [self[key] for key in self.keys()] 
     133 
     134    def copy(self): 
     135        "Returns a copy of this object." 
     136        import copy 
     137        # Our custom __setitem__ must be disabled for copying machinery. 
     138        MultiValueDict.__setitem__ = dict.__setitem__ 
     139        cp = copy.deepcopy(self) 
     140        MultiValueDict.__setitem__ = MultiValueDict._setitem_list 
     141        return cp 
    124142 
    125143    def update(self, other_dict): 
     144        "update() extends rather than replaces existing key lists." 
    126145        if isinstance(other_dict, MultiValueDict): 
    127             for key, value_list in other_dict.data.items(): 
    128                 self.data.setdefault(key, []).extend(value_list) 
    129         elif type(other_dict) == type({}): 
    130             for key, value in other_dict.items(): 
    131                 self.data.setdefault(key, []).append(value) 
     146            for key, value_list in other_dict.lists(): 
     147                self.setlistdefault(key, []).extend(value_list) 
    132148        else: 
    133             raise ValueError, "MultiValueDict.update() takes either a MultiValueDict or dictionary" 
    134  
    135     def copy(self): 
    136         "Returns a copy of this object" 
    137         import copy 
    138         cp = copy.deepcopy(self) 
    139         return cp 
     149            try: 
     150                for key, value in other_dict.items(): 
     151                    self.setlistdefault(key, []).append(value) 
     152            except TypeError: 
     153                raise ValueError, "MultiValueDict.update() takes either a MultiValueDict or dictionary" 
    140154 
    141155class DotExpandedDict(dict): 
  • django/trunk/django/utils/httpwrappers.py

    r1503 r1504  
    6868    This is immutable unless you create a copy of it.""" 
    6969    def __init__(self, query_string): 
    70         if not query_string: 
    71             self.data = {} 
    72             self._keys = [] 
    73         else: 
    74             self.data = {} 
    75             self._keys = [] 
    76             for name, value in parse_qsl(query_string, True): # keep_blank_values=True 
    77                 if name in self.data: 
    78                     self.data[name].append(value) 
    79                 else: 
    80                     self.data[name] = [value] 
    81                 if name not in self._keys: 
    82                     self._keys.append(name) 
     70        MultiValueDict.__init__(self) 
     71        self._mutable = True 
     72        for key, value in parse_qsl(query_string, True): # keep_blank_values=True 
     73            self.appendlist(key, value) 
    8374        self._mutable = False 
    8475 
    85     def __setitem__(self, key, value): 
     76    def _assert_mutable(self): 
    8677        if not self._mutable: 
    8778            raise AttributeError, "This QueryDict instance is immutable" 
    88         else: 
    89             self.data[key] = [value] 
    90             if not key in self._keys: 
    91                 self._keys.append(key) 
     79 
     80    def _setitem_if_mutable(self, key, value): 
     81        self._assert_mutable() 
     82        MultiValueDict.__setitem__(self, key, value) 
     83    __setitem__ = _setitem_if_mutable 
    9284 
    9385    def setlist(self, key, list_): 
    94         if not self._mutable: 
    95             raise AttributeError, "This QueryDict instance is immutable" 
    96         else: 
    97             self.data[key] = list_ 
    98             if not key in self._keys: 
    99                 self._keys.append(key) 
     86        self._assert_mutable() 
     87        MultiValueDict.setlist(self, key, list_) 
     88 
     89    def appendlist(self, key, value): 
     90        self._assert_mutable() 
     91        MultiValueDict.appendlist(self, key, value) 
     92 
     93    def update(self, other_dict): 
     94        self._assert_mutable() 
     95        MultiValueDict.update(self, other_dict) 
     96 
     97    def pop(self, key): 
     98        self._assert_mutable() 
     99        return MultiValueDict.pop(self, key) 
     100 
     101    def popitem(self): 
     102        self._assert_mutable() 
     103        return MultiValueDict.popitem(self) 
     104 
     105    def clear(self): 
     106        self._assert_mutable() 
     107        MultiValueDict.clear(self) 
     108 
     109    def setdefault(self, *args): 
     110        self._assert_mutable() 
     111        return MultiValueDict.setdefault(self, *args) 
    100112 
    101113    def copy(self): 
    102         "Returns a mutable copy of this object" 
    103         cp = MultiValueDict.copy(self) 
     114        "Returns a mutable copy of this object." 
     115        import copy 
     116        # Our custom __setitem__ must be disabled for copying machinery. 
     117        QueryDict.__setitem__ = dict.__setitem__ 
     118        cp = copy.deepcopy(self) 
     119        QueryDict.__setitem__ = QueryDict._setitem_if_mutable 
    104120        cp._mutable = True 
    105121        return cp 
    106122 
    107     def assert_synchronized(self): 
    108         assert(len(self._keys) == len(self.data.keys())), \ 
    109             "QueryDict data structure is out of sync: %s %s" % (str(self._keys), str(self.data)) 
    110  
    111     def items(self): 
    112         "Respect order preserved by self._keys" 
    113         self.assert_synchronized() 
    114         items = [] 
    115         for key in self._keys: 
    116             if key in self.data: 
    117                 items.append((key, self.data[key][0])) 
    118         return items 
    119  
    120     def keys(self): 
    121         self.assert_synchronized() 
    122         return self._keys 
    123  
    124123    def urlencode(self): 
    125124        output = [] 
    126         for k, list_ in self.data.items(): 
     125        for k, list_ in self.lists(): 
    127126            output.extend([urlencode({k: v}) for v in list_]) 
    128127        return '&'.join(output) 
  • django/trunk/docs/request_response.txt

    r1498 r1504  
    141141directly. 
    142142 
    143 ``QueryDict`` implements the following standard dictionary methods: 
    144  
    145     * ``__repr__()`` 
     143``QueryDict`` implements the all standard dictionary methods, because it's a 
     144subclass of dictionary. Exceptions are outlined here: 
    146145 
    147146    * ``__getitem__(key)`` -- Returns the value for the given key. If the key 
     
    149148 
    150149    * ``__setitem__(key, value)`` -- Sets the given key to ``[value]`` 
    151       (a Python list whose single element is ``value``). 
    152  
    153     * ``__contains__(key)`` -- **New in Django development version.*** Returns 
    154       ``True`` if the given key exists. This lets you do, e.g., 
     150      (a Python list whose single element is ``value``). Note that this, as 
     151      other dictionary functions that have side effects, can only be called on 
     152      an immutable ``QueryDict`` (one that was created via ``copy()``). 
     153 
     154    * ``__contains__(key)`` -- **New in Django development version.** Returns 
     155      ``True`` if the given key is set. This lets you do, e.g., 
    155156      ``if "foo" in request.GET``. 
    156  
    157     * ``__len__()`` 
    158157 
    159158    * ``get(key, default)`` -- Uses the same logic as ``__getitem__()`` above, 
     
    162161    * ``has_key(key)`` 
    163162 
     163    * ``setdefault(key, default)`` -- Just like the standard dictionary 
     164      ``setdefault()`` method, except it uses ``__setitem__`` internally. 
     165 
     166    * ``update(other_dict)`` -- Takes either a ``QueryDict`` or standard 
     167      dictionary. Just like the standard dictionary ``update()`` method, except 
     168      it *appends* to the current dictionary items rather than replacing them. 
     169      For example:: 
     170 
     171          >>> q = QueryDict('a=1') 
     172          >>> q = q.copy() # to make it mutable 
     173          >>> q.update({'a': '2'}) 
     174          >>> q.getlist('a') 
     175          ['1', '2'] 
     176          >>> q['a'] # returns the last 
     177          ['2'] 
     178 
    164179    * ``items()`` -- Just like the standard dictionary ``items()`` method, 
    165       except this retains the order for values of duplicate keys, if any. For 
    166       example, if the original query string was ``"a=1&b=2&b=3"``, ``items()`` 
    167       will return ``[("a", ["1"]), ("b", ["2", "3"])]``, where the order of 
    168       ``["2", "3"]`` is guaranteed, but the order of ``a`` vs. ``b`` isn't. 
    169  
    170     * ``keys()`` 
    171  
    172     * ``update(other_dict)`` 
    173  
    174 In addition, it has the following methods: 
     180      except this uses the same last-value logic as ``__getitem()__``. For 
     181      example:: 
     182 
     183           >>> q = QueryDict('a=1&a=2&a=3') 
     184           >>> q.items() 
     185           [('a', '3')] 
     186 
     187    * ``values()`` -- Just like the standard dictionary ``values()`` method, 
     188      except this uses the same last-value logic as ``__getitem()__``. For 
     189      example:: 
     190 
     191           >>> q = QueryDict('a=1&a=2&a=3') 
     192           >>> q.values() 
     193           ['3'] 
     194 
     195In addition, ``QueryDict`` has the following methods: 
    175196 
    176197    * ``copy()`` -- Returns a copy of the object, using ``copy.deepcopy()`` 
     
    179200 
    180201    * ``getlist(key)`` -- Returns the data with the requested key, as a Python 
    181       list. Returns an empty list if the key doesn't exist. 
     202      list. Returns an empty list if the key doesn't exist. It's guaranteed to 
     203      return a list of some sort. 
    182204 
    183205    * ``setlist(key, list_)`` -- Sets the given key to ``list_`` (unlike 
     
    186208    * ``appendlist(key, item)`` -- Appends an item to the internal list 
    187209      associated with key. 
     210 
     211    * ``setlistdefault(key, default_list)`` -- Just like ``setdefault``, except 
     212      it takes a list of values instead of a single value. 
     213 
     214    * ``lists()`` -- Like ``items()``, except it includes all values, as a list, 
     215      for each member of the dictionary. For example:: 
     216 
     217           >>> q = QueryDict('a=1&a=2&a=3') 
     218           >>> q.lists() 
     219           [('a', ['1', '2', '3'])] 
    188220 
    189221    * ``urlencode()`` -- Returns a string of the data in query-string format.