Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using MultiValueDict breaks LinkWidget #1689

Open
pajowu opened this issue Oct 16, 2024 · 5 comments · May be fixed by #1691
Open

Using MultiValueDict breaks LinkWidget #1689

pajowu opened this issue Oct 16, 2024 · 5 comments · May be fixed by #1691

Comments

@pajowu
Copy link

pajowu commented Oct 16, 2024

#1634 introduced using a MultiValueDict as the default empty value for the form data. This however broke using LinkWidget. If the FilterSet is initialised with a false-y data attribute, the FilterSets data attribute is set to an empty MultiValueDict. This gets passed to LinkWidgets value_from_datadict, which then sets its self.data attribute to it. This leads to LinkWidget.render_option passing a MultiValueDict to django.utils.http.urlencode which then renders the option values as lists (?price=%5B%27test-val1%27%5D, i.e. ?price=['test-val1'], instead of ?price=test-val1 for an empty option in the example below).

  def test_widget_multivaluedict(self):
      choices = (
          ("", "---------"),
          ("test-val1", "test-label1"),
          ("test-val2", "test-label2"),
      )

      w = LinkWidget(choices=choices)
      w.value_from_datadict(MultiValueDict(), {}, "price")
      self.assertHTMLEqual(
          w.render("price", ""),
          """
          <ul>
              <li><a class="selected" href="?price=">All</a></li>
              <li><a href="?price=test-val1">test-label1</a></li>
              <li><a href="?price=test-val2">test-label2</a></li>
          </ul>""",
      )

fails as follows:

======================================================================
FAIL: test_widget_multivaluedict (tests.test_widgets.LinkWidgetTests.test_widget_multivaluedict)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "[...]/django-filter/tests/test_widgets.py", line 156, in test_widget_multivaluedict
    self.assertHTMLEqual(
  File "[...]/env/lib/python3.12/site-packages/django/test/testcases.py", line 1044, in assertHTMLEqual
    self.fail(self._formatMessage(msg, standardMsg))
AssertionError: <ul>
<li>
<a class="selected" href="?price=%5B%27%27%5D">
All
</a>
</li><li>
<a  [truncated]... != <ul>
<li>
<a class="selected" href="?price=">
All
</a>
</li><li>
<a href="?price [truncated]...
  <ul>
  <li>
- <a class="selected" href="?price=%5B%27%27%5D">
?                                  ------------

+ <a class="selected" href="?price=">
  All
  </a>
  </li><li>
- <a href="?price=%5B%27test-val1%27%5D">
?                 ------         ------

+ <a href="?price=test-val1">
  test-label1
  </a>
  </li><li>
- <a href="?price=%5B%27test-val2%27%5D">
?                 ------         ------

+ <a href="?price=test-val2">
  test-label2
  </a>
  </li>
  </ul>

----------------------------------------------------------------------
@carltongibson
Copy link
Owner

Hmmm. Prior to the change the default was a dict which has the same behaviour when passed to value_from_datadict():

>>> from django.utils.datastructures import MultiValueDict
>>> d = MultiValueDict()
>>> d.get("prices")
>>> {}.get("prices")

i.e. data should be None in both cases no? 🤔

@pajowu
Copy link
Author

pajowu commented Oct 16, 2024

I don't fully understand your comment, but just in case:

The problem is not the .get-Method, but the self.data attribute getting set which is then used further in render_option:

data = self.data.copy()
data[name] = option_value
selected = data == self.data or option_value in selected_choices
try:
url = data.urlencode()
except AttributeError:
url = urlencode(data)

Urlencode then encodes the lists which MultiValueDict returns as their string-representation (as seen in the test output)

@pajowu
Copy link
Author

pajowu commented Oct 16, 2024

>>> from django.utils.http import MultiValueDict, urlencode
>>>
>>> d = MultiValueDict()
>>> d['prices'] = ''
>>> urlencode(d)
'prices=%5B%27%27%5D'
>>>
>>>
>>> d = {}
>>> d["prices"] = ""
>>> urlencode(d)
'prices='

@carltongibson
Copy link
Owner

Ok, so we'll likely have to cast to a dict there. The data should be a multidict, so LinkWidget should handle that.

Good spot.

Fancy making a PR with the regression test and tweak?

@carltongibson
Copy link
Owner

@pajowu Actually, it looks like we want a QueryDict in this case (which provides the urlencode() method.

Do you fancy trying the change from #1634 using that to see the effect?

(Possibly we can drop the try block, since data should no longer be a dict in any case)

@pajowu pajowu linked a pull request Oct 17, 2024 that will close this issue
pajowu added a commit to okfde/fragdenstaat_de that referenced this issue Oct 17, 2024
pajowu added a commit to okfde/fragdenstaat_de that referenced this issue Oct 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants