Skip to content

Commit b3f8082

Browse files
committed
[OS-1744] Doctorate > some trainings do not require the proximity commission
1 parent 5497424 commit b3f8082

File tree

8 files changed

+153
-40
lines changed

8 files changed

+153
-40
lines changed

ddd/admission/doctorat/preparation/domain/model/doctorat_formation.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# The core business involves the administration of students, teachers,
77
# courses, programs and so on.
88
#
9-
# Copyright (C) 2015-2025 Université catholique de Louvain (http://www.uclouvain.be)
9+
# Copyright (C) 2015-2026 Université catholique de Louvain (http://www.uclouvain.be)
1010
#
1111
# This program is free software: you can redistribute it and/or modify
1212
# it under the terms of the GNU General Public License as published by
@@ -35,6 +35,14 @@
3535
ENTITY_CLSM = 'CLSM'
3636
ENTITY_SCIENCES = 'CDSC'
3737
SIGLE_SCIENCES = 'SC3DP'
38+
FORMATIONS_DONT_COMMISSION_CDE_CLSM_EST_FACULTATIVE = {
39+
'GEST3DP',
40+
'ECON3DP',
41+
'ECOB3DP',
42+
'GESB3DP',
43+
'ECOM3DP',
44+
'GESM3DP',
45+
}
3846

3947
COMMISSIONS_CDE_CLSM = {ENTITY_CDE, ENTITY_CLSM}
4048
COMMISSIONS_CDSS = {ENTITY_CDSS}

ddd/admission/doctorat/preparation/domain/validator/_should_commission_proximite_etre_valide.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# The core business involves the administration of students, teachers,
77
# courses, programs and so on.
88
#
9-
# Copyright (C) 2015-2024 Université catholique de Louvain (http://www.uclouvain.be)
9+
# Copyright (C) 2015-2026 Université catholique de Louvain (http://www.uclouvain.be)
1010
#
1111
# This program is free software: you can redistribute it and/or modify
1212
# it under the terms of the GNU General Public License as published by
@@ -27,7 +27,10 @@
2727

2828
import attr
2929

30-
from admission.ddd.admission.doctorat.preparation.domain.model.doctorat_formation import DoctoratFormation
30+
from admission.ddd.admission.doctorat.preparation.domain.model.doctorat_formation import (
31+
FORMATIONS_DONT_COMMISSION_CDE_CLSM_EST_FACULTATIVE,
32+
DoctoratFormation,
33+
)
3134
from admission.ddd.admission.doctorat.preparation.domain.model.enums import (
3235
ChoixCommissionProximiteCDEouCLSM,
3336
ChoixCommissionProximiteCDSS,
@@ -45,9 +48,16 @@ class ShouldCommissionProximiteEtreValide(BusinessValidator):
4548
commission_proximite: Optional[str]
4649

4750
def validate(self, *args, **kwargs):
48-
if (self.doctorat.est_entite_CDE() or self.doctorat.est_entite_CLSM()) and (
49-
not self.commission_proximite
50-
or self.commission_proximite not in ChoixCommissionProximiteCDEouCLSM.get_names()
51+
if (
52+
(self.doctorat.est_entite_CDE() or self.doctorat.est_entite_CLSM())
53+
and (
54+
not self.commission_proximite
55+
or self.commission_proximite not in ChoixCommissionProximiteCDEouCLSM.get_names()
56+
)
57+
if self.doctorat.entity_id.sigle not in FORMATIONS_DONT_COMMISSION_CDE_CLSM_EST_FACULTATIVE
58+
else (
59+
self.commission_proximite and not hasattr(ChoixCommissionProximiteCDEouCLSM, self.commission_proximite)
60+
)
5161
):
5262
raise CommissionProximiteInconsistantException()
5363
elif self.doctorat.est_entite_CDSS() and (

ddd/admission/doctorat/preparation/test/use_case/write/test_modifier_choix_formation_par_gestionnaire_service.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# The core business involves the administration of students, teachers,
77
# courses, programs and so on.
88
#
9-
# Copyright (C) 2015-2025 Université catholique de Louvain (http://www.uclouvain.be)
9+
# Copyright (C) 2015-2026 Université catholique de Louvain (http://www.uclouvain.be)
1010
#
1111
# This program is free software: you can redistribute it and/or modify
1212
# it under the terms of the GNU General Public License as published by
@@ -39,11 +39,16 @@
3939
ChoixSousDomaineSciences,
4040
ChoixTypeAdmission,
4141
)
42+
from admission.ddd.admission.doctorat.preparation.domain.model.proposition import PropositionIdentity
4243
from admission.ddd.admission.doctorat.preparation.domain.validator.exceptions import (
4344
CommissionProximiteInconsistantException,
4445
JustificationRequiseException,
4546
PropositionNonTrouveeException,
4647
)
48+
from admission.ddd.admission.doctorat.preparation.test.factory.proposition import (
49+
PropositionAdmissionECGE3DPMinimaleFactory,
50+
)
51+
from admission.ddd.admission.shared_kernel.domain.model.formation import FormationIdentity
4752
from admission.infrastructure.admission.doctorat.preparation.repository.in_memory.proposition import (
4853
PropositionInMemoryRepository,
4954
)
@@ -96,6 +101,29 @@ def test_should_modifier_choix_formation_avec_commission_proximite_cde(self):
96101
proposition = self.proposition_repository.get(proposition_id)
97102
self.assertEqual(cmd.commission_proximite, proposition.commission_proximite.name)
98103

104+
def test_should_modifier_choix_formation_avec_commission_proximite_cde_vide(self):
105+
for sigle_formation in ['ECON3DP', 'GEST3DP']:
106+
proposition = PropositionAdmissionECGE3DPMinimaleFactory(
107+
entity_id=PropositionIdentity(uuid=f"uuid-{sigle_formation}"),
108+
formation_id=FormationIdentity(sigle=sigle_formation, annee=2020),
109+
)
110+
111+
self.proposition_repository.save(entity=proposition)
112+
113+
cmd = attr.evolve(
114+
self.cmd,
115+
commission_proximite='',
116+
uuid_proposition=proposition.entity_id.uuid,
117+
)
118+
119+
proposition_id = self.message_bus.invoke(command=cmd)
120+
121+
self.assertEqual(proposition.entity_id, proposition_id)
122+
123+
proposition = self.proposition_repository.get(proposition.entity_id)
124+
125+
self.assertEqual(proposition.commission_proximite, None)
126+
99127
def test_should_modifier_choix_formation_avec_commission_proximite_cdss(self):
100128
cmd = attr.evolve(
101129
self.cmd,

forms/admission/training_choice.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# The core business involves the administration of students, teachers,
77
# courses, programs and so on.
88
#
9-
# Copyright (C) 2015-2025 Université catholique de Louvain (http://www.uclouvain.be)
9+
# Copyright (C) 2015-2026 Université catholique de Louvain (http://www.uclouvain.be)
1010
#
1111
# This program is free software: you can redistribute it and/or modify
1212
# it under the terms of the GNU General Public License as published by
@@ -48,7 +48,6 @@
4848
from admission.ddd.admission.doctorat.preparation.dtos.proposition import (
4949
PropositionGestionnaireDTO as PropositionDoctoraleDTO,
5050
)
51-
from admission.ddd.admission.shared_kernel.domain.enums import TypeFormation
5251
from admission.ddd.admission.formation_continue.domain.model.enums import (
5352
ChoixMoyensDecouverteFormation,
5453
)
@@ -58,6 +57,7 @@
5857
from admission.ddd.admission.formation_generale.dtos.proposition import (
5958
PropositionGestionnaireDTO as PropositionGeneraleDTO,
6059
)
60+
from admission.ddd.admission.shared_kernel.domain.enums import TypeFormation
6161
from admission.forms import (
6262
AdmissionMainCampusChoiceField,
6363
format_training,
@@ -431,7 +431,14 @@ def get_proximity_commission_field(cls, training: DoctoratFormationDTO) -> Optio
431431
class Media:
432432
js = ('js/dependsOn.min.js',)
433433

434-
def __init__(self, proposition: PropositionDoctoraleDTO, user: User, *args, **kwargs):
434+
def __init__(
435+
self,
436+
proposition: PropositionDoctoraleDTO,
437+
user: User,
438+
trainings_whose_proximity_commission_is_facultative: set[str],
439+
*args,
440+
**kwargs,
441+
):
435442
self.training_type = AnneeInscriptionFormationTranslator.ADMISSION_EDUCATION_TYPE_BY_OSIS_TYPE[
436443
proposition.doctorat.type
437444
]
@@ -455,6 +462,11 @@ def __init__(self, proposition: PropositionDoctoraleDTO, user: User, *args, **kw
455462

456463
self.fields['campus'].disabled = True
457464

465+
if self.proximity_commission_field:
466+
self.fields[self.proximity_commission_field].required = (
467+
proposition.doctorat.sigle not in trainings_whose_proximity_commission_is_facultative
468+
)
469+
458470
# Initialise the choice fields
459471
self.fields['doctorate_training'].choices = [
460472
[self.initial['doctorate_training'], mark_safe(format_training(proposition.formation))]
@@ -475,8 +487,5 @@ def clean(self):
475487
'proximity_commission_cdss',
476488
'science_sub_domain',
477489
]:
478-
if field == self.proximity_commission_field:
479-
if not cleaned_data.get(field):
480-
self.add_error(field, FIELD_REQUIRED_MESSAGE)
481-
else:
490+
if field != self.proximity_commission_field:
482491
cleaned_data[field] = ''

infrastructure/admission/doctorat/preparation/domain/service/in_memory/doctorat.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# The core business involves the administration of students, teachers,
77
# courses, programs and so on.
88
#
9-
# Copyright (C) 2015-2025 Université catholique de Louvain (http://www.uclouvain.be)
9+
# Copyright (C) 2015-2026 Université catholique de Louvain (http://www.uclouvain.be)
1010
#
1111
# This program is free software: you can redistribute it and/or modify
1212
# it under the terms of the GNU General Public License as published by
@@ -55,6 +55,26 @@ class DoctoratInMemoryTranslator(IDoctoratTranslator):
5555
intitule_secteur='Secteur des sciences humaines',
5656
grade_academique='1',
5757
),
58+
DoctoratCDEFactory(
59+
entity_id__sigle='ECON3DP',
60+
entity_id__annee=2020,
61+
campus='Louvain-la-Neuve',
62+
campus_inscription='Louvain-la-Neuve',
63+
code_secteur='SSH',
64+
intitule='Doctorat en sciences économiques',
65+
intitule_secteur='Secteur des sciences humaines',
66+
grade_academique='1',
67+
),
68+
DoctoratCDEFactory(
69+
entity_id__sigle='GEST3DP',
70+
entity_id__annee=2020,
71+
campus='Louvain-la-Neuve',
72+
campus_inscription='Louvain-la-Neuve',
73+
code_secteur='SSH',
74+
intitule='Doctorat en sciences de gestion',
75+
intitule_secteur='Secteur des sciences humaines',
76+
grade_academique='1',
77+
),
5878
DoctoratCLSMFactory(
5979
entity_id__sigle='ECGM3DP',
6080
entity_id__annee=2020,

templates/admission/forms/training_choice.html

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* The core business involves the administration of students, teachers,
99
* courses, programs and so on.
1010
*
11-
* Copyright (C) 2015-2025 Université catholique de Louvain (http://www.uclouvain.be)
11+
* Copyright (C) 2015-2026 Université catholique de Louvain (http://www.uclouvain.be)
1212
*
1313
* This program is free software: you can redistribute it and/or modify
1414
* it under the terms of the GNU General Public License as published by
@@ -72,9 +72,7 @@
7272
{% bootstrap_field form.doctorate_training %}
7373
</div>
7474
{% if form.proximity_commission_field %}
75-
<div class="required_field">
76-
{% bootstrap_field form|get_bound_field:form.proximity_commission_field %}
77-
</div>
75+
{% bootstrap_field form|get_bound_field:form.proximity_commission_field %}
7876
{% endif %}
7977
</div>
8078
{% elif view.is_continuing %}

tests/views/common/form_tabs/training_choice/test_doctorate.py

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# The core business involves the administration of students, teachers,
77
# courses, programs and so on.
88
#
9-
# Copyright (C) 2015-2025 Université catholique de Louvain (http://www.uclouvain.be)
9+
# Copyright (C) 2015-2026 Université catholique de Louvain (http://www.uclouvain.be)
1010
#
1111
# This program is free software: you can redistribute it and/or modify
1212
# it under the terms of the GNU General Public License as published by
@@ -226,14 +226,16 @@ def test_training_choice_form_initialization(self):
226226
self.assertCountEqual(
227227
form.fields['doctorate_training'].choices,
228228
[
229-
tuple([
230-
self.doctorate_admission.training.acronym,
231-
'{} ({}) <span class="training-acronym">{}</span>'.format(
232-
self.doctorate_admission.training.title,
233-
self.first_campus.name,
229+
tuple(
230+
[
234231
self.doctorate_admission.training.acronym,
235-
),
236-
])
232+
'{} ({}) <span class="training-acronym">{}</span>'.format(
233+
self.doctorate_admission.training.title,
234+
self.first_campus.name,
235+
self.doctorate_admission.training.acronym,
236+
),
237+
]
238+
)
237239
],
238240
)
239241
self.assertCountEqual(form.fields['sector'].choices, [('SST', 'Sector name')])
@@ -264,11 +266,43 @@ def test_training_choice_form_initialization(self):
264266
self.assertEqual(form['proximity_commission_cde'].value(), ChoixCommissionProximiteCDEouCLSM.MANAGEMENT.name)
265267
self.assertEqual(form['proximity_commission_cdss'].value(), None)
266268
self.assertEqual(form['science_sub_domain'].value(), None)
269+
self.assertTrue(form.fields['proximity_commission_cde'].required)
270+
self.assertFalse(form.fields['proximity_commission_cdss'].required)
271+
self.assertFalse(form.fields['science_sub_domain'].required)
272+
273+
# CDE > GEST3DP
274+
self.doctorate_admission.training.acronym = 'GEST3DP'
275+
self.doctorate_admission.training.save(update_fields=['acronym'])
276+
277+
response = self.client.get(self.doctorate_url)
278+
279+
self.assertEqual(response.status_code, status.HTTP_200_OK)
280+
281+
# Check context data
282+
form = response.context['form']
283+
284+
self.assertEqual(form['proximity_commission_cde'].value(), ChoixCommissionProximiteCDEouCLSM.MANAGEMENT.name)
285+
self.assertFalse(form.fields['proximity_commission_cde'].required)
286+
287+
# CDE > GEST3DP
288+
self.doctorate_admission.training.acronym = 'ECON3DP'
289+
self.doctorate_admission.training.save(update_fields=['acronym'])
290+
291+
response = self.client.get(self.doctorate_url)
292+
293+
self.assertEqual(response.status_code, status.HTTP_200_OK)
294+
295+
# Check context data
296+
form = response.context['form']
297+
298+
self.assertEqual(form['proximity_commission_cde'].value(), ChoixCommissionProximiteCDEouCLSM.MANAGEMENT.name)
299+
self.assertFalse(form.fields['proximity_commission_cde'].required)
267300

268301
# CDSS
269302
self.doctorate_admission.training.management_entity = self.cdss_commission
303+
self.doctorate_admission.training.acronym = 'CDSS3DP'
270304
self.doctorate_admission.proximity_commission = ChoixCommissionProximiteCDSS.BCGIM.name
271-
self.doctorate_admission.training.save(update_fields=['management_entity'])
305+
self.doctorate_admission.training.save(update_fields=['management_entity', 'acronym'])
272306
self.doctorate_admission.save(update_fields=['proximity_commission'])
273307

274308
response = self.client.get(self.doctorate_url)
@@ -281,6 +315,9 @@ def test_training_choice_form_initialization(self):
281315
self.assertEqual(form['proximity_commission_cde'].value(), None)
282316
self.assertEqual(form['proximity_commission_cdss'].value(), ChoixCommissionProximiteCDSS.BCGIM.name)
283317
self.assertEqual(form['science_sub_domain'].value(), None)
318+
self.assertFalse(form.fields['proximity_commission_cde'].required)
319+
self.assertTrue(form.fields['proximity_commission_cdss'].required)
320+
self.assertFalse(form.fields['science_sub_domain'].required)
284321

285322
# Sciences
286323
self.doctorate_admission.training.management_entity = self.science_commission
@@ -299,6 +336,9 @@ def test_training_choice_form_initialization(self):
299336
self.assertEqual(form['proximity_commission_cde'].value(), None)
300337
self.assertEqual(form['proximity_commission_cdss'].value(), None)
301338
self.assertEqual(form['science_sub_domain'].value(), ChoixSousDomaineSciences.MATHEMATICS.name)
339+
self.assertFalse(form.fields['proximity_commission_cde'].required)
340+
self.assertFalse(form.fields['proximity_commission_cdss'].required)
341+
self.assertTrue(form.fields['science_sub_domain'].required)
302342

303343
@freezegun.freeze_time('2021-12-01')
304344
def test_form_submit_with_valid_data(self):
@@ -404,16 +444,6 @@ def test_form_submit_with_valid_data(self):
404444
def test_form_submit_with_invalid_data(self):
405445
self.client.force_login(self.sic_manager_user)
406446

407-
default_data = {
408-
'admission_type': ChoixTypeAdmission.ADMISSION.name,
409-
'justification': 'My justification',
410-
'proximity_commission_cde': ChoixCommissionProximiteCDEouCLSM.MANAGEMENT.name,
411-
'proximity_commission_cdss': ChoixCommissionProximiteCDSS.BCGIM.name,
412-
'science_sub_domain': ChoixSousDomaineSciences.MATHEMATICS.name,
413-
'specific_question_answers_0': 'My answer 1 updated',
414-
'specific_question_answers_2': 'My answer 2 updated',
415-
}
416-
417447
# Without any data
418448
response = self.client.post(self.doctorate_url, {})
419449

views/common/form_tabs/training_choice.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# The core business involves the administration of students, teachers,
77
# courses, programs and so on.
88
#
9-
# Copyright (C) 2015-2025 Université catholique de Louvain (http://www.uclouvain.be)
9+
# Copyright (C) 2015-2026 Université catholique de Louvain (http://www.uclouvain.be)
1010
#
1111
# This program is free software: you can redistribute it and/or modify
1212
# it under the terms of the GNU General Public License as published by
@@ -30,13 +30,16 @@
3030
from admission.ddd.admission.doctorat.preparation.commands import (
3131
ModifierChoixFormationParGestionnaireCommand as ModifierChoixFormationDoctoraleParGestionnaireCommand,
3232
)
33-
from admission.ddd.admission.shared_kernel.enums import Onglets
33+
from admission.ddd.admission.doctorat.preparation.domain.model.doctorat_formation import (
34+
FORMATIONS_DONT_COMMISSION_CDE_CLSM_EST_FACULTATIVE,
35+
)
3436
from admission.ddd.admission.formation_continue.commands import (
3537
ModifierChoixFormationParGestionnaireCommand as ModifierChoixFormationContinueParGestionnaireCommand,
3638
)
3739
from admission.ddd.admission.formation_generale.commands import (
3840
ModifierChoixFormationParGestionnaireCommand as ModifierChoixFormationGeneraleParGestionnaireCommand,
3941
)
42+
from admission.ddd.admission.shared_kernel.enums import Onglets
4043
from admission.forms.admission.training_choice import (
4144
ContinuingTrainingChoiceForm,
4245
DoctorateTrainingChoiceForm,
@@ -63,9 +66,16 @@ def get_form_class(self):
6366

6467
def get_form_kwargs(self):
6568
kwargs = super().get_form_kwargs()
69+
6670
kwargs['proposition'] = self.proposition
6771
kwargs['user'] = self.request.user
6872
kwargs['form_item_configurations'] = self.specific_questions
73+
74+
if self.is_doctorate:
75+
kwargs['trainings_whose_proximity_commission_is_facultative'] = (
76+
FORMATIONS_DONT_COMMISSION_CDE_CLSM_EST_FACULTATIVE
77+
)
78+
6979
return kwargs
7080

7181
def form_valid(self, form):

0 commit comments

Comments
 (0)