From cb1939339108459348704945568661fe07baf513 Mon Sep 17 00:00:00 2001 From: Antoine LAURENT Date: Wed, 8 Jan 2025 23:42:34 +0100 Subject: [PATCH 1/2] gps: Use a table to display beneficiary list --- .../gps/includes/memberships_results.html | 83 ++++++------ itou/templates/gps/my_groups.html | 6 +- itou/www/gps/views.py | 12 +- tests/gps/__snapshots__/test_views.ambr | 123 ++++++------------ tests/gps/test_views.py | 30 +++-- 5 files changed, 108 insertions(+), 146 deletions(-) diff --git a/itou/templates/gps/includes/memberships_results.html b/itou/templates/gps/includes/memberships_results.html index fbc6a840df..e7d533c6b9 100644 --- a/itou/templates/gps/includes/memberships_results.html +++ b/itou/templates/gps/includes/memberships_results.html @@ -1,53 +1,52 @@ {% load str_filters %} {% load matomo %} -
+
{% if not memberships_page %} -
+

Aucun résultat.

{% else %} - {% for membership in memberships_page %} - - {% endfor %} +
+ + + + + + + + + + + + {% for membership in memberships_page %} + + + + + + + {% endfor %} + +
Liste des bénéficiaires
Prénom NomSuivi depuisRéférent⸱eNbr d'intervenants
+ + {{ membership.follow_up_group.beneficiary.get_full_name }} + + {{ membership.created_at|date:"d/m/Y" }} + {% if membership.is_referent %} + vous êtes référent + {% elif membership.follow_up_group.referent %} + {{ membership.follow_up_group.referent.0.member.get_full_name }} + {% else %} + pas de référent + {% endif %} + {{ membership.nb_members }}
+
{% include "includes/pagination.html" with page=memberships_page boost=True boost_target="#follow-up-groups-section" boost_indicator="#follow-up-groups-section" %} {% endif %} -
+ diff --git a/itou/templates/gps/my_groups.html b/itou/templates/gps/my_groups.html index aae1233655..5b0852a5be 100644 --- a/itou/templates/gps/my_groups.html +++ b/itou/templates/gps/my_groups.html @@ -12,7 +12,7 @@ {% endblock %} {% block title_content %} -
+

Mes bénéficiaires

diff --git a/itou/www/gps/views.py b/itou/www/gps/views.py index f92a8ada7e..c838fe2a29 100644 --- a/itou/www/gps/views.py +++ b/itou/www/gps/views.py @@ -1,6 +1,6 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.core.exceptions import PermissionDenied -from django.db.models import Count +from django.db.models import Count, Prefetch from django.http import HttpResponseRedirect from django.shortcuts import render from django.urls import reverse, reverse_lazy @@ -30,14 +30,20 @@ def my_groups(request, template_name="gps/my_groups.html"): .annotate(nb_members=Count("follow_up_group__members")) .order_by("-created_at") .select_related("follow_up_group", "follow_up_group__beneficiary", "member") - .prefetch_related("follow_up_group__members") + .prefetch_related( + Prefetch( + "follow_up_group__memberships", + queryset=FollowUpGroupMembership.objects.filter(is_referent=True)[:1], + to_attr="referent", + ), + ) ) filters_form = MembershipsFiltersForm(memberships_qs=memberships, data=request.GET or None) if filters_form.is_valid(): memberships = filters_form.filter() - memberships_page = pager(memberships, request.GET.get("page"), items_per_page=10) + memberships_page = pager(memberships, request.GET.get("page"), items_per_page=50) context = { "back_url": reverse("dashboard:index"), diff --git a/tests/gps/__snapshots__/test_views.ambr b/tests/gps/__snapshots__/test_views.ambr index f0ab3091ec..180d37bec4 100644 --- a/tests/gps/__snapshots__/test_views.ambr +++ b/tests/gps/__snapshots__/test_views.ambr @@ -2,7 +2,6 @@ # name: test_beneficiary_details '''
-

Données administratives


@@ -19,43 +18,31 @@
  • Date de naissance - 01/01/1990 -
  • Adresse e-mail jane.doe@test.local - -
  • Téléphone - 06 12 34 56 78 - - -
  • Adresse -
    12 rue Georges Bizet, 35000 Rennes
    -
  • Numéro de sécurité sociale - 290010101010125 -
  • @@ -63,24 +50,11 @@
    • Niveau de formation - Formation de niveau BAC -
    • - - - - - - - -
    • Identifiant France Travail (ex Pôle emploi) - - Non renseigné -
    • @@ -90,11 +64,9 @@ conseiller emploi France Travail
      -

      La fonctionnalité est en test dans deux départements et sera bientôt disponible pour votre territoire.

      -
    • @@ -102,14 +74,9 @@
    - - - -

    Intervenant

      -
    • @@ -117,52 +84,38 @@

      Pierre DUPONT - - (prescripteur pour Les Olivades) -

      -
      • pierre.dupont@test.local - -
      • - 06 12 34 56 78 - - -
      • Suivi depuis le 21/06/2024
      • -
      • Référent
      • -
    - - ''' # --- @@ -175,7 +128,6 @@ conseiller emploi France Travail
    -
    • Test MacTest @@ -187,10 +139,8 @@ Copier -
    -
    ''' @@ -204,9 +154,7 @@ conseiller emploi France Travail
    -

    Le conseiller emploi France Travail de ce bénéficiaire n’est pas connu.

    -
    ''' @@ -220,48 +168,55 @@ conseiller emploi France Travail
    -

    La fonctionnalité est en test dans deux départements et sera bientôt disponible pour votre territoire.

    -
    ''' # --- # name: test_my_groups[test_my_groups__group_card] ''' -
    + + + +

    Intervenant

    + + ''' # --- @@ -128,6 +175,7 @@ conseiller emploi France Travail
    + +
    ''' @@ -154,7 +204,9 @@ conseiller emploi France Travail
    +

    Le conseiller emploi France Travail de ce bénéficiaire n’est pas connu.

    +
    ''' @@ -168,13 +220,459 @@ conseiller emploi France Travail
    +

    La fonctionnalité est en test dans deux départements et sera bientôt disponible pour votre territoire.

    +
    ''' # --- +# name: test_my_groups + dict({ + 'num_queries': 14, + 'queries': list([ + dict({ + 'origin': list([ + 'SessionStore._get_session_from_db[/django/contrib/sessions/backends/db.py]', + ]), + 'sql': ''' + SELECT "django_session"."session_key", + "django_session"."session_data", + "django_session"."expire_date" + FROM "django_session" + WHERE ("django_session"."expire_date" > %s + AND "django_session"."session_key" = %s) + LIMIT 21 + ''', + }), + dict({ + 'origin': list([ + 'ItouCurrentOrganizationMiddleware.__call__[utils/perms/middleware.py]', + ]), + 'sql': ''' + SELECT "users_user"."id", + "users_user"."password", + "users_user"."last_login", + "users_user"."is_superuser", + "users_user"."username", + "users_user"."first_name", + "users_user"."last_name", + "users_user"."is_staff", + "users_user"."is_active", + "users_user"."date_joined", + "users_user"."address_line_1", + "users_user"."address_line_2", + "users_user"."post_code", + "users_user"."city", + "users_user"."department", + "users_user"."coords", + "users_user"."geocoding_score", + "users_user"."geocoding_updated_at", + "users_user"."ban_api_resolved_address", + "users_user"."insee_city_id", + "users_user"."title", + "users_user"."email", + "users_user"."phone", + "users_user"."kind", + "users_user"."identity_provider", + "users_user"."has_completed_welcoming_tour", + "users_user"."created_by_id", + "users_user"."external_data_source_history", + "users_user"."last_checked_at", + "users_user"."public_id", + "users_user"."address_filled_at", + "users_user"."first_login" + FROM "users_user" + WHERE "users_user"."id" = %s + LIMIT 21 + ''', + }), + dict({ + 'origin': list([ + 'ItouCurrentOrganizationMiddleware.__call__[utils/perms/middleware.py]', + ]), + 'sql': ''' + SELECT "prescribers_prescribermembership"."id", + "prescribers_prescribermembership"."user_id", + "prescribers_prescribermembership"."joined_at", + "prescribers_prescribermembership"."is_admin", + "prescribers_prescribermembership"."is_active", + "prescribers_prescribermembership"."created_at", + "prescribers_prescribermembership"."updated_at", + "prescribers_prescribermembership"."organization_id", + "prescribers_prescribermembership"."updated_by_id", + "prescribers_prescriberorganization"."id", + "prescribers_prescriberorganization"."address_line_1", + "prescribers_prescriberorganization"."address_line_2", + "prescribers_prescriberorganization"."post_code", + "prescribers_prescriberorganization"."city", + "prescribers_prescriberorganization"."department", + "prescribers_prescriberorganization"."coords", + "prescribers_prescriberorganization"."geocoding_score", + "prescribers_prescriberorganization"."geocoding_updated_at", + "prescribers_prescriberorganization"."ban_api_resolved_address", + "prescribers_prescriberorganization"."insee_city_id", + "prescribers_prescriberorganization"."name", + "prescribers_prescriberorganization"."created_at", + "prescribers_prescriberorganization"."updated_at", + "prescribers_prescriberorganization"."uid", + "prescribers_prescriberorganization"."active_members_email_reminder_last_sent_at", + "prescribers_prescriberorganization"."automatic_geocoding_update", + "prescribers_prescriberorganization"."siret", + "prescribers_prescriberorganization"."is_head_office", + "prescribers_prescriberorganization"."kind", + "prescribers_prescriberorganization"."is_brsa", + "prescribers_prescriberorganization"."phone", + "prescribers_prescriberorganization"."email", + "prescribers_prescriberorganization"."website", + "prescribers_prescriberorganization"."description", + "prescribers_prescriberorganization"."is_authorized", + "prescribers_prescriberorganization"."code_safir_pole_emploi", + "prescribers_prescriberorganization"."created_by_id", + "prescribers_prescriberorganization"."authorization_status", + "prescribers_prescriberorganization"."authorization_updated_at", + "prescribers_prescriberorganization"."authorization_updated_by_id" + FROM "prescribers_prescribermembership" + INNER JOIN "prescribers_prescriberorganization" ON ("prescribers_prescribermembership"."organization_id" = "prescribers_prescriberorganization"."id") + WHERE ("prescribers_prescribermembership"."user_id" = %s + AND "prescribers_prescribermembership"."is_active") + ORDER BY "prescribers_prescribermembership"."created_at" ASC + ''', + }), + dict({ + 'origin': list([ + 'Atomic.__enter__[/django/db/transaction.py]', + ]), + 'sql': 'SAVEPOINT ""', + }), + dict({ + 'origin': list([ + 'MembershipsFiltersForm._get_beneficiary_choices[www/gps/forms.py]', + 'MembershipsFiltersForm.__init__[www/gps/forms.py]', + 'my_groups[www/gps/views.py]', + '_check_user_view_wrapper[utils/auth.py]', + ]), + 'sql': ''' + SELECT "users_user"."id", + "users_user"."password", + "users_user"."last_login", + "users_user"."is_superuser", + "users_user"."username", + "users_user"."first_name", + "users_user"."last_name", + "users_user"."is_staff", + "users_user"."is_active", + "users_user"."date_joined", + "users_user"."address_line_1", + "users_user"."address_line_2", + "users_user"."post_code", + "users_user"."city", + "users_user"."department", + "users_user"."coords", + "users_user"."geocoding_score", + "users_user"."geocoding_updated_at", + "users_user"."ban_api_resolved_address", + "users_user"."insee_city_id", + "users_user"."title", + "users_user"."email", + "users_user"."phone", + "users_user"."kind", + "users_user"."identity_provider", + "users_user"."has_completed_welcoming_tour", + "users_user"."created_by_id", + "users_user"."external_data_source_history", + "users_user"."last_checked_at", + "users_user"."public_id", + "users_user"."address_filled_at", + "users_user"."first_login" + FROM "users_user" + WHERE "users_user"."id" IN + (SELECT U2."beneficiary_id" + FROM "gps_followupgroupmembership" U0 + INNER JOIN "gps_followupgroup" U2 ON (U0."follow_up_group_id" = U2."id") + LEFT OUTER JOIN "gps_followupgroupmembership" U3 ON (U2."id" = U3."follow_up_group_id") + WHERE (U0."member_id" = %s + AND U0."is_active") + GROUP BY U0."id", + U2."beneficiary_id") + ORDER BY RANDOM() ASC + ''', + }), + dict({ + 'origin': list([ + 'ItouPaginator.count[/django/core/paginator.py]', + 'pager[utils/pagination.py]', + 'my_groups[www/gps/views.py]', + '_check_user_view_wrapper[utils/auth.py]', + ]), + 'sql': ''' + SELECT COUNT(*) + FROM + (SELECT "gps_followupgroupmembership"."id" AS "col1" + FROM "gps_followupgroupmembership" + INNER JOIN "gps_followupgroup" ON ("gps_followupgroupmembership"."follow_up_group_id" = "gps_followupgroup"."id") + LEFT OUTER JOIN "gps_followupgroupmembership" T4 ON ("gps_followupgroup"."id" = T4."follow_up_group_id") + WHERE ("gps_followupgroupmembership"."member_id" = %s + AND "gps_followupgroupmembership"."is_active") + GROUP BY 1) subquery + ''', + }), + dict({ + 'origin': list([ + 'User.is_prescriber_with_authorized_org[users/models.py]', + 'is_allowed_to_use_gps_advanced_features[www/gps/views.py]', + 'my_groups[www/gps/views.py]', + '_check_user_view_wrapper[utils/auth.py]', + ]), + 'sql': ''' + SELECT %s AS "a" + FROM "prescribers_prescribermembership" + INNER JOIN "users_user" ON ("prescribers_prescribermembership"."user_id" = "users_user"."id") + INNER JOIN "prescribers_prescriberorganization" ON ("prescribers_prescribermembership"."organization_id" = "prescribers_prescriberorganization"."id") + WHERE ("prescribers_prescribermembership"."user_id" = %s + AND "prescribers_prescribermembership"."is_active" + AND "prescribers_prescriberorganization"."is_authorized" + AND "users_user"."is_active") + LIMIT 1 + ''', + }), + dict({ + 'origin': list([ + 'IfNode[gps/includes/memberships_results.html]', + 'IncludeNode[gps/my_groups.html]', + 'BlockNode[layout/base.html]', + 'ExtendsNode[gps/my_groups.html]', + 'my_groups[www/gps/views.py]', + '_check_user_view_wrapper[utils/auth.py]', + ]), + 'sql': ''' + SELECT "gps_followupgroupmembership"."id", + "gps_followupgroupmembership"."is_referent", + "gps_followupgroupmembership"."is_active", + "gps_followupgroupmembership"."created_at", + "gps_followupgroupmembership"."created_in_bulk", + "gps_followupgroupmembership"."ended_at", + "gps_followupgroupmembership"."updated_at", + "gps_followupgroupmembership"."follow_up_group_id", + "gps_followupgroupmembership"."member_id", + "gps_followupgroupmembership"."creator_id", + COUNT(T4."member_id") AS "nb_members", + "gps_followupgroup"."id", + "gps_followupgroup"."created_at", + "gps_followupgroup"."created_in_bulk", + "gps_followupgroup"."updated_at", + "gps_followupgroup"."beneficiary_id", + T6."id", + T6."password", + T6."last_login", + T6."is_superuser", + T6."username", + T6."first_name", + T6."last_name", + T6."is_staff", + T6."is_active", + T6."date_joined", + T6."address_line_1", + T6."address_line_2", + T6."post_code", + T6."city", + T6."department", + T6."coords", + T6."geocoding_score", + T6."geocoding_updated_at", + T6."ban_api_resolved_address", + T6."insee_city_id", + T6."title", + T6."email", + T6."phone", + T6."kind", + T6."identity_provider", + T6."has_completed_welcoming_tour", + T6."created_by_id", + T6."external_data_source_history", + T6."last_checked_at", + T6."public_id", + T6."address_filled_at", + T6."first_login", + "users_user"."id", + "users_user"."password", + "users_user"."last_login", + "users_user"."is_superuser", + "users_user"."username", + "users_user"."first_name", + "users_user"."last_name", + "users_user"."is_staff", + "users_user"."is_active", + "users_user"."date_joined", + "users_user"."address_line_1", + "users_user"."address_line_2", + "users_user"."post_code", + "users_user"."city", + "users_user"."department", + "users_user"."coords", + "users_user"."geocoding_score", + "users_user"."geocoding_updated_at", + "users_user"."ban_api_resolved_address", + "users_user"."insee_city_id", + "users_user"."title", + "users_user"."email", + "users_user"."phone", + "users_user"."kind", + "users_user"."identity_provider", + "users_user"."has_completed_welcoming_tour", + "users_user"."created_by_id", + "users_user"."external_data_source_history", + "users_user"."last_checked_at", + "users_user"."public_id", + "users_user"."address_filled_at", + "users_user"."first_login" + FROM "gps_followupgroupmembership" + INNER JOIN "users_user" ON ("gps_followupgroupmembership"."member_id" = "users_user"."id") + INNER JOIN "gps_followupgroup" ON ("gps_followupgroupmembership"."follow_up_group_id" = "gps_followupgroup"."id") + LEFT OUTER JOIN "gps_followupgroupmembership" T4 ON ("gps_followupgroup"."id" = T4."follow_up_group_id") + INNER JOIN "users_user" T6 ON ("gps_followupgroup"."beneficiary_id" = T6."id") + WHERE ("gps_followupgroupmembership"."member_id" = %s + AND "gps_followupgroupmembership"."is_active") + GROUP BY "gps_followupgroupmembership"."id", + "gps_followupgroup"."id", + T6."id", + "users_user"."id" + ORDER BY "gps_followupgroupmembership"."created_at" DESC + LIMIT 1 + ''', + }), + dict({ + 'origin': list([ + 'IfNode[gps/includes/memberships_results.html]', + 'IncludeNode[gps/my_groups.html]', + 'BlockNode[layout/base.html]', + 'ExtendsNode[gps/my_groups.html]', + 'my_groups[www/gps/views.py]', + '_check_user_view_wrapper[utils/auth.py]', + ]), + 'sql': ''' + SELECT "col1", + "col2", + "col3", + "col4", + "col5", + "col6", + "col7", + "col8", + "col9", + "col10" + FROM + (SELECT * + FROM + (SELECT "gps_followupgroupmembership"."id" AS "col1", + "gps_followupgroupmembership"."is_referent" AS "col2", + "gps_followupgroupmembership"."is_active" AS "col3", + "gps_followupgroupmembership"."created_at" AS "col4", + "gps_followupgroupmembership"."created_in_bulk" AS "col5", + "gps_followupgroupmembership"."ended_at" AS "col6", + "gps_followupgroupmembership"."updated_at" AS "col7", + "gps_followupgroupmembership"."follow_up_group_id" AS "col8", + "gps_followupgroupmembership"."member_id" AS "col9", + "gps_followupgroupmembership"."creator_id" AS "col10", + ROW_NUMBER() OVER (PARTITION BY "gps_followupgroupmembership"."follow_up_group_id" + ORDER BY RANDOM() ASC) AS "qual0", + RANDOM() AS "qual1" + FROM "gps_followupgroupmembership" + WHERE ("gps_followupgroupmembership"."is_referent" + AND "gps_followupgroupmembership"."follow_up_group_id" IN (%s)) + ORDER BY RANDOM() ASC) "qualify" + WHERE ("qual0" > %s + AND "qual0" <= %s)) "qualify_mask" + ORDER BY "qual1" ASC + ''', + }), + dict({ + 'origin': list([ + 'VariableNode[gps/includes/memberships_results.html]', + 'IfNode[gps/includes/memberships_results.html]', + 'ForNode[gps/includes/memberships_results.html]', + 'IfNode[gps/includes/memberships_results.html]', + 'IncludeNode[gps/my_groups.html]', + 'BlockNode[layout/base.html]', + 'ExtendsNode[gps/my_groups.html]', + 'my_groups[www/gps/views.py]', + '_check_user_view_wrapper[utils/auth.py]', + ]), + 'sql': ''' + SELECT "users_user"."id", + "users_user"."password", + "users_user"."last_login", + "users_user"."is_superuser", + "users_user"."username", + "users_user"."first_name", + "users_user"."last_name", + "users_user"."is_staff", + "users_user"."is_active", + "users_user"."date_joined", + "users_user"."address_line_1", + "users_user"."address_line_2", + "users_user"."post_code", + "users_user"."city", + "users_user"."department", + "users_user"."coords", + "users_user"."geocoding_score", + "users_user"."geocoding_updated_at", + "users_user"."ban_api_resolved_address", + "users_user"."insee_city_id", + "users_user"."title", + "users_user"."email", + "users_user"."phone", + "users_user"."kind", + "users_user"."identity_provider", + "users_user"."has_completed_welcoming_tour", + "users_user"."created_by_id", + "users_user"."external_data_source_history", + "users_user"."last_checked_at", + "users_user"."public_id", + "users_user"."address_filled_at", + "users_user"."first_login" + FROM "users_user" + WHERE "users_user"."id" = %s + LIMIT 21 + ''', + }), + dict({ + 'origin': list([ + 'Atomic.__exit__[/django/db/transaction.py]', + ]), + 'sql': 'RELEASE SAVEPOINT ""', + }), + dict({ + 'origin': list([ + 'Atomic.__enter__[/django/db/transaction.py]', + 'SessionStore.save[/django/contrib/sessions/backends/db.py]', + ]), + 'sql': 'SAVEPOINT ""', + }), + dict({ + 'origin': list([ + 'Session.save[/django/db/models/base.py]', + 'SessionStore.save[/django/contrib/sessions/backends/db.py]', + ]), + 'sql': ''' + UPDATE "django_session" + SET "session_data" = %s, + "expire_date" = %s + WHERE "django_session"."session_key" = %s + ''', + }), + dict({ + 'origin': list([ + 'Atomic.__exit__[/django/db/transaction.py]', + 'SessionStore.save[/django/contrib/sessions/backends/db.py]', + ]), + 'sql': 'RELEASE SAVEPOINT ""', + }), + ]), + }) +# --- # name: test_my_groups[test_my_groups__group_card] '''
    @@ -186,8 +684,8 @@ Prénom Nom Suivi depuis - Référent⸱e - Nbr d'intervenants + Référent⸱e + Nbr d’intervenants diff --git a/tests/gps/test_views.py b/tests/gps/test_views.py index ad0a3ce75d..16362f9017 100644 --- a/tests/gps/test_views.py +++ b/tests/gps/test_views.py @@ -15,7 +15,7 @@ PrescriberFactory, ) from tests.utils.htmx.test import assertSoupEqual, update_page_with_htmx -from tests.utils.test import parse_response_to_soup +from tests.utils.test import assertSnapshotQueries, parse_response_to_soup def test_job_seeker_cannot_use_gps(client): @@ -221,7 +221,8 @@ def test_my_groups(snapshot, client): ) FollowUpGroup.objects.follow_beneficiary(beneficiary=group.beneficiary, user=user) - response = client.get(reverse("gps:my_groups")) + with assertSnapshotQueries(snapshot): + response = client.get(reverse("gps:my_groups")) groups = parse_response_to_soup( response, selector="#follow-up-groups-section",