Skip to content

Commit 7aa700f

Browse files
authored
Merge pull request #316 from betagouv/pole-creation-responses
Fix pour permettre des répondant·e·s d'un pôle d'accéder aux enquêtes de l'organisation
2 parents 115dd7b + d748d51 commit 7aa700f

9 files changed

Lines changed: 62 additions & 30 deletions

File tree

backend/responses/permissions.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,9 @@ def has_permission(self, request, view):
2424
if qs.filter(pole__isnull=True).exists():
2525
return True
2626

27-
# Un·e RESPONDER au niveau d'un pôle ne peut répondre qu'aux enquêtes de ce pôle
28-
return survey.pole is not None and qs.filter(pole=survey.pole).exists()
27+
# Un·e RESPONDER au niveau d'un pôle peut répondre :
28+
# - aux enquêtes de son pôle spécifique
29+
# - aux enquêtes au niveau organisation (sans pôle)
30+
if survey.pole is None:
31+
return qs.filter(pole__isnull=False).exists()
32+
return qs.filter(pole=survey.pole).exists()

backend/responses/tests/test_create_response.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,13 @@ def test_pole_responder_can_create_response_for_their_pole_survey(self):
159159
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
160160

161161
@authenticate
162-
def test_pole_responder_cannot_create_response_for_org_level_survey(self):
162+
def test_pole_responder_can_create_response_for_org_level_survey(self):
163163
"""
164-
Un·e RESPONDER de pôle ne peut pas répondre à une enquête au niveau organisation (sans pôle)
164+
Un·e RESPONDER de pôle peut répondre à une enquête au niveau organisation (sans pôle)
165165
"""
166166
org = OrganisationFactory()
167167
pole = PoleFactory(organisation=org)
168-
survey = SurveyFactory(organisation=org)
168+
survey = SurveyFactory(organisation=org, pole=None)
169169
MembershipFactory(
170170
user=authenticate.user, organisation=org, pole=pole, membership_type=MembershipType.RESPONDER
171171
)
@@ -174,7 +174,7 @@ def test_pole_responder_cannot_create_response_for_org_level_survey(self):
174174
response_payload(survey),
175175
format="json",
176176
)
177-
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
177+
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
178178

179179
@authenticate
180180
def test_pole_responder_cannot_create_response_for_other_pole_survey(self):

backend/surveys/tests/test_list_survey.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,24 +60,25 @@ def test_org_member_sees_pole_surveys_within_org(self):
6060
self.assertEqual(json_response[0]["id"], survey.id)
6161

6262
@authenticate
63-
def test_pole_member_sees_only_their_pole_surveys(self):
63+
def test_pole_member_sees_pole_and_org_level_surveys(self):
6464
"""
65-
Un membre d'un pôle voit uniquement les enquêtes de ce pôle, pas les enquêtes au niveau organisation
65+
Un RESPONDER rattaché à un pôle voit les enquêtes de ce pôle
66+
ET les enquêtes au niveau organisation (sans pôle)
6667
"""
6768
org = OrganisationFactory()
6869
pole = PoleFactory(organisation=org)
6970
pole_survey = SurveyFactory(organisation=org, pole=pole)
70-
SurveyFactory(organisation=org, pole=None)
71+
org_survey = SurveyFactory(organisation=org, pole=None)
7172
MembershipFactory(
7273
user=authenticate.user, organisation=org, pole=pole, membership_type=MembershipType.RESPONDER
7374
)
7475

7576
response = self.client.get(reverse("survey_list_create"), format="json")
7677
self.assertEqual(response.status_code, status.HTTP_200_OK)
7778

78-
json_response = response.json()
79-
self.assertEqual(len(json_response), 1)
80-
self.assertEqual(json_response[0]["id"], pole_survey.id)
79+
ids = [s["id"] for s in response.json()]
80+
self.assertIn(pole_survey.id, ids)
81+
self.assertIn(org_survey.id, ids)
8182

8283
@authenticate
8384
def test_pole_member_cannot_see_other_pole_surveys(self):

backend/surveys/tests/test_retrieve_survey.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@ def test_pole_responder_can_retrieve_their_pole_survey(self):
101101
self.assertEqual(response.json()["id"], survey.id)
102102

103103
@authenticate
104-
def test_pole_responder_cannot_retrieve_org_level_survey(self):
104+
def test_pole_responder_can_retrieve_org_level_survey(self):
105105
"""
106-
Un·e RESPONDER de pôle ne peut pas accéder à une enquête au niveau organisation (sans pôle)
106+
Un·e RESPONDER de pôle peut accéder à une enquête au niveau organisation (sans pôle)
107107
"""
108108
org = OrganisationFactory()
109109
pole = PoleFactory(organisation=org)
@@ -114,7 +114,8 @@ def test_pole_responder_cannot_retrieve_org_level_survey(self):
114114

115115
response = self.client.get(survey_url(survey.id), format="json")
116116

117-
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
117+
self.assertEqual(response.status_code, status.HTTP_200_OK)
118+
self.assertEqual(response.json()["id"], survey.id)
118119

119120
@authenticate
120121
def test_pole_responder_cannot_retrieve_survey_from_other_pole(self):
@@ -362,9 +363,9 @@ def test_pole_responder_sees_their_pole_surveys(self):
362363
self.assertIn(survey.id, ids)
363364

364365
@authenticate
365-
def test_pole_responder_cannot_see_org_level_surveys(self):
366+
def test_pole_responder_sees_org_level_surveys(self):
366367
"""
367-
Un·e RESPONDER de pôle ne voit pas les enquêtes au niveau organisation (sans pôle)
368+
Un·e RESPONDER de pôle voit aussi les enquêtes au niveau organisation (sans pôle)
368369
"""
369370
org = OrganisationFactory()
370371
pole = PoleFactory(organisation=org)
@@ -377,7 +378,7 @@ def test_pole_responder_cannot_see_org_level_surveys(self):
377378

378379
self.assertEqual(response.status_code, status.HTTP_200_OK)
379380
ids = [s["id"] for s in response.json()]
380-
self.assertNotIn(org_survey.id, ids)
381+
self.assertIn(org_survey.id, ids)
381382

382383
@authenticate
383384
def test_pole_responder_cannot_see_other_pole_surveys(self):

backend/surveys/views/survey.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,16 @@ def get_queryset(self):
1515

1616
org_ids = Membership.objects.filter(user=user, pole__isnull=True).values_list("organisation_id", flat=True)
1717
pole_ids = Membership.objects.filter(user=user, pole__isnull=False).values_list("pole_id", flat=True)
18+
# Les RESPONDER rattachés à un pôle voient aussi les enquêtes au niveau organisation
19+
responder_pole_org_ids = Membership.objects.filter(
20+
user=user, pole__isnull=False, membership_type=MembershipType.RESPONDER
21+
).values_list("organisation_id", flat=True)
1822

19-
return Survey.objects.filter(Q(organisation_id__in=org_ids) | Q(pole_id__in=pole_ids))
23+
return Survey.objects.filter(
24+
Q(organisation_id__in=org_ids)
25+
| Q(pole_id__in=pole_ids)
26+
| Q(organisation_id__in=responder_pole_org_ids, pole__isnull=True)
27+
).distinct()
2028

2129

2230
class SurveyListCreateAPIView(SurveyQuerySetMixin, ListCreateAPIView):
@@ -50,11 +58,20 @@ def get_queryset(self):
5058
user=user, membership_type=MembershipType.RESPONDER, pole__isnull=True
5159
).values_list("organisation_id", flat=True)
5260

53-
pole_ids = Membership.objects.filter(
54-
user=user, membership_type=MembershipType.RESPONDER, pole__isnull=False
55-
).values_list("pole_id", flat=True)
61+
pole_memberships = list(
62+
Membership.objects.filter(user=user, membership_type=MembershipType.RESPONDER, pole__isnull=False).values(
63+
"pole_id", "organisation_id"
64+
)
65+
)
66+
67+
pole_ids = [m["pole_id"] for m in pole_memberships]
68+
pole_org_ids = [m["organisation_id"] for m in pole_memberships]
5669

57-
return Survey.objects.filter(Q(organisation_id__in=org_ids) | Q(pole_id__in=pole_ids))
70+
return Survey.objects.filter(
71+
Q(organisation_id__in=org_ids)
72+
| Q(pole_id__in=pole_ids)
73+
| Q(organisation_id__in=pole_org_ids, pole__isnull=True)
74+
).distinct()
5875

5976

6077
class SurveyRetrieveAPIView(SurveyQuerySetMixin, RetrieveAPIView):

mobile/android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ android {
77
applicationId "fr.gouv.beta.sylvasan"
88
minSdkVersion rootProject.ext.minSdkVersion
99
targetSdkVersion rootProject.ext.targetSdkVersion
10-
versionCode 12
11-
versionName "0.0.12"
10+
versionCode 13
11+
versionName "0.0.13"
1212
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
1313
aaptOptions {
1414
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.

mobile/src/composables/useMapPins.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ function getSurveyTitle(response: LocalResponse | ResponseFull): string {
3636

3737
export function useMapPins(mapRef: ShallowRef<maplibregl.Map | null>) {
3838
const { allResponses } = storeToRefs(useResponsesStore())
39-
const { getSurveyById } = useSurveysStore()
39+
const { getSurveyById } = storeToRefs(useSurveysStore())
4040

4141
const selectedPin = ref<PinData | null>(null)
4242

4343
const pins = computed<PinData[]>(() =>
4444
allResponses.value.flatMap((response) => {
45-
const schema = getSchema(response, getSurveyById)
45+
const schema = getSchema(response, getSurveyById.value)
4646
if (!schema) return []
4747

4848
const mapField = schema.fields.find((f) => f.ui?.widget === "map")

mobile/src/pages/PositionPage.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ onBeforeUnmount(() => {
127127
<div v-else class="relative h-full">
128128
<div
129129
ref="mapContainer"
130-
:class="{ 'w-full': true, 'h-full': true, hidden: !tilesLoaded }"
130+
:class="{ 'w-full': true, 'h-full': true, invisible: !tilesLoaded }"
131131
/>
132132

133133
<Transition name="fade">

mobile/src/pages/ResponseListPage/index.vue

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,17 @@ import type { ResponseFull, LocalResponse } from "@shared-types/response"
2020
const responsesStore = useResponsesStore()
2121
const surveysStore = useSurveysStore()
2222
23-
const draftResponses = computed(() => responsesStore.drafts)
24-
const allResponses = computed(() => responsesStore.allResponses)
23+
const byDateDesc = (
24+
a: LocalResponse | ResponseFull,
25+
b: LocalResponse | ResponseFull
26+
) => new Date(b.creationDate).getTime() - new Date(a.creationDate).getTime()
27+
28+
const draftResponses = computed(() =>
29+
[...responsesStore.drafts].sort(byDateDesc)
30+
)
31+
const allResponses = computed(() =>
32+
[...responsesStore.allResponses].sort(byDateDesc)
33+
)
2534
const nondraftResponses = computed(() =>
2635
allResponses.value.filter((x) => x.status !== "draft")
2736
)

0 commit comments

Comments
 (0)