Skip to content

Commit 08c8d2e

Browse files
authored
Merge pull request #717 from PROCOLLAB-github/feature/dev-vacancy-response-status
DEV: expose vacancy response status to applicant
2 parents 7d41166 + dca0e33 commit 08c8d2e

3 files changed

Lines changed: 77 additions & 6 deletions

File tree

‎vacancy/selectors.py‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from django.db.models import Count, Exists, OuterRef, Prefetch, Q, QuerySet
1+
from django.db.models import Count, Exists, OuterRef, Prefetch, Q, QuerySet, Subquery
22

33
from core.models import SkillToObject
44
from projects.models import Collaborator, Project
@@ -68,12 +68,16 @@ def with_applicant_state(queryset: QuerySet[Vacancy], user) -> QuerySet[Vacancy]
6868

6969
if not user or not user.is_authenticated:
7070
return queryset
71+
72+
current_user_response = VacancyResponse.objects.filter(
73+
vacancy_id=OuterRef("pk"),
74+
user_id=user.id,
75+
).order_by("-datetime_created", "-pk")
76+
7177
return queryset.annotate(
72-
current_user_has_responded=Exists(
73-
VacancyResponse.objects.filter(
74-
vacancy_id=OuterRef("pk"),
75-
user_id=user.id,
76-
)
78+
current_user_has_responded=Exists(current_user_response),
79+
current_user_response_is_approved=Subquery(
80+
current_user_response.values("is_approved")[:1]
7781
),
7882
current_user_is_collaborator=Exists(
7983
Collaborator.objects.filter(

‎vacancy/serializers.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,13 +180,29 @@ class VacancyDetailSerializer(
180180
):
181181
project = ProjectForVacancySerializer(many=False, read_only=True)
182182
has_responded = serializers.SerializerMethodField(read_only=True)
183+
response_status = serializers.SerializerMethodField(read_only=True)
183184
can_respond = serializers.SerializerMethodField(read_only=True)
184185
can_manage_responses = serializers.SerializerMethodField(read_only=True)
185186

186187
@staticmethod
187188
def get_has_responded(vacancy: Vacancy) -> bool:
188189
return bool(getattr(vacancy, "current_user_has_responded", False))
189190

191+
def get_response_status(self, vacancy: Vacancy) -> str | None:
192+
if not self.get_has_responded(vacancy):
193+
return None
194+
195+
is_approved = getattr(
196+
vacancy,
197+
"current_user_response_is_approved",
198+
None,
199+
)
200+
if is_approved is True:
201+
return "accepted"
202+
if is_approved is False:
203+
return "rejected"
204+
return "pending"
205+
190206
def get_can_respond(self, vacancy: Vacancy) -> bool:
191207
request = self.context.get("request")
192208
user = getattr(request, "user", None)
@@ -236,12 +252,14 @@ class Meta:
236252
"salary",
237253
"city",
238254
"has_responded",
255+
"response_status",
239256
"can_respond",
240257
"can_manage_responses",
241258
]
242259
read_only_fields = [
243260
"project",
244261
"has_responded",
262+
"response_status",
245263
"can_respond",
246264
"can_manage_responses",
247265
]

‎vacancy/tests/test_vacancy_response_contract_api.py‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,17 +316,40 @@ def test_outsider_state_changes_after_response(self):
316316
before = self.get_detail(outsider)
317317
self.assertEqual(before.status_code, status.HTTP_200_OK)
318318
self.assertFalse(before.data["has_responded"])
319+
self.assertIsNone(before.data["response_status"])
319320
self.assertTrue(before.data["can_respond"])
320321
self.assertFalse(before.data["can_manage_responses"])
321322

322323
create_vacancy_response(user=outsider, vacancy=self.vacancy)
323324
after = self.get_detail(outsider)
324325
self.assertTrue(after.data["has_responded"])
326+
self.assertEqual(after.data["response_status"], "pending")
325327
self.assertFalse(after.data["can_respond"])
326328

329+
def test_response_status_maps_processed_responses(self):
330+
for is_approved, expected_status in (
331+
(True, "accepted"),
332+
(False, "rejected"),
333+
):
334+
with self.subTest(is_approved=is_approved):
335+
applicant = create_user(prefix=f"applicant-{expected_status}")
336+
create_vacancy_response(
337+
user=applicant,
338+
vacancy=self.vacancy,
339+
is_approved=is_approved,
340+
)
341+
342+
response = self.get_detail(applicant)
343+
344+
self.assertEqual(response.status_code, status.HTTP_200_OK)
345+
self.assertTrue(response.data["has_responded"])
346+
self.assertEqual(response.data["response_status"], expected_status)
347+
self.assertFalse(response.data["can_respond"])
348+
327349
def test_leader_and_collaborator_states(self):
328350
leader_response = self.get_detail(self.leader)
329351
self.assertFalse(leader_response.data["has_responded"])
352+
self.assertIsNone(leader_response.data["response_status"])
330353
self.assertFalse(leader_response.data["can_respond"])
331354
self.assertTrue(leader_response.data["can_manage_responses"])
332355

@@ -348,9 +371,35 @@ def test_closed_and_anonymous_states(self):
348371

349372
anonymous_response = self.get_detail(None)
350373
self.assertFalse(anonymous_response.data["has_responded"])
374+
self.assertIsNone(anonymous_response.data["response_status"])
351375
self.assertFalse(anonymous_response.data["can_respond"])
352376
self.assertFalse(anonymous_response.data["can_manage_responses"])
353377

378+
def test_response_status_does_not_add_separate_detail_query(self):
379+
applicant = create_user(prefix="query-count-applicant")
380+
create_vacancy_response(user=applicant, vacancy=self.vacancy)
381+
self.client.force_authenticate(applicant)
382+
383+
with CaptureQueriesContext(connection) as queries:
384+
response = self.client.get(f"/vacancies/{self.vacancy.id}/")
385+
386+
self.assertEqual(response.status_code, status.HTTP_200_OK)
387+
response_table = VacancyResponse._meta.db_table
388+
response_queries = [
389+
query["sql"]
390+
for query in queries.captured_queries
391+
if response_table in query["sql"]
392+
]
393+
self.assertEqual(len(response_queries), 2)
394+
self.assertEqual(
395+
sum(
396+
'AS "current_user_response_is_approved"' in query
397+
for query in response_queries
398+
),
399+
1,
400+
)
401+
self.assertEqual(response.data["response_status"], "pending")
402+
354403

355404
class VacancyResponseDecisionContractTests(TestCase):
356405
def setUp(self):

0 commit comments

Comments
 (0)