Skip to content

Commit cd6f1e5

Browse files
authored
Merge pull request #1531 from betagouv/feat/fix-search-sort
Ajout filtre géographique dans le carnet d'adresse et réparer recherches d'orga et de contacts
2 parents a979d64 + 6f8eeb6 commit cd6f1e5

18 files changed

Lines changed: 626 additions & 187 deletions

File tree

recoco/apps/addressbook/rest.py

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44
from rest_framework.viewsets import ModelViewSet
55

66
from recoco.rest_api.filters import (
7-
VectorSearchFilter,
8-
WordTrigramSimilaritySearchFilter,
7+
StrictWordTrigramSimilaritySearchFilter,
98
)
109
from recoco.rest_api.pagination import StandardResultsSetPagination
1110
from recoco.rest_api.permissions import (
12-
IsStaffForSiteOrISAuthenticatedReadOnly,
11+
IsStaffForSiteOrIsAuthenticatedReadOnly,
1312
IsStaffForSiteOrReadOnly,
1413
)
1514

@@ -22,15 +21,21 @@ class OrganizationGroupViewSet(ModelViewSet):
2221
queryset = OrganizationGroup.objects.all()
2322
permission_classes = [IsStaffForSiteOrReadOnly]
2423
pagination_class = StandardResultsSetPagination
25-
filter_backends = [VectorSearchFilter]
26-
search_fields = ["name"]
27-
search_min_rank = 0.05
24+
25+
filter_backends = [StrictWordTrigramSimilaritySearchFilter]
26+
27+
trgm_search_fields = ["name"]
28+
trgm_search_min_rank = 0.3
2829

2930

3031
class OrganizationViewSet(ModelViewSet):
3132
permission_classes = [IsStaffForSiteOrReadOnly]
3233
pagination_class = StandardResultsSetPagination
33-
filter_backends = [VectorSearchFilter]
34+
filter_backends = [StrictWordTrigramSimilaritySearchFilter]
35+
36+
trgm_search_fields = [("name", 1.5), "group__name"]
37+
trgm_search_min_rank = 0.3
38+
3439
search_fields = ["name"]
3540
search_min_rank = 0.05
3641

@@ -43,6 +48,12 @@ def get_serializer_class(self):
4348
return serializers.OrganizationListSerializer
4449
case "retrieve":
4550
return serializers.OrganizationDetailSerializer
51+
case "partial_update":
52+
return serializers.OrganizationWritableSerializer
53+
case "update":
54+
return serializers.OrganizationWritableSerializer
55+
case "create":
56+
return serializers.OrganizationWritableSerializer
4657
case _:
4758
return serializers.OrganizationSerializer
4859

@@ -67,19 +78,31 @@ def filter_queryset(self, request, queryset, view):
6778
)
6879

6980

81+
class ByDepartmentFilterBackend(BaseFilterBackend):
82+
def filter_queryset(self, request, queryset, view):
83+
departments = request.query_params.getlist("departments")
84+
if not departments:
85+
return queryset
86+
return queryset.filter(organization__departments__in=departments)
87+
88+
7089
class ContactViewSet(ModelViewSet):
71-
permission_classes = [IsStaffForSiteOrISAuthenticatedReadOnly]
90+
filter_backends = [
91+
OrgaStartswithFilterBackend,
92+
ByDepartmentFilterBackend,
93+
StrictWordTrigramSimilaritySearchFilter,
94+
]
95+
permission_classes = [IsStaffForSiteOrIsAuthenticatedReadOnly]
7296
pagination_class = StandardResultsSetPagination
73-
filter_backends = [WordTrigramSimilaritySearchFilter, OrgaStartswithFilterBackend]
7497

7598
trgm_search_fields = [
76-
("last_name", 1.5),
99+
("last_name", 1.25),
77100
"first_name",
78-
("division", 1.5),
79-
("organization__name", 2.0),
101+
("division", 1.25),
102+
("organization__name", 1.5),
80103
"organization__group__name",
81104
]
82-
trgm_search_min_rank = 0.3
105+
trgm_search_min_rank = 0.37
83106

84107
def get_queryset(self):
85108
return Contact.on_site.select_related("organization__group")

recoco/apps/addressbook/serializers.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
created: 2022-05-16 17:44:55 CET
88
"""
99

10+
from rest_framework import serializers
1011
from rest_framework.exceptions import ValidationError
1112
from rest_framework.serializers import (
1213
FloatField,
@@ -18,6 +19,7 @@
1819
from recoco.apps.geomatics.serializers import DepartmentSerializer
1920
from recoco.rest_api.serializers import BaseSerializerMixin
2021

22+
from ..geomatics.models import Department
2123
from .models import Contact, Organization, OrganizationGroup
2224

2325
# OrganizationGroup serializers
@@ -84,6 +86,24 @@ class Meta:
8486
]
8587

8688

89+
class OrganizationWritableSerializer(OrganizationListSerializer):
90+
group_id = serializers.PrimaryKeyRelatedField(
91+
source="group", queryset=OrganizationGroup.objects, allow_null=True
92+
)
93+
departments = serializers.PrimaryKeyRelatedField(
94+
queryset=Department.objects, many=True
95+
)
96+
97+
class Meta:
98+
model = Organization
99+
fields = [
100+
"id",
101+
"name",
102+
"group_id",
103+
"departments",
104+
]
105+
106+
87107
class NestedOrganizationSerializer(OrganizationListSerializer):
88108
group = OrganizationGroupSerializer()
89109

recoco/apps/addressbook/templates/addressbook/contact_list.html

Lines changed: 71 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,22 @@
1313
<link href="{% sass_src 'addressbook/css/contact_list.scss' %}"
1414
rel="stylesheet"
1515
type="text/css">
16+
<link href="{% sass_src 'projects/css/segmented_ctrl.scss' %}"
17+
rel="stylesheet"
18+
type="text/css">
1619
{% endblock css %}
1720
{% block js %}
1821
{% vite_asset 'js/components/ContactBook.js' %}
1922
{% vite_asset 'js/apps/featureAddContact.js' %}
2023
{% endblock js %}
2124
{% block content %}
22-
<div x-data="ContactBook"
25+
{{ departments|json_script:"departmentsArray" }}
26+
{{ regions|json_script:"regionsArray" }}
27+
<div x-data="ContactBook(departmentsArray, regionsArray)"
2328
x-on:beforeunload.window="resetLetterFilter(false)"
2429
@modal-response="closeCreatesModal($event)"
30+
@selected-departments="saveSelectedDepartment($event)"
31+
@is-select-all-departments="isSelectAllDepartments = $event.detail"
2532
class="contact-list col-12 fr-px-15w fr-mx-auto">
2633
<!-- Breadcrumb -->
2734
<nav role="navigation" class="fr-breadcrumb" aria-label="vous êtes ici :">
@@ -50,6 +57,9 @@ <h2>Contact</h2>
5057
x-model="searchParams.search"
5158
placeholder="Rechercher">
5259
</div>
60+
<div class="header__region-filter">
61+
{% include "projects/project/fragments/departments_selector.html" with label="Sélection par département" filter_by_regions=True zone_objects="regions" select_all=True %}
62+
</div>
5363
<button class="actions__add-contact no-wrap fr-btn fr-btn--icon-left fr-icon-add-line"
5464
@click="openModalCreateContact()"
5565
data-test-id="button-create-contact">Créer un contact</button>
@@ -70,53 +80,73 @@ <h2>Contact</h2>
7080
</div>
7181
</div>
7282
<div class="contact-list__list">
73-
<template x-for="nationalGroup in contactListGroupByNationalGroup"
74-
:key="nationalGroup.id">
75-
<div class="national-group-container w-100 fr-my-5v">
76-
<h3 class="national-group__name fr-mb-0 no-wrap"
77-
x-text="nationalGroup.name"></h3>
78-
<template x-for="organization in nationalGroup.organizations" :key="index">
79-
<div class="organization-container">
80-
<div class="d-flex align-items-center" data-test-id="organization-header">
81-
<h3 class="organization__name fr-mb-0 no-wrap" x-text="organization.name"></h3>
82-
<button class="fr-btn fr-btn--tertiary-no-outline fr-btn--sm fr-icon-pencil-line button__actions-edit"
83-
@click="openModalCreateOrganization(organization, nationalGroup)"
84-
data-test-id="button-edit-organization">
85-
Éditer l'organisation
86-
</button>
87-
<template x-if="organization.departments?.length > 0">
88-
<div class="organization__departments"
89-
:title="organization.departments.map(department => `${department.name} (${department.code})`).join(', ')">
90-
<template x-for="department in organization.departments">
91-
<template x-if="department.name">
92-
<span class="organization__departments-item"
93-
x-text="`${department.name} (${department.code})`"></span>
94-
</template>
95-
</template>
96-
</div>
97-
</template>
98-
</div>
99-
<div class="contact-list__list-container">
100-
<template x-for="contact in organization.contacts" :key="contact.id">
101-
<div x-data="{contact:contact, isOpenDeleteContact: false}"
102-
class="contact-list__list-item">
103-
{% include "tools/contacts/contact_card.html" with button_actions=True %}
104-
</div>
105-
</template>
106-
</div>
83+
<template x-if="searchParams.search!==''">
84+
<div class="contact-list__list-container w-100 fr-mt-3w">
85+
<template x-for="contact in contactSearched" :key="contact.id">
86+
<div x-data="{contact:contact, isOpenDeleteContact: false}"
87+
class="contact-list__list-item">
88+
{% include "tools/contacts/contact_card.html" with button_actions=True %}
10789
</div>
10890
</template>
10991
</div>
11092
</template>
93+
<template x-if="searchParams.search===''">
94+
<template x-for="nationalGroup in contactListGroupByNationalGroup"
95+
:key="nationalGroup.id">
96+
<div class="national-group-container w-100 fr-my-5v">
97+
<h3 class="national-group__name fr-mb-0 no-wrap"
98+
x-text="nationalGroup.name"></h3>
99+
<template x-for="organization in nationalGroup.organizations" :key="index">
100+
<div class="organization-container">
101+
<div class="d-flex align-items-center" data-test-id="organization-header">
102+
<h3 class="organization__name fr-mb-0 no-wrap" x-text="organization.name"></h3>
103+
<button class="fr-btn fr-btn--tertiary-no-outline fr-btn--sm fr-icon-pencil-line button__actions-edit"
104+
@click="openModalCreateOrganization(organization, nationalGroup)"
105+
data-test-id="button-edit-organization">Éditer l'organisation</button>
106+
<template x-if="organization.departments?.length > 0">
107+
<div class="organization__departments"
108+
:title="organization.departments.map(department => `${department.name} (${department.code})`).join(', ')">
109+
<template x-for="department in organization.departments">
110+
<template x-if="department.name">
111+
<span class="organization__departments-item"
112+
x-text="`${department.name} (${department.code})`"></span>
113+
</template>
114+
</template>
115+
</div>
116+
</template>
117+
</div>
118+
<div class="contact-list__list-container">
119+
<template x-for="contact in organization.contacts" :key="contact.id">
120+
<div x-data="{contact:contact, isOpenDeleteContact: false}"
121+
class="contact-list__list-item">
122+
{% include "tools/contacts/contact_card.html" with button_actions=True %}
123+
</div>
124+
</template>
125+
</div>
126+
</div>
127+
</template>
128+
</div>
129+
</template>
130+
</template>
111131
<template x-if="contactListGroupByNationalGroup.length === 0">
112-
<div class="no-contacts fr-m-2w">
113-
<p class="no-contacts__text">Aucun contact trouvé.</p>
114-
<br>
115-
<p class="no-contacts__text fr-m-0">Vous pouvez créer un contact en cliquant sur le bouton ci-dessus.</p>
116-
</div>
132+
<template x-if="isContactDataLoaded">
133+
<div class="no-contacts fr-m-2w">
134+
<p class="no-contacts__text">Aucun contact trouvé.</p>
135+
<br>
136+
<p class="no-contacts__text fr-m-0">Vous pouvez créer un contact en cliquant sur le bouton ci-dessus.</p>
137+
</div>
138+
</template>
117139
</template>
140+
<div x-show="!isContactDataLoaded"
141+
x-transition
142+
class="position-absolute text-center w-100"
143+
data-cy="loader">
144+
<div class="spinner-border" role="status">
145+
<span class="visually-hidden">Loading...</span>
146+
</div>
147+
</div>
118148
</div>
119-
{% include "tools/contacts/create_contact_modal.html" with is_it_returning_data=False %}
149+
{% include "tools/contacts/create_contact_modal.html" with is_it_returning_data=True %}
120150
{% include "tools/contacts/create_organization_modal.html" with is_it_returning_data=False %}
121151
</div>
122152
{% endblock content %}

0 commit comments

Comments
 (0)