Repository: pallets/werkzeug (python)

Issue:
to_dict method on CombinedMultiDict doesn't give combined result for overlapping keys
Whenever I call `to_dict(flat=False)` on a `CombinedMultiDict`, obtained from combining two MultiDicts with overlapping keys, only the values of the last MultiDict are returned. 

```python
# altered from documentation to make keys overlapping.
from werkzeug.datastructures import CombinedMultiDict, MultiDict
post = MultiDict([('foo', 'bar'),('foo', 'bar2')])
get = MultiDict([('foo', 'bar3')])
combined = CombinedMultiDict([get, post])
combined.getlist('foo')
>> ['bar3', 'bar', 'bar2']
combined.to_dict(flat=False)
>> {'foo': ['bar3']}
```

From a class that intends to combine two MultiDicts, I expect the result from one of its methods to be a combined result.
Environment:
- Python version: 3.6.8
- Werkzeug version: 1.0.1


Pull request description:
Fix CombinedMultiDict.to_dict result with flat=False
`CombinedMultiDict.to_dict(flat=False)` now returns `dict` with all values for each key returned as lists.

```python3
d1 = MultiDict([('foo', 'bar'),('foo', 'bar2')])
d2 = MultiDict([('foo', 'bar3')])
d = CombinedMultiDict([d1, d2])
d.to_dict(flat=False)
>>> {'foo': ['bar', 'bar2', 'bar3']}
```

Without this fix, only the value from the first MultiDict added would be present.

```python3
d.to_dict(flat=False)
>>> {'foo': ['bar', 'bar2']}
```

- fixes #2189  

Checklist:

- [x] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change.
- [ ] Add or update relevant docs, in the docs folder and in code.
- [x] Add an entry in `CHANGES.rst` summarizing the change and linking to the issue.
- [ ] Add `.. versionchanged::` entries in any relevant code docs.
- [x] Run `pre-commit` hooks and fix any issues.
- [x] Run `pytest` and `tox`, no tests failed.


Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index bf157b56..3ebba3fe 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -12,6 +12,8 @@ Unreleased
     values :pr:`2142`
 -   Prevent double encoding of redirect URL when ``merge_slash=True``
     for ``Rule.match``. :issue:`2157`
+-   ``CombinedMultiDict.to_dict`` with ``flat=False`` considers all
+    component dicts when building value lists. :issue:`2189`
 
 
 Version 2.0.1
diff --git a/src/werkzeug/datastructures.py b/src/werkzeug/datastructures.py
index 5fb59281..b4bee5f9 100644
--- a/src/werkzeug/datastructures.py
+++ b/src/werkzeug/datastructures.py
@@ -1526,10 +1526,10 @@ class CombinedMultiDict(ImmutableMultiDictMixin, MultiDict):
                      contain the first item for each key.
         :return: a :class:`dict`
         """
-        rv = {}
-        for d in reversed(self.dicts):
-            rv.update(d.to_dict(flat))
-        return rv
+        if flat:
+            return dict(self.items())
+
+        return dict(self.lists())
 
     def __len__(self):
         return len(self._keys_impl())


Test patch:
diff --git a/tests/test_datastructures.py b/tests/test_datastructures.py
index 7bed93f4..a46599fd 100644
--- a/tests/test_datastructures.py
+++ b/tests/test_datastructures.py
@@ -595,10 +595,14 @@ class TestCombinedMultiDict:
         d["foo"] = "blub"
 
         # make sure lists merges
-        md1 = ds.MultiDict((("foo", "bar"),))
+        md1 = ds.MultiDict((("foo", "bar"), ("foo", "baz")))
         md2 = ds.MultiDict((("foo", "blafasel"),))
         x = self.storage_class((md1, md2))
-        assert list(x.lists()) == [("foo", ["bar", "blafasel"])]
+        assert list(x.lists()) == [("foo", ["bar", "baz", "blafasel"])]
+
+        # make sure dicts are created properly
+        assert x.to_dict() == {"foo": "bar"}
+        assert x.to_dict(flat=False) == {"foo": ["bar", "baz", "blafasel"]}
 
     def test_length(self):
         d1 = ds.MultiDict([("foo", "1")])