Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# The core business involves the administration of students, teachers,
# courses, programs and so on.
#
# Copyright (C) 2015-2025 Université catholique de Louvain (http://www.uclouvain.be)
# Copyright (C) 2015-2026 Université catholique de Louvain (http://www.uclouvain.be)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -35,6 +35,14 @@
ENTITY_CLSM = 'CLSM'
ENTITY_SCIENCES = 'CDSC'
SIGLE_SCIENCES = 'SC3DP'
FORMATIONS_DONT_COMMISSION_CDE_CLSM_EST_FACULTATIVE = {
'GEST3DP',
'ECON3DP',
'ECOB3DP',
'GESB3DP',
'ECOM3DP',
'GESM3DP',
}

COMMISSIONS_CDE_CLSM = {ENTITY_CDE, ENTITY_CLSM}
COMMISSIONS_CDSS = {ENTITY_CDSS}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# The core business involves the administration of students, teachers,
# courses, programs and so on.
#
# Copyright (C) 2015-2024 Université catholique de Louvain (http://www.uclouvain.be)
# Copyright (C) 2015-2026 Université catholique de Louvain (http://www.uclouvain.be)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand All @@ -27,7 +27,10 @@

import attr

from admission.ddd.admission.doctorat.preparation.domain.model.doctorat_formation import DoctoratFormation
from admission.ddd.admission.doctorat.preparation.domain.model.doctorat_formation import (
FORMATIONS_DONT_COMMISSION_CDE_CLSM_EST_FACULTATIVE,
DoctoratFormation,
)
from admission.ddd.admission.doctorat.preparation.domain.model.enums import (
ChoixCommissionProximiteCDEouCLSM,
ChoixCommissionProximiteCDSS,
Expand All @@ -45,9 +48,16 @@ class ShouldCommissionProximiteEtreValide(BusinessValidator):
commission_proximite: Optional[str]

def validate(self, *args, **kwargs):
if (self.doctorat.est_entite_CDE() or self.doctorat.est_entite_CLSM()) and (
not self.commission_proximite
or self.commission_proximite not in ChoixCommissionProximiteCDEouCLSM.get_names()
if (
(self.doctorat.est_entite_CDE() or self.doctorat.est_entite_CLSM())
and (
not self.commission_proximite
or self.commission_proximite not in ChoixCommissionProximiteCDEouCLSM.get_names()
)
if self.doctorat.entity_id.sigle not in FORMATIONS_DONT_COMMISSION_CDE_CLSM_EST_FACULTATIVE
else (
self.commission_proximite and not hasattr(ChoixCommissionProximiteCDEouCLSM, self.commission_proximite)
)
):
raise CommissionProximiteInconsistantException()
elif self.doctorat.est_entite_CDSS() and (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# The core business involves the administration of students, teachers,
# courses, programs and so on.
#
# Copyright (C) 2015-2025 Université catholique de Louvain (http://www.uclouvain.be)
# Copyright (C) 2015-2026 Université catholique de Louvain (http://www.uclouvain.be)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -39,11 +39,16 @@
ChoixSousDomaineSciences,
ChoixTypeAdmission,
)
from admission.ddd.admission.doctorat.preparation.domain.model.proposition import PropositionIdentity
from admission.ddd.admission.doctorat.preparation.domain.validator.exceptions import (
CommissionProximiteInconsistantException,
JustificationRequiseException,
PropositionNonTrouveeException,
)
from admission.ddd.admission.doctorat.preparation.test.factory.proposition import (
PropositionAdmissionECGE3DPMinimaleFactory,
)
from admission.ddd.admission.shared_kernel.domain.model.formation import FormationIdentity
from admission.infrastructure.admission.doctorat.preparation.repository.in_memory.proposition import (
PropositionInMemoryRepository,
)
Expand Down Expand Up @@ -96,6 +101,29 @@ def test_should_modifier_choix_formation_avec_commission_proximite_cde(self):
proposition = self.proposition_repository.get(proposition_id)
self.assertEqual(cmd.commission_proximite, proposition.commission_proximite.name)

def test_should_modifier_choix_formation_avec_commission_proximite_cde_vide(self):
for sigle_formation in ['ECON3DP', 'GEST3DP']:
proposition = PropositionAdmissionECGE3DPMinimaleFactory(
entity_id=PropositionIdentity(uuid=f"uuid-{sigle_formation}"),
formation_id=FormationIdentity(sigle=sigle_formation, annee=2020),
)

self.proposition_repository.save(entity=proposition)

cmd = attr.evolve(
self.cmd,
commission_proximite='',
uuid_proposition=proposition.entity_id.uuid,
)

proposition_id = self.message_bus.invoke(command=cmd)

self.assertEqual(proposition.entity_id, proposition_id)

proposition = self.proposition_repository.get(proposition.entity_id)

self.assertEqual(proposition.commission_proximite, None)

def test_should_modifier_choix_formation_avec_commission_proximite_cdss(self):
cmd = attr.evolve(
self.cmd,
Expand Down
23 changes: 16 additions & 7 deletions forms/admission/training_choice.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# The core business involves the administration of students, teachers,
# courses, programs and so on.
#
# Copyright (C) 2015-2025 Université catholique de Louvain (http://www.uclouvain.be)
# Copyright (C) 2015-2026 Université catholique de Louvain (http://www.uclouvain.be)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -48,7 +48,6 @@
from admission.ddd.admission.doctorat.preparation.dtos.proposition import (
PropositionGestionnaireDTO as PropositionDoctoraleDTO,
)
from admission.ddd.admission.shared_kernel.domain.enums import TypeFormation
from admission.ddd.admission.formation_continue.domain.model.enums import (
ChoixMoyensDecouverteFormation,
)
Expand All @@ -58,6 +57,7 @@
from admission.ddd.admission.formation_generale.dtos.proposition import (
PropositionGestionnaireDTO as PropositionGeneraleDTO,
)
from admission.ddd.admission.shared_kernel.domain.enums import TypeFormation
from admission.forms import (
AdmissionMainCampusChoiceField,
format_training,
Expand Down Expand Up @@ -431,7 +431,14 @@ def get_proximity_commission_field(cls, training: DoctoratFormationDTO) -> Optio
class Media:
js = ('js/dependsOn.min.js',)

def __init__(self, proposition: PropositionDoctoraleDTO, user: User, *args, **kwargs):
def __init__(
self,
proposition: PropositionDoctoraleDTO,
user: User,
trainings_whose_proximity_commission_is_facultative: set[str],
*args,
**kwargs,
):
self.training_type = AnneeInscriptionFormationTranslator.ADMISSION_EDUCATION_TYPE_BY_OSIS_TYPE[
proposition.doctorat.type
]
Expand All @@ -455,6 +462,11 @@ def __init__(self, proposition: PropositionDoctoraleDTO, user: User, *args, **kw

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

if self.proximity_commission_field:
self.fields[self.proximity_commission_field].required = (
proposition.doctorat.sigle not in trainings_whose_proximity_commission_is_facultative
)

# Initialise the choice fields
self.fields['doctorate_training'].choices = [
[self.initial['doctorate_training'], mark_safe(format_training(proposition.formation))]
Expand All @@ -475,8 +487,5 @@ def clean(self):
'proximity_commission_cdss',
'science_sub_domain',
]:
if field == self.proximity_commission_field:
if not cleaned_data.get(field):
self.add_error(field, FIELD_REQUIRED_MESSAGE)
else:
if field != self.proximity_commission_field:
cleaned_data[field] = ''
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# The core business involves the administration of students, teachers,
# courses, programs and so on.
#
# Copyright (C) 2015-2025 Université catholique de Louvain (http://www.uclouvain.be)
# Copyright (C) 2015-2026 Université catholique de Louvain (http://www.uclouvain.be)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -55,6 +55,26 @@ class DoctoratInMemoryTranslator(IDoctoratTranslator):
intitule_secteur='Secteur des sciences humaines',
grade_academique='1',
),
DoctoratCDEFactory(
entity_id__sigle='ECON3DP',
entity_id__annee=2020,
campus='Louvain-la-Neuve',
campus_inscription='Louvain-la-Neuve',
code_secteur='SSH',
intitule='Doctorat en sciences économiques',
intitule_secteur='Secteur des sciences humaines',
grade_academique='1',
),
DoctoratCDEFactory(
entity_id__sigle='GEST3DP',
entity_id__annee=2020,
campus='Louvain-la-Neuve',
campus_inscription='Louvain-la-Neuve',
code_secteur='SSH',
intitule='Doctorat en sciences de gestion',
intitule_secteur='Secteur des sciences humaines',
grade_academique='1',
),
DoctoratCLSMFactory(
entity_id__sigle='ECGM3DP',
entity_id__annee=2020,
Expand Down
6 changes: 2 additions & 4 deletions templates/admission/forms/training_choice.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* The core business involves the administration of students, teachers,
* courses, programs and so on.
*
* Copyright (C) 2015-2025 Université catholique de Louvain (http://www.uclouvain.be)
* Copyright (C) 2015-2026 Université catholique de Louvain (http://www.uclouvain.be)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -72,9 +72,7 @@
{% bootstrap_field form.doctorate_training %}
</div>
{% if form.proximity_commission_field %}
<div class="required_field">
{% bootstrap_field form|get_bound_field:form.proximity_commission_field %}
</div>
{% bootstrap_field form|get_bound_field:form.proximity_commission_field %}
{% endif %}
</div>
{% elif view.is_continuing %}
Expand Down
68 changes: 49 additions & 19 deletions tests/views/common/form_tabs/training_choice/test_doctorate.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# The core business involves the administration of students, teachers,
# courses, programs and so on.
#
# Copyright (C) 2015-2025 Université catholique de Louvain (http://www.uclouvain.be)
# Copyright (C) 2015-2026 Université catholique de Louvain (http://www.uclouvain.be)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -226,14 +226,16 @@ def test_training_choice_form_initialization(self):
self.assertCountEqual(
form.fields['doctorate_training'].choices,
[
tuple([
self.doctorate_admission.training.acronym,
'{} ({}) <span class="training-acronym">{}</span>'.format(
self.doctorate_admission.training.title,
self.first_campus.name,
tuple(
[
self.doctorate_admission.training.acronym,
),
])
'{} ({}) <span class="training-acronym">{}</span>'.format(
self.doctorate_admission.training.title,
self.first_campus.name,
self.doctorate_admission.training.acronym,
),
]
)
],
)
self.assertCountEqual(form.fields['sector'].choices, [('SST', 'Sector name')])
Expand Down Expand Up @@ -264,11 +266,43 @@ def test_training_choice_form_initialization(self):
self.assertEqual(form['proximity_commission_cde'].value(), ChoixCommissionProximiteCDEouCLSM.MANAGEMENT.name)
self.assertEqual(form['proximity_commission_cdss'].value(), None)
self.assertEqual(form['science_sub_domain'].value(), None)
self.assertTrue(form.fields['proximity_commission_cde'].required)
self.assertFalse(form.fields['proximity_commission_cdss'].required)
self.assertFalse(form.fields['science_sub_domain'].required)

# CDE > GEST3DP
self.doctorate_admission.training.acronym = 'GEST3DP'
self.doctorate_admission.training.save(update_fields=['acronym'])

response = self.client.get(self.doctorate_url)

self.assertEqual(response.status_code, status.HTTP_200_OK)

# Check context data
form = response.context['form']

self.assertEqual(form['proximity_commission_cde'].value(), ChoixCommissionProximiteCDEouCLSM.MANAGEMENT.name)
self.assertFalse(form.fields['proximity_commission_cde'].required)

# CDE > GEST3DP
self.doctorate_admission.training.acronym = 'ECON3DP'
self.doctorate_admission.training.save(update_fields=['acronym'])

response = self.client.get(self.doctorate_url)

self.assertEqual(response.status_code, status.HTTP_200_OK)

# Check context data
form = response.context['form']

self.assertEqual(form['proximity_commission_cde'].value(), ChoixCommissionProximiteCDEouCLSM.MANAGEMENT.name)
self.assertFalse(form.fields['proximity_commission_cde'].required)

# CDSS
self.doctorate_admission.training.management_entity = self.cdss_commission
self.doctorate_admission.training.acronym = 'CDSS3DP'
self.doctorate_admission.proximity_commission = ChoixCommissionProximiteCDSS.BCGIM.name
self.doctorate_admission.training.save(update_fields=['management_entity'])
self.doctorate_admission.training.save(update_fields=['management_entity', 'acronym'])
self.doctorate_admission.save(update_fields=['proximity_commission'])

response = self.client.get(self.doctorate_url)
Expand All @@ -281,6 +315,9 @@ def test_training_choice_form_initialization(self):
self.assertEqual(form['proximity_commission_cde'].value(), None)
self.assertEqual(form['proximity_commission_cdss'].value(), ChoixCommissionProximiteCDSS.BCGIM.name)
self.assertEqual(form['science_sub_domain'].value(), None)
self.assertFalse(form.fields['proximity_commission_cde'].required)
self.assertTrue(form.fields['proximity_commission_cdss'].required)
self.assertFalse(form.fields['science_sub_domain'].required)

# Sciences
self.doctorate_admission.training.management_entity = self.science_commission
Expand All @@ -299,6 +336,9 @@ def test_training_choice_form_initialization(self):
self.assertEqual(form['proximity_commission_cde'].value(), None)
self.assertEqual(form['proximity_commission_cdss'].value(), None)
self.assertEqual(form['science_sub_domain'].value(), ChoixSousDomaineSciences.MATHEMATICS.name)
self.assertFalse(form.fields['proximity_commission_cde'].required)
self.assertFalse(form.fields['proximity_commission_cdss'].required)
self.assertTrue(form.fields['science_sub_domain'].required)

@freezegun.freeze_time('2021-12-01')
def test_form_submit_with_valid_data(self):
Expand Down Expand Up @@ -404,16 +444,6 @@ def test_form_submit_with_valid_data(self):
def test_form_submit_with_invalid_data(self):
self.client.force_login(self.sic_manager_user)

default_data = {
'admission_type': ChoixTypeAdmission.ADMISSION.name,
'justification': 'My justification',
'proximity_commission_cde': ChoixCommissionProximiteCDEouCLSM.MANAGEMENT.name,
'proximity_commission_cdss': ChoixCommissionProximiteCDSS.BCGIM.name,
'science_sub_domain': ChoixSousDomaineSciences.MATHEMATICS.name,
'specific_question_answers_0': 'My answer 1 updated',
'specific_question_answers_2': 'My answer 2 updated',
}

# Without any data
response = self.client.post(self.doctorate_url, {})

Expand Down
14 changes: 12 additions & 2 deletions views/common/form_tabs/training_choice.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# The core business involves the administration of students, teachers,
# courses, programs and so on.
#
# Copyright (C) 2015-2025 Université catholique de Louvain (http://www.uclouvain.be)
# Copyright (C) 2015-2026 Université catholique de Louvain (http://www.uclouvain.be)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand All @@ -30,13 +30,16 @@
from admission.ddd.admission.doctorat.preparation.commands import (
ModifierChoixFormationParGestionnaireCommand as ModifierChoixFormationDoctoraleParGestionnaireCommand,
)
from admission.ddd.admission.shared_kernel.enums import Onglets
from admission.ddd.admission.doctorat.preparation.domain.model.doctorat_formation import (
FORMATIONS_DONT_COMMISSION_CDE_CLSM_EST_FACULTATIVE,
)
from admission.ddd.admission.formation_continue.commands import (
ModifierChoixFormationParGestionnaireCommand as ModifierChoixFormationContinueParGestionnaireCommand,
)
from admission.ddd.admission.formation_generale.commands import (
ModifierChoixFormationParGestionnaireCommand as ModifierChoixFormationGeneraleParGestionnaireCommand,
)
from admission.ddd.admission.shared_kernel.enums import Onglets
from admission.forms.admission.training_choice import (
ContinuingTrainingChoiceForm,
DoctorateTrainingChoiceForm,
Expand All @@ -63,9 +66,16 @@ def get_form_class(self):

def get_form_kwargs(self):
kwargs = super().get_form_kwargs()

kwargs['proposition'] = self.proposition
kwargs['user'] = self.request.user
kwargs['form_item_configurations'] = self.specific_questions

if self.is_doctorate:
kwargs['trainings_whose_proximity_commission_is_facultative'] = (
FORMATIONS_DONT_COMMISSION_CDE_CLSM_EST_FACULTATIVE
)

return kwargs

def form_valid(self, form):
Expand Down