Repository: liqd/a4-meinberlin

Issue:
proposal list - url params are not remembered correctly after adding a token
**URL:** https://meinberlin-dev.liqd.net/projekte/module/burgerinnenhaushalt-3-phasen-23/ 
**user:** 
**expected behaviour:** filtering is kept after posting a token
**behaviour:** filtering gets lost if it was applied on react list
**important screensize:**
**device & browser:** 
**Comment/Question:** The filters/query params are passed via a tempatetag [here](https://github.com/liqd/a4-meinberlin/blob/7c6f2c3add38af5a4383a871a1d04d3c3adb3c63/meinberlin/apps/budgeting/templates/meinberlin_budgeting/proposal_list.html#L26). The problem is that request.GET does not get updated unless the page is reloaded, so it will not reflect any filters that have been changed by react. Instead, it will keep the filters from last page reload (e.g. when coming from map with filters, those filters will be passed, no matter what was changed in the meantime).

Screenshot?



Discussion:

Resolving patch:
diff --git a/meinberlin/apps/budgeting/views.py b/meinberlin/apps/budgeting/views.py
--- a/meinberlin/apps/budgeting/views.py
+++ b/meinberlin/apps/budgeting/views.py
@@ -3,6 +3,7 @@
 from urllib.parse import urlparse
 
 import django_filters
+from django.shortcuts import redirect
 from django.urls import resolve
 from django.urls import reverse
 from django.utils.translation import gettext_lazy as _
@@ -123,6 +124,16 @@ def post(self, request, *args, **kwargs):
                 datetime.datetime.now() + TOKEN_SESSION_EXPIRE
             ).timestamp()
             kwargs["valid_token_present"] = True
+            # redirect to the list view, but keep filters after successful
+            # token addition
+            list_url = request.path
+            parameter = request.GET.copy()
+            if not parameter.get("mode") or "list" not in parameter.get("mode"):
+                parameter.setlist("mode", ["list"])
+            encoded_parameter = "?" + parameter.urlencode()
+            return redirect(list_url + encoded_parameter)
+        # stay on the same page if the token form has errors,
+        # add the form including errors so that they are displayed.
         kwargs["token_form"] = token_form
         context = super().get_context_data(**kwargs)
         return self.render_to_response(context)


Test patch:
diff --git a/tests/budgeting/test_views_integration.py b/tests/budgeting/test_views_integration.py
--- a/tests/budgeting/test_views_integration.py
+++ b/tests/budgeting/test_views_integration.py
@@ -145,12 +145,12 @@ def test_list_view_token_form(
         assert not response.context["valid_token_present"]
 
         response = client.post(url, data)
-        assert response.status_code == 200
+        assert response.status_code == 302
         assert "voting_tokens" in client.session
         assert "token_expire_date" in client.session
-        assert "token_form" in response.context
 
         response = client.get(url)
+        assert "token_form" in response.context
         assert response.context["valid_token_present"]
 
     other_module = module_factory()
@@ -173,6 +173,65 @@ def test_list_view_token_form(
         assert "voting_tokens" not in client.session
 
 
+@pytest.mark.django_db
+def test_list_view_token_form_redirect(
+    client, user, phase_factory, proposal_factory, voting_token_factory, module_factory
+):
+    phase, module, project, item = setup_phase(
+        phase_factory, proposal_factory, phases.VotingPhase
+    )
+    url = project.get_absolute_url()
+    token = voting_token_factory(module=module)
+
+    data = {"token": str(token)}
+    invalid_data = {"token": "madeupandinvalid"}
+
+    with freeze_phase(phase):
+        response = client.get(url)
+        assert "token_form" in response.context
+
+        # valid token should trigger redirect to list
+        response = client.post(url, data)
+        assert response.status_code == 302
+        assert response.url == url + "?mode=list"
+        # end session to remove token
+        client.session.flush()
+
+        parameter = "?bli=blub"
+        response = client.post(url + parameter, data)
+        assert response.status_code == 302
+        assert response.url == url + parameter + "&mode=list"
+        # end session to remove token
+        client.session.flush()
+
+        parameter = "?mode=map&bli=blub"
+        response = client.post(url + parameter, data)
+        assert response.status_code == 302
+        assert response.url == url + "?mode=list&bli=blub"
+        # end session to remove token
+        client.session.flush()
+
+        parameter = "?mode=list&bli=blub"
+        response = client.post(url + parameter, data)
+        assert response.status_code == 302
+        assert response.url == url + "?mode=list&bli=blub"
+        # end session to remove token
+        client.session.flush()
+
+        # posting invalid token should stay on map
+        parameter = "?bli=blub"
+        response = client.post(url + parameter, invalid_data)
+        assert response.status_code == 200
+        assert response.request["QUERY_STRING"] == parameter.lstrip("?")
+        # end session to remove token
+        client.session.flush()
+
+        parameter = "?mode=map&bli=blub"
+        response = client.post(url + parameter, invalid_data)
+        assert response.status_code == 200
+        assert response.request["QUERY_STRING"] == parameter.lstrip("?")
+
+
 @pytest.mark.django_db
 def test_list_view_tokens_for_different_modules(
     client, phase_factory, proposal_factory, voting_token_factory
@@ -186,7 +245,7 @@ def test_list_view_tokens_for_different_modules(
 
     with freeze_phase(phase_1):
         response = client.post(url_1, data_1)
-        assert response.status_code == 200
+        assert response.status_code == 302
         assert "voting_tokens" in client.session
         assert str(module_1.id) in client.session["voting_tokens"]
 
@@ -202,7 +261,7 @@ def test_list_view_tokens_for_different_modules(
 
     with freeze_phase(phase_2):
         response = client.post(url_2, data_2)
-        assert response.status_code == 200
+        assert response.status_code == 302
         assert "voting_tokens" in client.session
         assert str(module_2.id) in client.session["voting_tokens"]
         assert str(module_1.id) in client.session["voting_tokens"]
diff --git a/tests/votes/test_voting_token_middleware.py b/tests/votes/test_voting_token_middleware.py
--- a/tests/votes/test_voting_token_middleware.py
+++ b/tests/votes/test_voting_token_middleware.py
@@ -45,7 +45,7 @@ def test_token_deleted_from_session(
     with freeze_time(phase_started):
         # post token
         response = apiclient.post(url_project, token_data)
-        assert response.status_code == 200
+        assert response.status_code == 302
 
         # post and delete token vote
         response = apiclient.post(url_post, proposal_data, format="json")
@@ -66,7 +66,7 @@ def test_token_deleted_from_session(
 
         # post token again
         response = apiclient.post(url_project, token_data)
-        assert response.status_code == 200
+        assert response.status_code == 302
 
         response = apiclient.post(url_post, proposal_data, format="json")
         assert response.status_code == status.HTTP_201_CREATED
@@ -103,8 +103,7 @@ def test_token_expire_date_renewed(
 
     with freeze_time(phase_1_started):
         response = apiclient.post(url_module_1, token_data_1)
-        assert response.status_code == 200
-        response.context_data["token_form"].is_valid()
+        assert response.status_code == 302
 
         assert "voting_tokens" in apiclient.session
         assert str(module_1.id) in apiclient.session["voting_tokens"]
@@ -116,8 +115,7 @@ def test_token_expire_date_renewed(
 
     with freeze_time(phase_2_started):
         response = apiclient.post(url_module_2, token_data_2)
-        response.context_data["token_form"].is_valid()
-        assert response.status_code == 200
+        assert response.status_code == 302
 
         assert "voting_tokens" in apiclient.session
         assert str(module_1.id) in apiclient.session["voting_tokens"]