From de939b349604ea523e0b9b99f5f2219eac1dd794 Mon Sep 17 00:00:00 2001 From: krmax44 Date: Mon, 2 Feb 2026 15:23:29 +0100 Subject: [PATCH 01/17] =?UTF-8?q?=F0=9F=A9=B9=20hide=20"is=20foi"=20confir?= =?UTF-8?q?m=20when=20text=20editing=20is=20hidden?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- froide/foirequest/views/make_request.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/froide/foirequest/views/make_request.py b/froide/foirequest/views/make_request.py index 21cf58c49..84d2adeeb 100644 --- a/froide/foirequest/views/make_request.py +++ b/froide/foirequest/views/make_request.py @@ -792,9 +792,9 @@ def get_context_data(self, **kwargs): if self.request.GET.get("single") is not None: is_multi = False - # skip "i confirm" nag heuristically for all who can multi-request - # TODO: find better heuristic for "trusted users"? - confirm_required = not is_multi + # skip "i confirm this is a foi request" for users who can multi-request + # or if editing is hidden (less friction for pre-written requests) + confirm_required = not (config["hide_editing"] or is_multi) campaigns = Campaign.objects.get_active() From 9cab05d25cb7a0587dd4de7afe72937800cd07af Mon Sep 17 00:00:00 2001 From: krmax44 Date: Mon, 2 Feb 2026 15:43:10 +0100 Subject: [PATCH 02/17] =?UTF-8?q?=F0=9F=99=88=20add=20revert/re-add=20requ?= =?UTF-8?q?est=20flow=20commits=20to=20blame=20ignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .git-blame-ignore-revs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 3e98777cc..63651cccc 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -5,4 +5,7 @@ # Format html files with djlint 3698b97c143c773639dd0e75617cd8d8fb034131 # Format with prettier -43bc670f81530fa7f62d1e4f5c1b5f8c8edbb92a \ No newline at end of file +43bc670f81530fa7f62d1e4f5c1b5f8c8edbb92a +# revert/bring back request flow +2bd30cb88398f0f2a02190d81ceeb129dbe0808c +9483cfdeb3e6b3a036b460c78d9da074f16a020a \ No newline at end of file From 7534dd5853690b634eacb2619e98e3e54d623011 Mon Sep 17 00:00:00 2001 From: krmax44 Date: Mon, 2 Feb 2026 15:44:25 +0100 Subject: [PATCH 03/17] =?UTF-8?q?=F0=9F=94=A5=20remove=20collapsible=20tes?= =?UTF-8?q?t=20from=20foirequest=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit this is testing bootstrap --- froide/tests/live/test_request.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/froide/tests/live/test_request.py b/froide/tests/live/test_request.py index 2903dc876..fbb04ae0b 100644 --- a/froide/tests/live/test_request.py +++ b/froide/tests/live/test_request.py @@ -244,17 +244,6 @@ async def test_make_request_logged_out_with_existing_account(page, live_server, assert pb in req.publicbodies.all() -@pytest.mark.django_db -@pytest.mark.asyncio(loop_scope="session") -async def test_collapsed_menu(page, live_server): - SCREEN_SIZE = (400, 800) - await page.set_viewport_size({"width": SCREEN_SIZE[0], "height": SCREEN_SIZE[1]}) - await page.goto(live_server.url + reverse("index")) - await expect(page.locator(".navbar form[role=search]")).not_to_be_visible() - await page.locator(".navbar-toggler").click() - await expect(page.locator(".navbar form[role=search]")).to_be_visible() - - @pytest.mark.django_db @pytest.mark.asyncio(loop_scope="session") @pytest.mark.parametrize( From 36c8a7356b0fd33f6f67ba0b874830f65bd05a7a Mon Sep 17 00:00:00 2001 From: krmax44 Date: Mon, 2 Feb 2026 16:38:30 +0100 Subject: [PATCH 04/17] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20replace=20timeouts,?= =?UTF-8?q?=20extract=20constants?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- froide/tests/live/test_request.py | 73 +++++++++++++------------------ 1 file changed, 31 insertions(+), 42 deletions(-) diff --git a/froide/tests/live/test_request.py b/froide/tests/live/test_request.py index fbb04ae0b..26ffce758 100644 --- a/froide/tests/live/test_request.py +++ b/froide/tests/live/test_request.py @@ -6,7 +6,7 @@ from django.utils import timezone import pytest -from playwright.async_api import expect +from playwright.async_api import Page, expect from froide.foirequest.models import FoiRequest, RequestDraft from froide.foirequest.tests import factories @@ -16,10 +16,15 @@ User = get_user_model() +req_title = "FoiRequest Number" +req_body = "Documents describing & something..." + @pytest.mark.django_db @pytest.mark.asyncio(loop_scope="session") -async def test_make_not_logged_in_request(page, live_server, public_body_with_index): +async def test_make_not_logged_in_request( + page: Page, live_server, public_body_with_index +): pb = PublicBody.objects.all().first() await go_to_make_request_url(page, live_server) await page.locator("request-page .btn-primary >> nth=0").click() @@ -40,9 +45,8 @@ async def test_make_not_logged_in_request(page, live_server, public_body_with_in await page.locator("[name=terms]").click() await page.locator("#step_create_account .btn-primary").click() - req_title = "FoiRequest Number" await page.fill("[name=subject]", req_title) - await page.fill("[name=body]", "Documents describing something...") + await page.fill("[name=body]", req_body) await page.locator("[name=confirm]").click() await page.locator("#step_write_request .btn-primary").click() @@ -50,16 +54,7 @@ async def test_make_not_logged_in_request(page, live_server, public_body_with_in await page.locator("#send-request-button").click() - new_account_url = reverse("account-new") - - # FIXME - await page.wait_for_timeout(1000) - # none of these worked, unexpectedly: - # await page.wait_for_url('*'+new_account-url+'*') - # await page.wait_for_url('*') - # await page.wait_for_load_state() - - assert new_account_url in page.url + await page.wait_for_url(f"**{reverse('account-new')}*") new_user = User.objects.get(email=user_email) assert not new_user.private @@ -83,14 +78,15 @@ async def test_make_not_logged_in_request(page, live_server, public_body_with_in @pytest.mark.django_db @pytest.mark.asyncio(loop_scope="session") -async def test_make_not_logged_in_request_to_public_body(page, live_server, world): +async def test_make_not_logged_in_request_to_public_body( + page: Page, live_server, world +): pb = PublicBody.objects.all().first() assert pb await go_to_make_request_url(page, live_server, pb=pb) - req_title = "FoiRequest Number" await page.fill("[name=subject]", req_title) - await page.fill("[name=body]", "Documents describing something...") + await page.fill("[name=body]", req_body) await page.locator("[name=confirm]").click() await page.locator("#step_write_request .btn-primary").click() @@ -110,9 +106,8 @@ async def test_make_not_logged_in_request_to_public_body(page, live_server, worl await page.locator("#send-request-button").click() - new_account_url = reverse("account-new") - await page.wait_for_timeout(1000) - assert new_account_url in page.url + await page.wait_for_url(f"**{reverse('account-new')}*") + new_user = User.objects.get(email=user_email) assert new_user.first_name == user_first_name assert new_user.last_name == user_last_name @@ -140,23 +135,21 @@ async def test_make_logged_in_request( await expect(buttons).to_have_count(2) await page.locator(".search-results .search-result .btn >> nth=0").click() - req_title = "FoiRequest Number" - body_text = "Documents describing & something..." await page.fill("[name=subject]", req_title) - await page.fill("[name=body]", body_text) + await page.fill("[name=body]", req_body) await page.locator("[name=confirm]").click() await page.locator("#step_write_request .btn-primary").click() await page.locator("#step_request_public .btn-primary").click() await page.locator("#send-request-button").click() - request_sent = reverse("foirequest-request_sent") - await page.wait_for_timeout(1000) - assert request_sent in page.url + + await page.wait_for_url(f"**{reverse('foirequest-request_sent')}*") + req = FoiRequest.objects.filter(user=dummy_user).order_by("-id")[0] assert req.title == req_title - assert req.description in body_text - assert body_text, req.messages[0].plaintext + assert req.description in req_body + assert req_body, req.messages[0].plaintext assert req.public assert req.public_body == pb assert req.status == "awaiting_response" @@ -165,7 +158,7 @@ async def test_make_logged_in_request( @pytest.mark.django_db @pytest.mark.asyncio(loop_scope="session") async def test_make_logged_in_request_too_many( - page, + page: Page, live_server, foi_request_factory, foi_message_factory, @@ -183,34 +176,32 @@ async def test_make_logged_in_request_too_many( pb = PublicBody.objects.all().first() await go_to_make_request_url(page, live_server, pb=pb) - req_title = "FoiRequest Number" - body_text = "Documents describing & something..." await page.fill("[name=subject]", req_title) - await page.fill("[name=body]", body_text) + await page.fill("[name=body]", req_body) await page.locator("[name=confirm]").click() await page.locator("#step_write_request .btn-primary").click() await page.locator("#step_request_public .btn-primary").click() await page.locator("#send-request-button").click() - make_request = reverse("foirequest-make_request") - await page.wait_for_timeout(1000) - assert make_request in page.url + + await page.wait_for_load_state() + alert = page.locator(".alert-danger", has_text="exceeded your request limit") await expect(alert).to_be_visible() @pytest.mark.django_db @pytest.mark.asyncio(loop_scope="session") -async def test_make_request_logged_out_with_existing_account(page, live_server, world): +async def test_make_request_logged_out_with_existing_account( + page: Page, live_server, world +): pb = PublicBody.objects.all().first() user = User.objects.get(username="dummy") await go_to_make_request_url(page, live_server, pb=pb) - req_title = "FoiRequest Number" - body_text = "Documents describing & something..." user_first_name = user.first_name user_last_name = user.last_name await page.fill("[name=subject]", req_title) - await page.fill("[name=body]", body_text) + await page.fill("[name=body]", req_body) await page.locator("[name=confirm]").click() await page.locator("#step_write_request .btn-primary").click() @@ -230,9 +221,7 @@ async def test_make_request_logged_out_with_existing_account(page, live_server, draft_count = RequestDraft.objects.filter(user=None).count() await page.locator("#send-request-button").click() - new_account_url = reverse("account-new") - await page.wait_for_timeout(1000) - assert new_account_url in page.url + await page.wait_for_url(f"**{reverse('account-new')}*") new_count = FoiRequest.objects.filter(user=user).count() assert old_count == new_count From af340203b779bddb7d612c1570fc1095d8165966 Mon Sep 17 00:00:00 2001 From: krmax44 Date: Mon, 2 Feb 2026 16:45:20 +0100 Subject: [PATCH 05/17] =?UTF-8?q?=F0=9F=93=9D=20add=20documentation=20to?= =?UTF-8?q?=20the=20request=20form=20config=20params?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- froide/foirequest/views/make_request.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/froide/foirequest/views/make_request.py b/froide/foirequest/views/make_request.py index 84d2adeeb..90d05dbf4 100644 --- a/froide/foirequest/views/make_request.py +++ b/froide/foirequest/views/make_request.py @@ -81,12 +81,12 @@ class MakeRequestView(FormView): form_class = RequestForm template_name = "foirequest/request.html" FORM_CONFIG_PARAMS = ( - "hide_similar", - "hide_public", - "hide_draft", - "hide_publicbody", - "hide_full_text", - "hide_editing", + "hide_similar", # hide similar request search + "hide_public", # hide option to make request non-public + "hide_draft", # hide button to save request as draft + "hide_publicbody", # hide option to change public body. use on public-body specific request url + "hide_full_text", # hide option to edit the request boilerplate text + "hide_editing", # hide step to write request text. use together with body param ) draft = None From 37a0e66381b6a9ddea32969685f8b717683ebb36 Mon Sep 17 00:00:00 2001 From: krmax44 Date: Mon, 2 Feb 2026 16:53:50 +0100 Subject: [PATCH 06/17] =?UTF-8?q?=E2=9C=85=20assert=20request=20body?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- froide/tests/live/test_request.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/froide/tests/live/test_request.py b/froide/tests/live/test_request.py index 26ffce758..70f10a0c3 100644 --- a/froide/tests/live/test_request.py +++ b/froide/tests/live/test_request.py @@ -63,6 +63,8 @@ async def test_make_not_logged_in_request( assert req.public assert req.public_body == pb assert req.status == FoiRequest.STATUS.AWAITING_USER_CONFIRMATION + assert req.description == req_body + assert req_body in req.messages[0].plaintext message = mail.outbox[0] match = re.search(r"http://[^/]+(/.+)", message.body) activate_url = match.group(1) @@ -117,6 +119,8 @@ async def test_make_not_logged_in_request_to_public_body( assert req.public assert req.public_body == pb assert req.status == FoiRequest.STATUS.AWAITING_USER_CONFIRMATION + assert req.description == req_body + assert req_body in req.messages[0].plaintext @pytest.mark.django_db @@ -148,8 +152,8 @@ async def test_make_logged_in_request( req = FoiRequest.objects.filter(user=dummy_user).order_by("-id")[0] assert req.title == req_title - assert req.description in req_body - assert req_body, req.messages[0].plaintext + assert req.description == req_body + assert req_body in req.messages[0].plaintext assert req.public assert req.public_body == pb assert req.status == "awaiting_response" From 568ed50a85c3f9cb791fc90de07b1e9c8785b2f1 Mon Sep 17 00:00:00 2001 From: krmax44 Date: Mon, 2 Feb 2026 17:06:34 +0100 Subject: [PATCH 07/17] =?UTF-8?q?=E2=9C=85=20test=20editing=20full=20reque?= =?UTF-8?q?st=20text,=20skip=20similar=20search?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- froide/tests/live/test_request.py | 84 +++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/froide/tests/live/test_request.py b/froide/tests/live/test_request.py index 70f10a0c3..7f281c8f1 100644 --- a/froide/tests/live/test_request.py +++ b/froide/tests/live/test_request.py @@ -237,6 +237,90 @@ async def test_make_request_logged_out_with_existing_account( assert pb in req.publicbodies.all() +@pytest.mark.django_db +@pytest.mark.asyncio(loop_scope="session") +async def test_edit_request_boilerplate( + page: Page, live_server, public_body_with_index, dummy_user +): + pb = PublicBody.objects.all().first() + await do_login(page, live_server) + await go_to_make_request_url(page, live_server) + + await page.get_by_role("checkbox", name="Skip this part next time and").check() + await page.get_by_role("button", name="Skip").click() + + await page.locator(".search-public_bodies").fill(pb.name) + await page.get_by_role("button", name="Search").click() + await page.get_by_role("link", name="Make request").first.click() + await page.get_by_role("textbox", name="Subject:").fill(req_title) + + await page.get_by_role("checkbox", name="Don't wrap in template").check() + + body = page.get_by_role("textbox", name="Request body:") + initial_template = await body.input_value() + assert initial_template + + await body.fill(req_body) + + await page.get_by_role("checkbox", name="I confirm that I am not").check() + await page.get_by_role("button", name="Next").click() + + await ( + page.locator("#step_request_public").get_by_role("button", name="Next").click() + ) + await page.get_by_role("button", name="Submit request").click() + + await page.wait_for_url(f"**{reverse('foirequest-request_sent')}*") + + req = FoiRequest.objects.filter(user=dummy_user).order_by("-id")[0] + assert req.title == req_title + assert req.description in req_body + assert all( + line.strip() not in req.messages[0].plaintext + for line in initial_template.split("\n") + if line.strip() + ) + + +@pytest.mark.django_db +@pytest.mark.asyncio(loop_scope="session") +async def test_skip_search_similar( + page: Page, live_server, world, public_body_with_index +): + pb = PublicBody.objects.all().first() + await do_login(page, live_server) + await go_to_make_request_url(page, live_server) + + await page.get_by_role("checkbox", name="Skip this part next time and").check() + await page.get_by_role("button", name="Skip").click() + + await page.locator(".search-public_bodies").fill(pb.name) + await page.get_by_role("button", name="Search").click() + await page.get_by_role("link", name="Make request").first.click() + await page.get_by_role("textbox", name="Subject:").fill(req_title) + await page.get_by_role("textbox", name="Request body:").fill(req_body) + await page.get_by_role("checkbox", name="I confirm that I am not").check() + await page.get_by_role("button", name="Next").click() + + await ( + page.locator("#step_request_public").get_by_role("button", name="Next").click() + ) + await page.get_by_role("button", name="Submit request").click() + + await page.wait_for_url(f"**{reverse('foirequest-request_sent')}*") + + await go_to_make_request_url(page, live_server) + + await page.get_by_role("heading", name="Choose public body").click() + + await expect(page.get_by_role("heading", name="Choose public body")).to_be_visible() + + await page.get_by_role("link", name="Similar requests").click() + await expect( + page.get_by_role("checkbox", name="Skip this part next time and") + ).to_be_checked() + + @pytest.mark.django_db @pytest.mark.asyncio(loop_scope="session") @pytest.mark.parametrize( From edf48951c6bcb1d4ac59de63405aff28545211df Mon Sep 17 00:00:00 2001 From: krmax44 Date: Mon, 2 Feb 2026 17:34:53 +0100 Subject: [PATCH 08/17] =?UTF-8?q?=F0=9F=92=84=20adjust=20visual=20hierarch?= =?UTF-8?q?y=20of=20campaigns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/foirequest/request.html | 22 ++++++------ .../makerequest/intro-campaigns.vue | 36 +++++++++++-------- .../components/makerequest/request-page.vue | 4 --- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/froide/foirequest/templates/foirequest/request.html b/froide/foirequest/templates/foirequest/request.html index 99e684331..2e0025ffc 100644 --- a/froide/foirequest/templates/foirequest/request.html +++ b/froide/foirequest/templates/foirequest/request.html @@ -61,18 +61,16 @@ @@ -37,3 +35,13 @@ defineProps({ const emit = defineEmits('stepNext') + + diff --git a/frontend/javascript/components/makerequest/request-page.vue b/frontend/javascript/components/makerequest/request-page.vue index df4735bcc..106b40116 100644 --- a/frontend/javascript/components/makerequest/request-page.vue +++ b/frontend/javascript/components/makerequest/request-page.vue @@ -1007,8 +1007,4 @@ legend { color: #999; text-decoration: underline; } - -:deep(.campaign-logo) { - max-height: 6em; -} From a6549bfe42690c540a852b93ed4a7ce143082cdd Mon Sep 17 00:00:00 2001 From: krmax44 Date: Thu, 5 Feb 2026 15:02:55 +0100 Subject: [PATCH 09/17] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20move=20public=20body?= =?UTF-8?q?=20help=20text=20to=20snippet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- froide/foirequest/templates/foirequest/request.html | 4 +--- .../foirequest/snippets/request_publicbody_helptext.html | 4 ++++ 2 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 froide/foirequest/templates/foirequest/snippets/request_publicbody_helptext.html diff --git a/froide/foirequest/templates/foirequest/request.html b/froide/foirequest/templates/foirequest/request.html index 2e0025ffc..58fc70472 100644 --- a/froide/foirequest/templates/foirequest/request.html +++ b/froide/foirequest/templates/foirequest/request.html @@ -86,9 +86,7 @@

{% blocktrans %}Choose public body{% endblocktrans %}

{% include "foirequest/snippets/request_publicbody_search_hint.html" %}