diff --git a/admin.py b/admin.py
index 885dbd277..be5f625eb 100644
--- a/admin.py
+++ b/admin.py
@@ -105,7 +105,7 @@
from base.models.enums.education_group_categories import Categories
from base.models.person import Person
from base.models.person_merge_proposal import PersonMergeStatus
-from education_group.auth.scope import Scope
+from admission.auth.scope import Scope
from education_group.contrib.admin import EducationGroupRoleModelAdmin
from epc.models.inscription_programme_cycle import InscriptionProgrammeCycle
from osis_profile.models import EducationalExperience, ProfessionalExperience
@@ -684,15 +684,15 @@ def queryset(self, request, queryset):
| Q(
checklist__current__financabilite__status='GEST_REUSSITE',
checklist__current__financanbilite__extra__reussite='financable',
- generaleducationadmission__financability_rule=''
+ generaleducationadmission__financability_rule='',
)
| Q(
checklist__current__financabilite__status='GEST_REUSSITE',
- generaleducationadmission__financability_rule_established_on__isnull=True
+ generaleducationadmission__financability_rule_established_on__isnull=True,
)
| Q(
checklist__current__financabilite__status='GEST_REUSSITE',
- generaleducationadmission__financability_rule_established_by_id__isnull=True
+ generaleducationadmission__financability_rule_established_by_id__isnull=True,
),
generaleducationadmission__isnull=False,
then=Value(False),
diff --git a/auth/constants.py b/auth/constants.py
index d9e16d0e3..ed1568b11 100644
--- a/auth/constants.py
+++ b/auth/constants.py
@@ -80,7 +80,7 @@
# Training choice
'training-choice': 'admission.change_admission_training_choice',
# Previous experience
- 'curriculum': 'admission.change_admission_curriculum',
+ 'curriculum': 'admission.change_admission_global_curriculum',
'educational': '',
'educational_create': '',
'non_educational': '',
diff --git a/auth/predicates/common.py b/auth/predicates/common.py
index 52a2c67d1..f707d966f 100644
--- a/auth/predicates/common.py
+++ b/auth/predicates/common.py
@@ -31,9 +31,10 @@
from waffle import switch_is_active
from admission.contrib.models import DoctorateAdmission, GeneralEducationAdmission
+from admission.constants import CONTEXT_GENERAL, CONTEXT_DOCTORATE, CONTEXT_CONTINUING
from admission.contrib.models.base import BaseAdmission
-from admission.contrib.models.epc_injection import EPCInjectionStatus
from base.models.person_creation_ticket import PersonTicketCreation, PersonTicketCreationStatus
+from admission.auth.scope import Scope
from osis_role.errors import predicate_failed_msg
@@ -43,6 +44,28 @@ def is_admission_request_author(self, user: User, obj: BaseAdmission):
return obj.candidate == user.person
+@predicate(bind=True)
+@predicate_failed_msg(
+ message=_(
+ "This action cannot be performed as an admission or an internal experience is related to a general education."
+ ),
+)
+def candidate_has_other_general_admissions(self, user: User, obj: BaseAdmission):
+ return bool(obj.other_candidate_trainings[CONTEXT_GENERAL])
+
+
+@predicate(bind=True)
+@predicate_failed_msg(
+ message=_(
+ "This action cannot be performed as an admission or an internal experience is related to a general "
+ "or a doctorate education."
+ ),
+)
+def candidate_has_other_doctorate_or_general_admissions(self, user: User, obj: BaseAdmission):
+ other_admissions = obj.other_candidate_trainings
+ return bool(other_admissions[CONTEXT_GENERAL]) or bool(other_admissions[CONTEXT_DOCTORATE])
+
+
@predicate(bind=True)
@predicate_failed_msg(message=_("Another admission has been submitted."))
def does_not_have_a_submitted_admission(self, user: User, obj: DoctorateAdmission):
@@ -109,6 +132,25 @@ def is_entity_manager(self, user: User, obj: BaseAdmission):
return obj.training.management_entity_id in getattr(user, cache_key)
+@predicate(bind=True)
+def is_scoped_entity_manager(self, user: User, obj: BaseAdmission):
+ """
+ Check that the user is a manager of the admission training management entity with the correct scope.
+ """
+ scope = {
+ CONTEXT_GENERAL: Scope.GENERAL,
+ CONTEXT_DOCTORATE: Scope.DOCTORAT,
+ CONTEXT_CONTINUING: Scope.IUFC,
+ }[obj.admission_context]
+
+ cache_key = _build_queryset_cache_key_from_role_qs(self.context['role_qs'], f'entities_ids_by_scope_{scope.name}')
+
+ if not hasattr(user, cache_key):
+ setattr(user, cache_key, self.context['role_qs'].filter(scopes__contains=[scope.name]).get_entities_ids())
+
+ return obj.training.management_entity_id in getattr(user, cache_key)
+
+
def has_education_group_of_types(*education_group_types):
name = 'has_education_group_of_types:%s' % ','.join(education_group_types)
diff --git a/auth/predicates/continuing.py b/auth/predicates/continuing.py
index c79f06ebe..af269dccc 100644
--- a/auth/predicates/continuing.py
+++ b/auth/predicates/continuing.py
@@ -50,7 +50,9 @@ def is_continuing(self, user: User, obj: ContinuingEducationAdmission):
@predicate(bind=True)
@predicate_failed_msg(message=_('The proposition must be in draft form to realize this action.'))
def in_progress(self, user: User, obj: ContinuingEducationAdmission):
- return obj.status == ChoixStatutPropositionContinue.EN_BROUILLON.name
+ return (
+ isinstance(obj, ContinuingEducationAdmission) and obj.status == ChoixStatutPropositionContinue.EN_BROUILLON.name
+ )
@predicate(bind=True)
diff --git a/auth/predicates/doctorate.py b/auth/predicates/doctorate.py
index 7ee351f7f..84eae485d 100644
--- a/auth/predicates/doctorate.py
+++ b/auth/predicates/doctorate.py
@@ -55,7 +55,7 @@
@predicate(bind=True)
@predicate_failed_msg(message=_("Invitations must have been sent"))
def in_progress(self, user: User, obj: DoctorateAdmission):
- return obj.status == ChoixStatutPropositionDoctorale.EN_BROUILLON.name
+ return isinstance(obj, DoctorateAdmission) and obj.status == ChoixStatutPropositionDoctorale.EN_BROUILLON.name
@predicate(bind=True)
diff --git a/auth/predicates/general.py b/auth/predicates/general.py
index e3d3558d4..01f24853f 100644
--- a/auth/predicates/general.py
+++ b/auth/predicates/general.py
@@ -47,7 +47,7 @@
@predicate(bind=True)
@predicate_failed_msg(message=_('The proposition must be in draft form to realize this action.'))
def in_progress(self, user: User, obj: GeneralEducationAdmission):
- return obj.status == ChoixStatutPropositionGenerale.EN_BROUILLON.name
+ return isinstance(obj, GeneralEducationAdmission) and obj.status == ChoixStatutPropositionGenerale.EN_BROUILLON.name
@predicate(bind=True)
diff --git a/auth/roles/central_manager.py b/auth/roles/central_manager.py
index 1858ec07e..2e7665958 100644
--- a/auth/roles/central_manager.py
+++ b/auth/roles/central_manager.py
@@ -33,12 +33,15 @@
from admission.auth.predicates.common import (
has_scope,
is_debug,
- is_entity_manager,
+ is_entity_manager as is_entity_manager_without_scope,
+ is_scoped_entity_manager,
is_sent_to_epc,
pending_digit_ticket_response,
past_experiences_checklist_tab_is_not_sufficient,
+ candidate_has_other_doctorate_or_general_admissions,
+ candidate_has_other_general_admissions,
)
-from education_group.auth.scope import Scope
+from admission.auth.scope import Scope
from osis_role.contrib.models import EntityRoleModel
@@ -59,11 +62,19 @@ class Meta:
verbose_name_plural = _("Role: Central managers")
group_name = "admission_central_managers"
+ @classmethod
+ def rule_set_without_scope(cls):
+ return cls.common_rule_set(is_entity_manager_without_scope)
+
@classmethod
def rule_set(cls):
+ return cls.common_rule_set(is_scoped_entity_manager)
+
+ @classmethod
+ def common_rule_set(cls, is_entity_manager: callable):
ruleset = {
# Listings
- 'admission.view_enrolment_applications': has_scope(Scope.ALL),
+ 'admission.view_enrolment_applications': has_scope(Scope.GENERAL),
'admission.view_doctorate_enrolment_applications': has_scope(Scope.DOCTORAT),
'admission.view_continuing_enrolment_applications': has_scope(Scope.IUFC),
# Access a single application
@@ -74,14 +85,26 @@ def rule_set(cls):
'admission.appose_sic_notice': is_entity_manager,
'admission.view_admission_person': is_entity_manager,
'admission.change_admission_person': is_entity_manager
- & (general.in_sic_status | continuing.in_manager_status | doctorate.in_sic_status
- | general.in_progress | continuing.in_progress | doctorate.in_progress)
+ & (
+ (general.in_sic_status | general.in_progress)
+ | (
+ (continuing.in_manager_status | continuing.in_progress)
+ & ~candidate_has_other_doctorate_or_general_admissions
+ )
+ | ((doctorate.in_sic_status | doctorate.in_progress) & ~candidate_has_other_general_admissions)
+ )
& ~is_sent_to_epc
& ~pending_digit_ticket_response,
'admission.view_admission_coordinates': is_entity_manager,
'admission.change_admission_coordinates': is_entity_manager
- & (general.in_sic_status | continuing.in_manager_status | doctorate.in_sic_status
- | general.in_progress | continuing.in_progress | doctorate.in_progress)
+ & (
+ general.in_sic_status
+ | continuing.in_manager_status
+ | doctorate.in_sic_status
+ | general.in_progress
+ | continuing.in_progress
+ | doctorate.in_progress
+ )
& ~is_sent_to_epc
& ~pending_digit_ticket_response,
'admission.view_admission_training_choice': is_entity_manager,
@@ -93,14 +116,27 @@ def rule_set(cls):
'admission.change_admission_languages': is_entity_manager & doctorate.in_sic_status & ~is_sent_to_epc,
'admission.view_admission_secondary_studies': is_entity_manager,
'admission.change_admission_secondary_studies': is_entity_manager
- & (general.in_sic_status | continuing.in_manager_status)
+ & (
+ general.in_sic_status
+ | (continuing.in_manager_status & ~candidate_has_other_doctorate_or_general_admissions)
+ )
& ~is_sent_to_epc,
'admission.view_admission_curriculum': is_entity_manager,
- 'admission.change_admission_curriculum': is_entity_manager
+ 'admission.change_admission_global_curriculum': is_entity_manager
& (general.in_sic_status | continuing.in_manager_status | doctorate.in_sic_status)
& ~is_sent_to_epc,
+ 'admission.change_admission_curriculum': is_entity_manager
+ & (
+ general.in_sic_status
+ | (continuing.in_manager_status & ~candidate_has_other_doctorate_or_general_admissions)
+ | doctorate.in_sic_status
+ )
+ & ~is_sent_to_epc,
'admission.delete_admission_curriculum': is_entity_manager
- & (general.in_sic_status | continuing.in_manager_status | doctorate.in_sic_status)
+ & (
+ general.in_sic_status
+ | (continuing.in_manager_status & ~candidate_has_other_doctorate_or_general_admissions)
+ )
& ~is_sent_to_epc,
'admission.view_admission_project': is_entity_manager,
'admission.change_admission_project': is_entity_manager & doctorate.in_sic_status & ~is_sent_to_epc,
diff --git a/auth/roles/program_manager.py b/auth/roles/program_manager.py
index 342631c2e..f8204f0dd 100644
--- a/auth/roles/program_manager.py
+++ b/auth/roles/program_manager.py
@@ -35,6 +35,7 @@
is_sent_to_epc,
pending_digit_ticket_response,
past_experiences_checklist_tab_is_not_sufficient,
+ candidate_has_other_doctorate_or_general_admissions,
)
from admission.auth.predicates import general, continuing, doctorate
from admission.infrastructure.admission.domain.service.annee_inscription_formation import (
@@ -86,7 +87,8 @@ def rule_set(cls):
'admission.change_admission_person': is_part_of_education_group
& continuing.in_manager_status
& ~is_sent_to_epc
- & ~pending_digit_ticket_response,
+ & ~pending_digit_ticket_response
+ & ~candidate_has_other_doctorate_or_general_admissions,
'admission.view_admission_coordinates': is_part_of_education_group,
'admission.change_admission_coordinates': is_part_of_education_group
& continuing.in_manager_status
@@ -95,18 +97,26 @@ def rule_set(cls):
'admission.view_admission_secondary_studies': is_part_of_education_group,
'admission.change_admission_secondary_studies': is_part_of_education_group
& continuing.in_manager_status
- & ~is_sent_to_epc,
+ & ~is_sent_to_epc
+ & ~candidate_has_other_doctorate_or_general_admissions,
'admission.view_admission_languages': is_part_of_education_group,
'admission.change_admission_languages': is_part_of_education_group
& doctorate.in_fac_status
& ~is_sent_to_epc,
'admission.view_admission_curriculum': is_part_of_education_group,
- 'admission.change_admission_curriculum': is_part_of_education_group
+ 'admission.change_admission_global_curriculum': is_part_of_education_group
& (continuing.in_manager_status | doctorate.in_fac_status)
& ~is_sent_to_epc,
+ 'admission.change_admission_curriculum': is_part_of_education_group
+ & (
+ (continuing.in_manager_status & ~candidate_has_other_doctorate_or_general_admissions)
+ | doctorate.in_fac_status
+ )
+ & ~is_sent_to_epc,
'admission.delete_admission_curriculum': is_part_of_education_group
& continuing.in_manager_status
- & ~is_sent_to_epc,
+ & ~is_sent_to_epc
+ & ~candidate_has_other_doctorate_or_general_admissions,
# Project
'admission.view_admission_project': is_part_of_education_group,
'admission.view_admission_cotutelle': is_part_of_education_group,
diff --git a/auth/roles/sic_management.py b/auth/roles/sic_management.py
index 315bb41fa..fa145b2d8 100644
--- a/auth/roles/sic_management.py
+++ b/auth/roles/sic_management.py
@@ -49,7 +49,7 @@ class Meta:
@classmethod
def rule_set(cls):
ruleset = {
- **CentralManager.rule_set(),
+ **CentralManager.rule_set_without_scope(),
# Listings
'admission.checklist_change_sic_decision': rules.always_allow & ~is_sent_to_epc,
'admission.view_enrolment_applications': rules.always_allow,
diff --git a/auth/scope.py b/auth/scope.py
new file mode 100644
index 000000000..f451ab8f9
--- /dev/null
+++ b/auth/scope.py
@@ -0,0 +1,32 @@
+##############################################################################
+#
+# OSIS stands for Open Student Information System. It's an application
+# designed to manage the core business of higher education institutions,
+# such as universities, faculties, institutes and professional schools.
+# 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)
+#
+# 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
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# A copy of this license - GNU General Public License - is available
+# at the root of the source code of this program. If not,
+# see http://www.gnu.org/licenses/.
+#
+##############################################################################
+from base.models.utils.utils import ChoiceEnum
+
+
+class Scope(ChoiceEnum):
+ GENERAL = 'GENERAL'
+ IUFC = 'IUFC'
+ DOCTORAT = 'DOCTORAT'
diff --git a/contrib/models/base.py b/contrib/models/base.py
index 06928d092..1a1fe8e08 100644
--- a/contrib/models/base.py
+++ b/contrib/models/base.py
@@ -23,7 +23,9 @@
# see http://www.gnu.org/licenses/.
#
##############################################################################
+import itertools
import uuid
+from typing import Dict, Set
from django.conf import settings
from django.contrib.auth.models import User
@@ -41,7 +43,6 @@
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _, get_language, pgettext_lazy
from osis_comment.models import CommentDeleteMixin
-from osis_document.contrib import FileField
from osis_history.models import HistoryEntry
from admission.constants import (
@@ -55,6 +56,7 @@
from admission.contrib.models.functions import ToChar
from admission.ddd.admission.doctorat.preparation.domain.model.enums import (
STATUTS_PROPOSITION_DOCTORALE_NON_SOUMISE,
+ STATUTS_PROPOSITION_DOCTORALE_PEU_AVANCEE,
)
from admission.ddd.admission.enums.type_demande import TypeDemande
from admission.ddd.admission.formation_continue.domain.model.enums import (
@@ -67,6 +69,7 @@
from admission.ddd.admission.repository.i_proposition import CAMPUS_LETTRE_DOSSIER
from admission.infrastructure.admission.domain.service.annee_inscription_formation import (
AnneeInscriptionFormationTranslator,
+ ADMISSION_CONTEXT_BY_ALL_OSIS_EDUCATION_TYPE,
)
from base.models.academic_calendar import AcademicCalendar
from base.models.education_group_year import EducationGroupYear
@@ -80,6 +83,9 @@
from base.models.student import Student
from base.utils.cte import CTESubquery
from education_group.contrib.models import EducationGroupRoleModel
+from epc.models.enums.etat_inscription import EtatInscriptionFormation
+from epc.models.inscription_programme_annuel import InscriptionProgrammeAnnuel
+from osis_document.contrib import FileField
from osis_role.contrib.models import EntityRoleModel
from osis_role.contrib.permissions import _get_relevant_roles
from program_management.models.education_group_version import EducationGroupVersion
@@ -622,6 +628,54 @@ def sent_to_epc(self):
def is_in_quarantine(self):
return BaseAdmission.objects.filter(pk=self.pk).filter_in_quarantine().exists()
+ @cached_property
+ def other_candidate_trainings(self) -> Dict[str, Set[str]]:
+ # Retrieve the education group types from the admissions
+ admission_training_types = (
+ BaseAdmission.objects.filter(
+ candidate_id=self.candidate_id,
+ )
+ .exclude(
+ Q(pk=self.pk)
+ | Q(generaleducationadmission__status__in=STATUTS_PROPOSITION_GENERALE_NON_SOUMISE)
+ | Q(continuingeducationadmission__status__in=STATUTS_PROPOSITION_CONTINUE_NON_SOUMISE)
+ | Q(doctorateadmission__status__in=STATUTS_PROPOSITION_DOCTORALE_PEU_AVANCEE),
+ )
+ .values_list(
+ 'training__education_group_type__name',
+ flat=True,
+ )
+ )
+
+ # Retrieve the education group types from the internal trainings
+ internal_training_types = (
+ InscriptionProgrammeAnnuel.objects.filter(
+ programme_cycle__etudiant__person_id=self.candidate_id,
+ programme__isnull=False,
+ )
+ .exclude(
+ etat_inscription__in=[
+ EtatInscriptionFormation.ERREUR.name,
+ EtatInscriptionFormation.ERREUR_PROCEDURE.name,
+ ]
+ )
+ .values_list(
+ 'programme__root_group__education_group_type__name',
+ flat=True,
+ )
+ )
+
+ other_admissions = {
+ CONTEXT_GENERAL: set(),
+ CONTEXT_DOCTORATE: set(),
+ CONTEXT_CONTINUING: set(),
+ }
+
+ for training in itertools.chain(internal_training_types, admission_training_types):
+ other_admissions[ADMISSION_CONTEXT_BY_ALL_OSIS_EDUCATION_TYPE[training]].add(training)
+
+ return other_admissions
+
class AdmissionEducationalValuatedExperiences(models.Model):
baseadmission = models.ForeignKey(
diff --git a/ddd/admission/doctorat/preparation/domain/model/enums/projet.py b/ddd/admission/doctorat/preparation/domain/model/enums/projet.py
index e230a5308..10f809449 100644
--- a/ddd/admission/doctorat/preparation/domain/model/enums/projet.py
+++ b/ddd/admission/doctorat/preparation/domain/model/enums/projet.py
@@ -79,6 +79,11 @@ def get_specific_values(cls, keys: Iterable[str]):
set(ChoixStatutPropositionDoctorale.get_names()) - STATUTS_PROPOSITION_DOCTORALE_NON_SOUMISE
)
+STATUTS_PROPOSITION_DOCTORALE_PEU_AVANCEE = {
+ ChoixStatutPropositionDoctorale.ANNULEE.name,
+ ChoixStatutPropositionDoctorale.EN_BROUILLON.name,
+}
+
# Le gestionnaire FAC a la main
STATUTS_PROPOSITION_DOCTORALE_SOUMISE_POUR_FAC = {
ChoixStatutPropositionDoctorale.COMPLETEE_POUR_FAC.name,
diff --git a/infrastructure/admission/domain/service/annee_inscription_formation.py b/infrastructure/admission/domain/service/annee_inscription_formation.py
index 3a6f10641..a37f4c5de 100644
--- a/infrastructure/admission/domain/service/annee_inscription_formation.py
+++ b/infrastructure/admission/domain/service/annee_inscription_formation.py
@@ -24,15 +24,15 @@
#
##############################################################################
import datetime
-from typing import Optional
+from typing import Optional, Dict
+from admission.constants import CONTEXT_GENERAL, CONTEXT_DOCTORATE, CONTEXT_CONTINUING
from admission.ddd.admission.domain.enums import TypeFormation
from admission.ddd.admission.domain.service.i_annee_inscription_formation import IAnneeInscriptionFormationTranslator
from base.models.academic_calendar import AcademicCalendar
from base.models.enums.academic_calendar_type import AcademicCalendarTypes
-from base.models.enums.education_group_types import TrainingType
-
+from base.models.enums.education_group_types import TrainingType, ALL_TYPES, AllTypes
ADMISSION_EDUCATION_TYPE_BY_ADMISSION_CONTEXT = {
'general-education': {
@@ -145,3 +145,16 @@ def recuperer_annee_selon_type_formation(cls, type_formation: TrainingType) -> O
for admission_type in admission_types
for osis_type in AnneeInscriptionFormationTranslator.OSIS_ADMISSION_EDUCATION_TYPES_MAPPING[admission_type]
}
+
+
+doctorate_types_as_set = set(TrainingType.doctorate_types())
+continuing_education_types_as_set = set(TrainingType.continuing_education_types())
+
+ADMISSION_CONTEXT_BY_ALL_OSIS_EDUCATION_TYPE: Dict[str, str] = {
+ osis_type: CONTEXT_DOCTORATE
+ if osis_type in doctorate_types_as_set
+ else CONTEXT_CONTINUING
+ if osis_type in continuing_education_types_as_set
+ else CONTEXT_GENERAL
+ for osis_type in AllTypes.get_names()
+}
diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po
index 9711828e4..67cb20556 100644
--- a/locale/en/LC_MESSAGES/django.po
+++ b/locale/en/LC_MESSAGES/django.po
@@ -7317,6 +7317,16 @@ msgstr ""
msgid "This account number has been verified"
msgstr ""
+msgid ""
+"This action cannot be performed as an admission or an internal experience is "
+"related to a general education."
+msgstr ""
+
+msgid ""
+"This action cannot be performed as an admission or an internal experience is "
+"related to a general or a doctorate education."
+msgstr ""
+
msgid "This action is limited to a specific admission context."
msgstr ""
diff --git a/locale/fr_BE/LC_MESSAGES/django.po b/locale/fr_BE/LC_MESSAGES/django.po
index 25fa731e7..62c0a3ae9 100644
--- a/locale/fr_BE/LC_MESSAGES/django.po
+++ b/locale/fr_BE/LC_MESSAGES/django.po
@@ -8111,6 +8111,21 @@ msgstr "Données relatives à la thèse"
msgid "This account number has been verified"
msgstr "Ce numéro a été vérifié"
+msgid ""
+"This action cannot be performed as an admission or an internal experience is "
+"related to a general education."
+msgstr ""
+"Cette action ne peut être réalisée car il existe, pour ce candidat, une "
+"autre admission ou expérience interne relative à une formation générale."
+
+msgid ""
+"This action cannot be performed as an admission or an internal experience is "
+"related to a general or a doctorate education."
+msgstr ""
+"Cette action ne peut être réalisée car il existe, pour ce candidat, une "
+"autre admission ou expérience interne relative à une formation générale ou "
+"doctorale."
+
msgid "This action is limited to a specific admission context."
msgstr "Cette action est limitée à un contexte d'admission particulier."
diff --git a/migrations/0227_alter_centralmanager_scopes.py b/migrations/0227_alter_centralmanager_scopes.py
new file mode 100644
index 000000000..d3864672d
--- /dev/null
+++ b/migrations/0227_alter_centralmanager_scopes.py
@@ -0,0 +1,48 @@
+# Generated by Django 3.2.25 on 2024-10-18 17:40
+
+import django.contrib.postgres.fields
+from django.db import migrations, models
+
+
+def _update_central_manager_scopes(apps, old_value, new_value):
+ CentralManager = apps.get_model('admission', 'CentralManager')
+ central_managers_with_all_scope = CentralManager.objects.filter(scopes__contains=[old_value])
+
+ for central_manager in central_managers_with_all_scope:
+ central_manager.scopes[central_manager.scopes.index(old_value)] = new_value
+
+ CentralManager.objects.bulk_update(central_managers_with_all_scope, ['scopes'])
+
+
+def update_central_manager_scopes_forward(apps, schema_editor):
+ _update_central_manager_scopes(apps, 'ALL', 'GENERAL')
+
+
+def update_central_manager_scopes_reverse(apps, schema_editor):
+ _update_central_manager_scopes(apps, 'GENERAL', 'ALL')
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('admission', '0226_initialize_iufc_specific_questions'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='centralmanager',
+ name='scopes',
+ field=django.contrib.postgres.fields.ArrayField(
+ base_field=models.CharField(
+ choices=[('GENERAL', 'GENERAL'), ('IUFC', 'IUFC'), ('DOCTORAT', 'DOCTORAT')],
+ max_length=200,
+ ),
+ blank=True,
+ size=None,
+ ),
+ ),
+ migrations.RunPython(
+ code=update_central_manager_scopes_forward,
+ reverse_code=update_central_manager_scopes_reverse,
+ ),
+ ]
diff --git a/templates/admission/doctorate/checklist.html b/templates/admission/doctorate/checklist.html
index 74a672e84..501e5ede8 100644
--- a/templates/admission/doctorate/checklist.html
+++ b/templates/admission/doctorate/checklist.html
@@ -63,8 +63,9 @@
{% can_update_tab 'languages' as can_update_languages_tab %}
{% can_update_tab 'accounting' as can_update_accounting_tab %}
{% can_update_tab 'training-choice' as can_update_training_choice_tab %}
- {% can_update_tab 'curriculum' as can_update_curriculum %}
+ {% can_update_tab 'curriculum' as can_update_global_curriculum %}
{% can_update_tab 'education' as can_update_education %}
+ {% has_perm 'admission.change_admission_curriculum' as can_update_curriculum %}
{% has_perm 'admission.delete_admission_curriculum' as can_delete_curriculum %}
{% if can_update_person_tab %}
@@ -268,7 +269,7 @@
- {% if can_update_curriculum %}
+ {% if can_update_global_curriculum %}
{% url 'admission:doctorate:update:curriculum' view.kwargs.uuid as global_curriculum_update_url %}
{% endif %}
{% multiple_field_data specific_questions_by_tab|get_item:'CURRICULUM' edit_link_button=global_curriculum_update_url|add:next_url %}
diff --git a/templates/admission/general_education/checklist.html b/templates/admission/general_education/checklist.html
index a9d16d3e6..9dac3283a 100644
--- a/templates/admission/general_education/checklist.html
+++ b/templates/admission/general_education/checklist.html
@@ -61,8 +61,9 @@
{% can_update_tab 'accounting' as can_update_accounting_tab %}
{% can_update_tab 'training-choice' as can_update_training_choice_tab %}
{% can_update_tab 'specific-questions' as can_update_specific_questions_tab %}
- {% can_update_tab 'curriculum' as can_update_curriculum %}
+ {% can_update_tab 'curriculum' as can_update_global_curriculum %}
{% can_update_tab 'education' as can_update_education %}
+ {% has_perm 'admission.change_admission_curriculum' as can_update_curriculum %}
{% has_perm 'admission.delete_admission_curriculum' as can_delete_curriculum %}
{% if can_update_person_tab %}
@@ -319,7 +320,7 @@
{% include 'admission/general_education/includes/checklist/financeabilty_info.html' with current=original_admission.checklist.current.financabilite %}
- {% if can_update_curriculum %}
+ {% if can_update_global_curriculum %}
{% url 'admission:general-education:update:curriculum' view.kwargs.uuid as global_curriculum_update_url %}
{% endif %}
{% multiple_field_data specific_questions_by_tab|get_item:'CURRICULUM' edit_link_button=global_curriculum_update_url|add:next_url %}
diff --git a/templates/admission/includes/common_curriculum.html b/templates/admission/includes/common_curriculum.html
index dc1e659b4..996f9ac39 100644
--- a/templates/admission/includes/common_curriculum.html
+++ b/templates/admission/includes/common_curriculum.html
@@ -26,7 +26,7 @@
{% endcomment %}
-{% can_update_tab 'curriculum' as can_update_curriculum%}
+{% has_perm 'admission.change_admission_curriculum' as can_update_curriculum %}
{% url base_namespace|add:':update:curriculum:educational_create' view.kwargs.uuid as curriculum_educational_create_url %}
{% url base_namespace|add:':update:curriculum:non_educational_create' view.kwargs.uuid as curriculum_professional_create_url %}
diff --git a/tests/auth/predicates/test_common.py b/tests/auth/predicates/test_common.py
index 2aa54a09b..1a8135739 100644
--- a/tests/auth/predicates/test_common.py
+++ b/tests/auth/predicates/test_common.py
@@ -24,11 +24,20 @@
#
# ##############################################################################
from unittest import mock
+
+from django.contrib.auth.models import User
from django.test import TestCase
+from pytz.reference import Central
from admission.auth.predicates import common
+from admission.auth.predicates.common import is_scoped_entity_manager
+from admission.auth.roles.central_manager import CentralManager
from admission.tests.factories import DoctorateAdmissionFactory
-from admission.tests.factories.roles import CandidateFactory
+from admission.tests.factories.continuing_education import ContinuingEducationAdmissionFactory
+from admission.tests.factories.general_education import GeneralEducationAdmissionFactory
+from admission.tests.factories.roles import CandidateFactory, CentralManagerRoleFactory
+from base.tests.factories.entity_version import EntityVersionFactory
+from admission.auth.scope import Scope
class PredicatesTestCase(TestCase):
@@ -47,3 +56,158 @@ def test_is_admission_request_author(self):
request = DoctorateAdmissionFactory(candidate=candidate1)
self.assertTrue(common.is_admission_request_author(candidate1.user, request))
self.assertFalse(common.is_admission_request_author(candidate2.user, request))
+
+
+class TestIsEntityManager(TestCase):
+ @classmethod
+ def setUpTestData(cls):
+ cls.sector_entity = EntityVersionFactory(acronym='SSH')
+
+ cls.faculty_entity_1 = EntityVersionFactory(acronym='LSM', parent=cls.sector_entity.entity)
+ cls.faculty_entity_2 = EntityVersionFactory(acronym='AGRO', parent=cls.sector_entity.entity)
+ cls.faculty_entity_3 = EntityVersionFactory(acronym='LOCI', parent=cls.sector_entity.entity)
+
+ cls.school_entity_1 = EntityVersionFactory(acronym='CLSM', parent=cls.faculty_entity_1.entity)
+
+ def setUp(self):
+ self.predicate_context_mock = mock.patch(
+ "rules.Predicate.context",
+ new_callable=mock.PropertyMock,
+ return_value={
+ 'perm_name': 'dummy_perm',
+ 'role_qs': CentralManager.objects.none(),
+ },
+ )
+ self.mock = self.predicate_context_mock.start()
+ self.addCleanup(self.predicate_context_mock.stop)
+
+ def test_is_entity_manager_depending_on_the_training_management_entity(self):
+ entity_manager = CentralManagerRoleFactory(
+ entity=self.faculty_entity_1.entity,
+ scopes=[Scope.GENERAL.name, Scope.DOCTORAT.name, Scope.IUFC.name],
+ with_child=False,
+ )
+
+ entity_manager_user = entity_manager.person.user
+
+ self.mock.return_value['role_qs'] = CentralManager.objects.filter(person=entity_manager.person)
+
+ # Correct with direct entity
+ admission = GeneralEducationAdmissionFactory(
+ training__management_entity=self.faculty_entity_1.entity,
+ )
+
+ self.assertTrue(is_scoped_entity_manager(entity_manager_user, admission))
+
+ # Wrong with direct entity
+ admission.training.management_entity = self.faculty_entity_2.entity
+ admission.training.save(update_fields=['management_entity'])
+ self.assertFalse(is_scoped_entity_manager(entity_manager_user, admission))
+
+ # Wrong with child entity
+ admission.training.management_entity = self.school_entity_1.entity
+ admission.training.save(update_fields=['management_entity'])
+ self.assertFalse(is_scoped_entity_manager(entity_manager_user, admission))
+
+ # Correct with child entity
+ self.mock.return_value['role_qs'] = CentralManager.objects.filter(person=entity_manager.person)
+ entity_manager_user = User.objects.get(pk=entity_manager_user.pk)
+
+ entity_manager.with_child = True
+ entity_manager.save(update_fields=['with_child'])
+ self.assertTrue(is_scoped_entity_manager(entity_manager_user, admission))
+
+ def test_is_entity_manager_depending_on_the_scope(self):
+ entity_manager = CentralManagerRoleFactory(
+ entity=self.faculty_entity_1.entity,
+ scopes=[],
+ with_child=False,
+ )
+
+ other_role = CentralManagerRoleFactory(
+ person=entity_manager.person,
+ scopes=[Scope.GENERAL.name, Scope.DOCTORAT.name, Scope.IUFC.name],
+ with_child=False,
+ entity=self.school_entity_1.entity,
+ )
+
+ entity_manager_user = entity_manager.person.user
+
+ self.mock.return_value['role_qs'] = CentralManager.objects.filter(person=entity_manager.person)
+
+ # General education admission
+ admission = GeneralEducationAdmissionFactory(
+ training__management_entity=self.faculty_entity_1.entity,
+ )
+
+ entity_manager_user = User.objects.get(pk=entity_manager_user.pk)
+ self.assertFalse(is_scoped_entity_manager(entity_manager_user, admission))
+
+ entity_manager.scopes = [Scope.GENERAL.name]
+ entity_manager.save(update_fields=['scopes'])
+ entity_manager_user = User.objects.get(pk=entity_manager_user.pk)
+ self.assertTrue(is_scoped_entity_manager(entity_manager_user, admission))
+
+ entity_manager.scopes = [Scope.DOCTORAT.name]
+ entity_manager.save(update_fields=['scopes'])
+ entity_manager_user = User.objects.get(pk=entity_manager_user.pk)
+ self.assertFalse(is_scoped_entity_manager(entity_manager_user, admission))
+
+ entity_manager.scopes = [Scope.IUFC.name]
+ entity_manager.save(update_fields=['scopes'])
+ entity_manager_user = User.objects.get(pk=entity_manager_user.pk)
+ self.assertFalse(is_scoped_entity_manager(entity_manager_user, admission))
+
+ # Doctorate education admission
+ entity_manager.scopes = []
+ entity_manager.save(update_fields=['scopes'])
+ entity_manager_user = User.objects.get(pk=entity_manager_user.pk)
+
+ admission = DoctorateAdmissionFactory(
+ training__management_entity=self.faculty_entity_1.entity,
+ )
+
+ entity_manager_user = User.objects.get(pk=entity_manager_user.pk)
+ self.assertFalse(is_scoped_entity_manager(entity_manager_user, admission))
+
+ entity_manager.scopes = [Scope.GENERAL.name]
+ entity_manager.save(update_fields=['scopes'])
+ entity_manager_user = User.objects.get(pk=entity_manager_user.pk)
+ self.assertFalse(is_scoped_entity_manager(entity_manager_user, admission))
+
+ entity_manager.scopes = [Scope.DOCTORAT.name]
+ entity_manager.save(update_fields=['scopes'])
+ entity_manager_user = User.objects.get(pk=entity_manager_user.pk)
+ self.assertTrue(is_scoped_entity_manager(entity_manager_user, admission))
+
+ entity_manager.scopes = [Scope.IUFC.name]
+ entity_manager.save(update_fields=['scopes'])
+ entity_manager_user = User.objects.get(pk=entity_manager_user.pk)
+ self.assertFalse(is_scoped_entity_manager(entity_manager_user, admission))
+
+ # Continuing education admission
+ entity_manager.scopes = []
+ entity_manager.save(update_fields=['scopes'])
+ entity_manager_user = User.objects.get(pk=entity_manager_user.pk)
+
+ admission = ContinuingEducationAdmissionFactory(
+ training__management_entity=self.faculty_entity_1.entity,
+ )
+
+ entity_manager_user = User.objects.get(pk=entity_manager_user.pk)
+ self.assertFalse(is_scoped_entity_manager(entity_manager_user, admission))
+
+ entity_manager.scopes = [Scope.GENERAL.name]
+ entity_manager.save(update_fields=['scopes'])
+ entity_manager_user = User.objects.get(pk=entity_manager_user.pk)
+ self.assertFalse(is_scoped_entity_manager(entity_manager_user, admission))
+
+ entity_manager.scopes = [Scope.DOCTORAT.name]
+ entity_manager.save(update_fields=['scopes'])
+ entity_manager_user = User.objects.get(pk=entity_manager_user.pk)
+ self.assertFalse(is_scoped_entity_manager(entity_manager_user, admission))
+
+ entity_manager.scopes = [Scope.IUFC.name]
+ entity_manager.save(update_fields=['scopes'])
+ entity_manager_user = User.objects.get(pk=entity_manager_user.pk)
+ self.assertTrue(is_scoped_entity_manager(entity_manager_user, admission))
diff --git a/tests/contrib/models/test_base.py b/tests/contrib/models/test_base.py
index c5ae880ec..4c3230b14 100644
--- a/tests/contrib/models/test_base.py
+++ b/tests/contrib/models/test_base.py
@@ -29,19 +29,29 @@
from django.db import IntegrityError
from django.test import TestCase
+from admission.constants import CONTEXT_GENERAL, CONTEXT_CONTINUING, CONTEXT_DOCTORATE
from admission.contrib.models import AdmissionViewer, ContinuingEducationAdmissionProxy
from admission.contrib.models.base import admission_directory_path, BaseAdmission
+from admission.ddd.admission.doctorat.preparation.domain.model.enums import ChoixStatutPropositionDoctorale
+from admission.ddd.admission.formation_continue.domain.model.enums import ChoixStatutPropositionContinue
from admission.ddd.admission.formation_generale.domain.model.enums import ChoixStatutPropositionGenerale
+from admission.infrastructure.admission.domain.service.annee_inscription_formation import (
+ continuing_education_types_as_set,
+ doctorate_types_as_set,
+)
from admission.tests.factories import DoctorateAdmissionFactory
from admission.tests.factories.admission_viewer import AdmissionViewerFactory
from admission.tests.factories.continuing_education import ContinuingEducationAdmissionFactory
from admission.tests.factories.general_education import GeneralEducationAdmissionFactory
from base.models.entity_version import EntityVersion
+from base.models.enums.education_group_types import TrainingType, AllTypes
from base.models.enums.entity_type import EntityType
from base.models.person_merge_proposal import PersonMergeProposal, PersonMergeStatus
from base.tests.factories.academic_year import AcademicYearFactory
+from base.tests.factories.education_group_type import EducationGroupTypeFactory
from base.tests.factories.entity_version import MainEntityVersionFactory, EntityVersionFactory
from base.tests.factories.person import PersonFactory
+from epc.tests.factories.inscription_programme_annuel import InscriptionProgrammeAnnuelFactory
class BaseTestCase(TestCase):
@@ -264,3 +274,152 @@ def test_get_formatted_reference_depending_on_academic_year(self):
admission = BaseAdmission.objects.with_training_management_and_reference().get(uuid=created_admission.uuid)
self.assertEqual(admission.formatted_reference, reference % {'year': '21'})
+
+
+class OtherCandidateTrainingsTestCase(TestCase):
+ @classmethod
+ def setUpTestData(cls):
+ cls.admission = GeneralEducationAdmissionFactory(
+ training__education_group_type__name=TrainingType.BACHELOR.name,
+ )
+
+ def setUp(self):
+ try:
+ delattr(self.admission, 'other_candidate_trainings')
+ except AttributeError:
+ pass
+
+ def test_by_excluding_the_current_admission(self):
+ # Don't use the current admission
+ other_contexts = self.admission.other_candidate_trainings
+
+ self.assertEqual(other_contexts[CONTEXT_GENERAL], set())
+ self.assertEqual(other_contexts[CONTEXT_CONTINUING], set())
+ self.assertEqual(other_contexts[CONTEXT_DOCTORATE], set())
+
+ def test_with_general_admission(self):
+ excluding_statuses = {
+ ChoixStatutPropositionGenerale.EN_BROUILLON,
+ ChoixStatutPropositionGenerale.ANNULEE,
+ }
+
+ other_admission = GeneralEducationAdmissionFactory(
+ training__education_group_type__name=TrainingType.MASTER_MC.name,
+ candidate=self.admission.candidate,
+ )
+
+ for status in ChoixStatutPropositionGenerale:
+ other_admission.status = status.name
+ other_admission.save(update_fields=['status'])
+
+ other_contexts = self.admission.other_candidate_trainings
+
+ self.assertEqual(
+ other_contexts[CONTEXT_GENERAL],
+ {TrainingType.MASTER_MC.name} if status not in excluding_statuses else set(),
+ )
+ self.assertEqual(other_contexts[CONTEXT_CONTINUING], set())
+ self.assertEqual(other_contexts[CONTEXT_DOCTORATE], set())
+
+ delattr(self.admission, 'other_candidate_trainings')
+
+ def test_with_doctorate_admission(self):
+ excluding_statuses = {
+ ChoixStatutPropositionDoctorale.EN_BROUILLON,
+ ChoixStatutPropositionDoctorale.ANNULEE,
+ }
+
+ other_admission = DoctorateAdmissionFactory(
+ training__education_group_type__name=TrainingType.PHD.name,
+ candidate=self.admission.candidate,
+ )
+
+ for status in ChoixStatutPropositionDoctorale:
+ other_admission.status = status.name
+ other_admission.save(update_fields=['status'])
+
+ other_contexts = self.admission.other_candidate_trainings
+
+ self.assertEqual(
+ other_contexts[CONTEXT_DOCTORATE],
+ {TrainingType.PHD.name} if status not in excluding_statuses else set(),
+ )
+ self.assertEqual(other_contexts[CONTEXT_GENERAL], set())
+ self.assertEqual(other_contexts[CONTEXT_CONTINUING], set())
+
+ delattr(self.admission, 'other_candidate_trainings')
+
+ def test_with_continuing_admission(self):
+ excluding_statuses = {
+ ChoixStatutPropositionContinue.EN_BROUILLON,
+ ChoixStatutPropositionContinue.ANNULEE,
+ }
+
+ other_admission = ContinuingEducationAdmissionFactory(
+ training__education_group_type__name=TrainingType.CERTIFICATE_OF_SUCCESS.name,
+ candidate=self.admission.candidate,
+ )
+
+ for status in ChoixStatutPropositionContinue:
+ other_admission.status = status.name
+ other_admission.save(update_fields=['status'])
+
+ other_contexts = self.admission.other_candidate_trainings
+
+ self.assertEqual(
+ other_contexts[CONTEXT_CONTINUING],
+ {TrainingType.CERTIFICATE_OF_SUCCESS.name} if status not in excluding_statuses else set(),
+ )
+ self.assertEqual(other_contexts[CONTEXT_GENERAL], set())
+ self.assertEqual(other_contexts[CONTEXT_DOCTORATE], set())
+
+ delattr(self.admission, 'other_candidate_trainings')
+
+ def test_with_internal_experiences(self):
+ internal_experience = InscriptionProgrammeAnnuelFactory(
+ programme_cycle__etudiant__person=self.admission.candidate,
+ programme__root_group__education_group_type__name=TrainingType.MASTER_M1.name,
+ )
+
+ other_contexts = self.admission.other_candidate_trainings
+ self.assertEqual(other_contexts[CONTEXT_CONTINUING], set())
+ self.assertEqual(other_contexts[CONTEXT_GENERAL], {TrainingType.MASTER_M1.name})
+ self.assertEqual(other_contexts[CONTEXT_DOCTORATE], set())
+
+ delattr(self.admission, 'other_candidate_trainings')
+
+ internal_experience.programme = None
+ internal_experience.save(update_fields=['programme'])
+
+ other_contexts = self.admission.other_candidate_trainings
+ self.assertEqual(other_contexts[CONTEXT_CONTINUING], set())
+ self.assertEqual(other_contexts[CONTEXT_GENERAL], set())
+ self.assertEqual(other_contexts[CONTEXT_DOCTORATE], set())
+
+ def test_right_context_by_training_type(self):
+ internal_experience = InscriptionProgrammeAnnuelFactory(
+ programme_cycle__etudiant__person=self.admission.candidate,
+ )
+
+ root_group = internal_experience.programme.root_group
+
+ for training_type in AllTypes.get_names():
+ root_group.education_group_type = EducationGroupTypeFactory(name=training_type)
+ root_group.save(update_fields=['education_group_type'])
+
+ other_contexts = self.admission.other_candidate_trainings
+
+ if training_type in continuing_education_types_as_set:
+ self.assertEqual(other_contexts[CONTEXT_CONTINUING], {training_type})
+ self.assertEqual(other_contexts[CONTEXT_GENERAL], set())
+ self.assertEqual(other_contexts[CONTEXT_DOCTORATE], set())
+ elif training_type in doctorate_types_as_set:
+ self.assertEqual(other_contexts[CONTEXT_DOCTORATE], {training_type})
+ self.assertEqual(other_contexts[CONTEXT_GENERAL], set())
+ self.assertEqual(other_contexts[CONTEXT_CONTINUING], set())
+ else:
+ self.assertEqual(other_contexts[CONTEXT_GENERAL], {training_type})
+ self.assertEqual(other_contexts[CONTEXT_CONTINUING], set())
+ self.assertEqual(other_contexts[CONTEXT_DOCTORATE], set())
+
+ delattr(self.admission, 'other_candidate_trainings')
diff --git a/tests/factories/roles.py b/tests/factories/roles.py
index 66d0e80cf..83eec8790 100644
--- a/tests/factories/roles.py
+++ b/tests/factories/roles.py
@@ -6,7 +6,7 @@
# The core business involves the administration of students, teachers,
# courses, programs and so on.
#
-# Copyright (C) 2015-2023 Université catholique de Louvain (http://www.uclouvain.be)
+# Copyright (C) 2015-2024 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
@@ -35,7 +35,7 @@
from admission.auth.roles.program_manager import ProgramManager
from admission.auth.roles.promoter import Promoter
from admission.auth.roles.sic_management import SicManagement
-from education_group.auth.scope import Scope
+from admission.auth.scope import Scope
from osis_role.contrib.tests.factories import EducationGroupRoleModelFactory
@@ -92,7 +92,7 @@ class Meta:
'base.tests.factories.entity.EntityWithVersionFactory',
organization=None,
)
- scopes = [Scope.ALL.name, Scope.DOCTORAT.name, Scope.IUFC.name]
+ scopes = [Scope.GENERAL.name, Scope.DOCTORAT.name, Scope.IUFC.name]
with_child = True
diff --git a/tests/views/autocomplete/test_trainings.py b/tests/views/autocomplete/test_trainings.py
index 48ee76dce..870da9b40 100644
--- a/tests/views/autocomplete/test_trainings.py
+++ b/tests/views/autocomplete/test_trainings.py
@@ -37,7 +37,7 @@
from base.tests.factories.academic_year import AcademicYearFactory
from base.tests.factories.entity import EntityWithVersionFactory
from base.tests.factories.user import UserFactory
-from education_group.auth.scope import Scope
+from admission.auth.scope import Scope
from program_management.models.education_group_version import EducationGroupVersion
diff --git a/tests/views/common/detail_tabs/curriculum/test_educational_experience.py b/tests/views/common/detail_tabs/curriculum/test_educational_experience.py
index c80c6a562..6635243d6 100644
--- a/tests/views/common/detail_tabs/curriculum/test_educational_experience.py
+++ b/tests/views/common/detail_tabs/curriculum/test_educational_experience.py
@@ -67,33 +67,43 @@ def setUpTestData(cls):
cls.sic_manager_user = SicManagementRoleFactory(entity=cls.entity).person.user
cls.candidate = CandidateFactory().person
+ cls.other_candidate = CandidateFactory().person
cls.educational_experience: EducationalExperience = EducationalExperienceFactory(
person=cls.candidate,
linguistic_regime=cls.linguistic_regime,
country=cls.be_country,
)
+ cls.other_educational_experience: EducationalExperience = EducationalExperienceFactory(
+ person=cls.other_candidate,
+ linguistic_regime=cls.linguistic_regime,
+ country=cls.be_country,
+ )
cls.educational_experience_year: EducationalExperienceYear = EducationalExperienceYearFactory(
educational_experience=cls.educational_experience,
academic_year=academic_years[0],
)
+ cls.other_educational_experience_year: EducationalExperienceYear = EducationalExperienceYearFactory(
+ educational_experience=cls.other_educational_experience,
+ academic_year=academic_years[0],
+ )
- cls.continuing_admission: ContinuingEducationAdmission = ContinuingEducationAdmissionFactory(
+ cls.other_continuing_admission: ContinuingEducationAdmission = ContinuingEducationAdmissionFactory(
training__management_entity=cls.entity,
training__academic_year=academic_years[0],
status=ChoixStatutPropositionContinue.CONFIRMEE.name,
- candidate=cls.candidate,
+ candidate=cls.other_candidate,
)
- cls.continuing_program_manager_user = ProgramManagerRoleFactory(
- education_group=cls.continuing_admission.training.education_group,
+ cls.other_continuing_program_manager_user = ProgramManagerRoleFactory(
+ education_group=cls.other_continuing_admission.training.education_group,
).person.user
- cls.continuing_url = resolve_url(
+ cls.other_continuing_url = resolve_url(
'admission:continuing-education:curriculum:educational',
- uuid=cls.continuing_admission.uuid,
- experience_uuid=cls.educational_experience.uuid,
+ uuid=cls.other_continuing_admission.uuid,
+ experience_uuid=cls.other_educational_experience.uuid,
)
cls.general_admission: GeneralEducationAdmission = GeneralEducationAdmissionFactory(
@@ -154,35 +164,74 @@ def test_general_with_sic_manager(self):
self.assertEqual(response.status_code, 200)
def test_continuing_with_program_manager(self):
- self.client.force_login(user=self.continuing_program_manager_user)
- response = self.client.get(self.continuing_url)
+ self.client.force_login(user=self.other_continuing_program_manager_user)
+ response = self.client.get(self.other_continuing_url)
self.assertEqual(response.status_code, 200)
+ self.assertEqual(
+ response.context['edit_url'],
+ f'/admissions/continuing-education/{self.other_continuing_admission.uuid}/update/curriculum/educational/'
+ f'{self.other_educational_experience.uuid}',
+ )
def test_continuing_with_sic_manager(self):
self.client.force_login(user=self.sic_manager_user)
- response = self.client.get(self.continuing_url)
+ response = self.client.get(self.other_continuing_url)
self.assertEqual(response.status_code, 200)
experience = response.context['experience']
self.assertIsInstance(experience, ExperienceAcademiqueDTO)
- self.assertEqual(experience.uuid, self.educational_experience.uuid)
+ self.assertEqual(experience.uuid, self.other_educational_experience.uuid)
self.assertFalse(response.context['translation_required'])
self.assertFalse(response.context['is_foreign_experience'])
self.assertFalse(response.context['evaluation_system_with_credits'])
self.assertTrue(response.context['is_belgian_experience'])
self.assertEqual(
response.context['edit_url'],
- f'/admissions/continuing-education/{self.continuing_admission.uuid}/update/curriculum/educational/'
- f'{self.educational_experience.uuid}',
+ f'/admissions/continuing-education/{self.other_continuing_admission.uuid}/update/curriculum/educational/'
+ f'{self.other_educational_experience.uuid}',
+ )
+
+ def test_continuing_with_blocking_admissions(self):
+ self.client.force_login(user=self.sic_manager_user)
+
+ doctorate_admission = DoctorateAdmissionFactory(
+ candidate=self.other_candidate,
+ submitted=True,
)
+ response = self.client.get(self.other_continuing_url)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['edit_url'], '')
+
+ self.client.force_login(user=self.other_continuing_program_manager_user)
+ response = self.client.get(self.other_continuing_url)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['edit_url'], '')
+
+ doctorate_admission.delete()
+
+ general_admission = GeneralEducationAdmissionFactory(
+ candidate=self.other_candidate,
+ status=ChoixStatutPropositionGenerale.CONFIRMEE.name,
+ )
+
+ response = self.client.get(self.other_continuing_url)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['edit_url'], '')
+
+ self.client.force_login(user=self.sic_manager_user)
+
+ response = self.client.get(self.other_continuing_url)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['edit_url'], '')
+
def test_continuing_with_unknown_experience(self):
self.client.force_login(user=self.sic_manager_user)
response = self.client.get(
resolve_url(
'admission:continuing-education:curriculum:educational',
- uuid=self.continuing_admission.uuid,
+ uuid=self.other_continuing_admission.uuid,
experience_uuid=uuid.uuid4(),
)
)
diff --git a/tests/views/common/detail_tabs/curriculum/test_non_educational_experience.py b/tests/views/common/detail_tabs/curriculum/test_non_educational_experience.py
index 7096adce4..5bf58d9aa 100644
--- a/tests/views/common/detail_tabs/curriculum/test_non_educational_experience.py
+++ b/tests/views/common/detail_tabs/curriculum/test_non_educational_experience.py
@@ -63,26 +63,30 @@ def setUpTestData(cls):
cls.sic_manager_user = SicManagementRoleFactory(entity=cls.entity).person.user
cls.candidate = CandidateFactory().person
+ cls.other_candidate = CandidateFactory().person
cls.experience: ProfessionalExperience = ProfessionalExperienceFactory(
person=cls.candidate,
)
+ cls.other_experience: ProfessionalExperience = ProfessionalExperienceFactory(
+ person=cls.other_candidate,
+ )
- cls.continuing_admission: ContinuingEducationAdmission = ContinuingEducationAdmissionFactory(
+ cls.other_continuing_admission: ContinuingEducationAdmission = ContinuingEducationAdmissionFactory(
training__management_entity=cls.entity,
training__academic_year=academic_years[0],
status=ChoixStatutPropositionContinue.CONFIRMEE.name,
- candidate=cls.candidate,
+ candidate=cls.other_candidate,
)
- cls.continuing_program_manager_user = ProgramManagerRoleFactory(
- education_group=cls.continuing_admission.training.education_group,
+ cls.other_continuing_program_manager_user = ProgramManagerRoleFactory(
+ education_group=cls.other_continuing_admission.training.education_group,
).person.user
- cls.continuing_url = resolve_url(
+ cls.other_continuing_url = resolve_url(
'admission:continuing-education:curriculum:non_educational',
- uuid=cls.continuing_admission.uuid,
- experience_uuid=cls.experience.uuid,
+ uuid=cls.other_continuing_admission.uuid,
+ experience_uuid=cls.other_experience.uuid,
)
cls.doctorate_admission: DoctorateAdmission = DoctorateAdmissionFactory(
@@ -143,33 +147,74 @@ def test_general_with_sic_manager(self):
self.assertEqual(response.status_code, 200)
def test_continuing_with_program_manager(self):
- self.client.force_login(user=self.continuing_program_manager_user)
- response = self.client.get(self.continuing_url)
+ self.client.force_login(user=self.other_continuing_program_manager_user)
+ response = self.client.get(self.other_continuing_url)
self.assertEqual(response.status_code, 200)
+ self.assertEqual(
+ response.context['edit_url'],
+ f'/admissions/continuing-education/{self.other_continuing_admission.uuid}/update/curriculum/'
+ f'non_educational/'
+ f'{self.other_experience.uuid}',
+ )
def test_continuing_with_sic_manager(self):
self.client.force_login(user=self.sic_manager_user)
- response = self.client.get(self.continuing_url)
+ response = self.client.get(self.other_continuing_url)
self.assertEqual(response.status_code, 200)
experience = response.context['experience']
self.assertIsInstance(experience, ExperienceNonAcademiqueDTO)
- self.assertEqual(experience.uuid, self.experience.uuid)
+ self.assertEqual(experience.uuid, self.other_experience.uuid)
self.assertEqual(
response.context['edit_url'],
- f'/admissions/continuing-education/{self.continuing_admission.uuid}/update/curriculum/non_educational/'
- f'{self.experience.uuid}',
+ f'/admissions/continuing-education/{self.other_continuing_admission.uuid}/update/curriculum/'
+ f'non_educational/'
+ f'{self.other_experience.uuid}',
+ )
+
+ def test_continuing_with_blocking_admissions(self):
+ self.client.force_login(user=self.sic_manager_user)
+
+ doctorate_admission = DoctorateAdmissionFactory(
+ candidate=self.other_candidate,
+ submitted=True,
+ )
+
+ response = self.client.get(self.other_continuing_url)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['edit_url'], '')
+
+ self.client.force_login(user=self.other_continuing_program_manager_user)
+ response = self.client.get(self.other_continuing_url)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['edit_url'], '')
+
+ doctorate_admission.delete()
+
+ general_admission = GeneralEducationAdmissionFactory(
+ candidate=self.other_candidate,
+ status=ChoixStatutPropositionGenerale.CONFIRMEE.name,
)
+ response = self.client.get(self.other_continuing_url)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['edit_url'], '')
+
+ self.client.force_login(user=self.sic_manager_user)
+
+ response = self.client.get(self.other_continuing_url)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['edit_url'], '')
+
def test_continuing_with_unknown_experience(self):
self.client.force_login(user=self.sic_manager_user)
response = self.client.get(
resolve_url(
'admission:continuing-education:curriculum:non_educational',
- uuid=self.continuing_admission.uuid,
+ uuid=self.other_continuing_admission.uuid,
experience_uuid=uuid.uuid4(),
)
)
diff --git a/tests/views/common/detail_tabs/test_person.py b/tests/views/common/detail_tabs/test_person.py
index 24de24b61..10f04ed14 100644
--- a/tests/views/common/detail_tabs/test_person.py
+++ b/tests/views/common/detail_tabs/test_person.py
@@ -29,6 +29,7 @@
from django.test import TestCase
from django.utils.translation import gettext_lazy as _
+from admission.auth.scope import Scope
from admission.contrib.models import ContinuingEducationAdmission, DoctorateAdmission, GeneralEducationAdmission
from admission.ddd.admission.doctorat.preparation.domain.model.doctorat import ENTITY_CDE
from admission.ddd.admission.dtos.profil_candidat import ProfilCandidatDTO
@@ -139,6 +140,38 @@ def test_continuing_person_detail_sic_manager(self):
self.assertEqual(response.context['person'], self.continuing_admission.candidate)
self.assertEqual(response.context['contact_language'], _('French'))
+ def test_person_detail_depending_on_the_sic_manager_scope(self):
+ entity_manager = CentralManagerRoleFactory(
+ entity=self.continuing_admission.training.management_entity,
+ scopes=[],
+ )
+
+ self.client.force_login(user=entity_manager.person.user)
+
+ for invalid_scope in [
+ [],
+ [Scope.GENERAL.name],
+ [Scope.DOCTORAT.name],
+ [Scope.GENERAL.name, Scope.DOCTORAT.name],
+ ]:
+ entity_manager.scopes = invalid_scope
+ entity_manager.save(update_fields=['scopes'])
+
+ response = self.client.get(self.continuing_url)
+ self.assertEqual(response.status_code, 403)
+
+ for valid_scope in [
+ [Scope.IUFC.name],
+ [Scope.IUFC.name, Scope.GENERAL.name],
+ [Scope.IUFC.name, Scope.DOCTORAT.name],
+ [Scope.IUFC.name, Scope.GENERAL.name, Scope.DOCTORAT.name],
+ ]:
+ entity_manager.scopes = valid_scope
+ entity_manager.save(update_fields=['scopes'])
+
+ response = self.client.get(self.continuing_url)
+ self.assertEqual(response.status_code, 200)
+
def test_general_person_detail_program_manager(self):
self.client.force_login(user=self.general_program_manager_user)
diff --git a/tests/views/common/form_tabs/curriculum/educational_experience_update/test_continuing.py b/tests/views/common/form_tabs/curriculum/educational_experience_update/test_continuing.py
index 7c3b63852..0806ebca4 100644
--- a/tests/views/common/form_tabs/curriculum/educational_experience_update/test_continuing.py
+++ b/tests/views/common/form_tabs/curriculum/educational_experience_update/test_continuing.py
@@ -35,11 +35,13 @@
from admission.constants import CONTEXT_CONTINUING
from admission.contrib.models import ContinuingEducationAdmission
from admission.ddd.admission.formation_continue.domain.model.enums import ChoixStatutPropositionContinue
+from admission.tests.factories import DoctorateAdmissionFactory
from admission.tests.factories.continuing_education import ContinuingEducationAdmissionFactory
from admission.tests.factories.curriculum import (
EducationalExperienceFactory,
EducationalExperienceYearFactory,
)
+from admission.tests.factories.general_education import GeneralEducationAdmissionFactory
from admission.tests.factories.roles import SicManagementRoleFactory, ProgramManagerRoleFactory
from base.forms.utils.file_field import PDF_MIME_TYPE
from base.models.campus import Campus
@@ -206,16 +208,50 @@ def setUp(self):
uuid=self.continuing_admission.uuid,
)
- def test_update_curriculum_is_allowed_for_fac_users(self):
+ def test_update_curriculum_for_fac_users(self):
self.client.force_login(self.program_manager_user)
+
response = self.client.get(self.form_url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
+ doctorate_admission = DoctorateAdmissionFactory(
+ candidate=self.continuing_admission.candidate,
+ submitted=True,
+ )
+ response = self.client.get(self.form_url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ doctorate_admission.delete()
+
+ general_admission = GeneralEducationAdmissionFactory(
+ candidate=self.continuing_admission.candidate,
+ status=ChoixStatutPropositionContinue.CONFIRMEE.name,
+ )
+ response = self.client.get(self.form_url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
def test_update_curriculum_is_allowed_for_sic_users(self):
self.client.force_login(self.sic_manager_user)
+
response = self.client.get(self.form_url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
+ doctorate_admission = DoctorateAdmissionFactory(
+ candidate=self.continuing_admission.candidate,
+ submitted=True,
+ )
+ response = self.client.get(self.form_url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ doctorate_admission.delete()
+
+ general_admission = GeneralEducationAdmissionFactory(
+ candidate=self.continuing_admission.candidate,
+ status=ChoixStatutPropositionContinue.CONFIRMEE.name,
+ )
+ response = self.client.get(self.form_url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
def test_form_initialization(self):
self.client.force_login(self.sic_manager_user)
diff --git a/tests/views/common/form_tabs/curriculum/global/test_continuing.py b/tests/views/common/form_tabs/curriculum/global/test_continuing.py
index 3afb426cd..b0051b7ca 100644
--- a/tests/views/common/form_tabs/curriculum/global/test_continuing.py
+++ b/tests/views/common/form_tabs/curriculum/global/test_continuing.py
@@ -36,6 +36,7 @@
from admission.ddd.admission.enums import Onglets
from admission.ddd.admission.formation_continue.domain.model.enums import ChoixStatutPropositionContinue
from admission.forms import REQUIRED_FIELD_CLASS
+from admission.tests.factories import DoctorateAdmissionFactory
from admission.tests.factories.continuing_education import (
ContinuingEducationTrainingFactory,
ContinuingEducationAdmissionFactory,
@@ -45,6 +46,7 @@
)
from admission.tests.factories.curriculum import EducationalExperienceFactory, EducationalExperienceYearFactory
from admission.tests.factories.form_item import TextAdmissionFormItemFactory, AdmissionFormItemInstantiationFactory
+from admission.tests.factories.general_education import GeneralEducationAdmissionFactory
from admission.tests.factories.roles import SicManagementRoleFactory, ProgramManagerRoleFactory, CandidateFactory
from base.forms.utils.file_field import PDF_MIME_TYPE
from base.models.enums.education_group_types import TrainingType
@@ -192,6 +194,16 @@ def test_update_global_curriculum_is_allowed_for_fac_users(self):
self.assertEqual(response.status_code, status.HTTP_200_OK)
+ GeneralEducationAdmissionFactory(candidate=self.continuing_admission_with_attachments.candidate)
+
+ response = self.client.get(self.form_url_with_attachments)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+ DoctorateAdmissionFactory(candidate=self.continuing_admission_with_attachments.candidate)
+
+ response = self.client.get(self.form_url_with_attachments)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
def test_update_education_is_allowed_for_sic_users(self):
self.client.force_login(self.sic_manager_user)
response = self.client.get(self.form_url_with_attachments)
diff --git a/tests/views/common/form_tabs/curriculum/test_educational_experience_delete.py b/tests/views/common/form_tabs/curriculum/test_educational_experience_delete.py
index 437f0979f..e529403f4 100644
--- a/tests/views/common/form_tabs/curriculum/test_educational_experience_delete.py
+++ b/tests/views/common/form_tabs/curriculum/test_educational_experience_delete.py
@@ -35,18 +35,24 @@
from django.utils.translation import gettext
from rest_framework import status
-from admission.contrib.models import EPCInjection as AdmissionEPCInjection, DoctorateAdmission
+from admission.contrib.models import (
+ EPCInjection as AdmissionEPCInjection,
+ DoctorateAdmission,
+ ContinuingEducationAdmission,
+)
from admission.contrib.models.base import AdmissionEducationalValuatedExperiences
from admission.contrib.models.epc_injection import EPCInjectionType, EPCInjectionStatus as AdmissionEPCInjectionStatus
from admission.contrib.models.general_education import GeneralEducationAdmission
from admission.ddd.admission.doctorat.preparation.domain.model.doctorat import ENTITY_CDE
from admission.ddd.admission.doctorat.preparation.domain.model.enums import ChoixStatutPropositionDoctorale
from admission.ddd.admission.enums.emplacement_document import OngletsDemande
+from admission.ddd.admission.formation_continue.domain.model.enums import ChoixStatutPropositionContinue
from admission.ddd.admission.formation_generale.domain.model.enums import (
ChoixStatutPropositionGenerale,
)
from admission.ddd.admission.formation_generale.domain.service.checklist import Checklist
from admission.tests.factories import DoctorateAdmissionFactory
+from admission.tests.factories.continuing_education import ContinuingEducationAdmissionFactory
from admission.tests.factories.curriculum import (
EducationalExperienceFactory,
EducationalExperienceYearFactory,
@@ -104,6 +110,12 @@ def setUpTestData(cls):
submitted=True,
)
+ cls.other_continuing_admission: ContinuingEducationAdmission = ContinuingEducationAdmissionFactory(
+ training__management_entity=first_doctoral_commission,
+ training__academic_year=cls.academic_years[0],
+ status=ChoixStatutPropositionContinue.CONFIRMEE.name,
+ )
+
# Create users
cls.sic_manager_user = SicManagementRoleFactory(entity=first_doctoral_commission).person.user
cls.program_manager_user = ProgramManagerRoleFactory(
@@ -112,6 +124,9 @@ def setUpTestData(cls):
cls.doctorate_program_manager_user = ProgramManagerRoleFactory(
education_group=cls.doctorate_admission.training.education_group,
).person.user
+ cls.other_continuing_program_manager_user = ProgramManagerRoleFactory(
+ education_group=cls.other_continuing_admission.training.education_group,
+ ).person.user
cls.first_cycle_diploma = DiplomaTitleFactory(
cycle=Cycle.FIRST_CYCLE.name,
)
@@ -364,12 +379,103 @@ def test_delete_experience_from_doctorate_curriculum_is_not_allowed_for_fac_user
)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
- def test_delete_experience_from_doctorate_curriculum_is_allowed_for_sic_users(self):
+ def test_delete_experience_from_doctorate_curriculum_is_not_allowed_for_sic_users(self):
self.client.force_login(self.sic_manager_user)
response = self.client.delete(self.doctorate_delete_url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ def test_delete_experience_from_continuing_curriculum_for_fac_users(self):
+ self.client.force_login(self.other_continuing_program_manager_user)
+
+ experience = EducationalExperienceFactory(
+ person=self.other_continuing_admission.candidate,
+ )
+
+ url = resolve_url(
+ 'admission:continuing-education:update:curriculum:educational_delete',
+ uuid=self.other_continuing_admission.uuid,
+ experience_uuid=experience.uuid,
+ )
+
+ doctorate_admission = DoctorateAdmissionFactory(
+ candidate=experience.person,
+ submitted=True,
+ )
+
+ response = self.client.delete(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ doctorate_admission.delete()
+
+ general_admission = GeneralEducationAdmissionFactory(
+ candidate=experience.person,
+ status=ChoixStatutPropositionGenerale.CONFIRMEE.name,
+ )
+
+ response = self.client.delete(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ general_admission.delete()
+
+ response = self.client.delete(url)
+
+ base_curriculum_url = resolve_url(
+ 'admission:continuing-education:curriculum',
+ uuid=self.other_continuing_admission.uuid,
+ )
self.assertRedirects(
response=response,
fetch_redirect_response=False,
- expected_url=resolve_url('admission:doctorate:checklist', uuid=self.doctorate_admission.uuid),
+ expected_url=base_curriculum_url,
)
+
+ self.assertFalse(EducationalExperience.objects.filter(uuid=experience.uuid).exists())
+
+ def test_delete_experience_from_continuing_curriculum_for_sic_users(self):
+ self.client.force_login(self.sic_manager_user)
+
+ experience = EducationalExperienceFactory(
+ person=self.other_continuing_admission.candidate,
+ )
+
+ url = resolve_url(
+ 'admission:continuing-education:update:curriculum:educational_delete',
+ uuid=self.other_continuing_admission.uuid,
+ experience_uuid=experience.uuid,
+ )
+
+ doctorate_admission = DoctorateAdmissionFactory(
+ candidate=experience.person,
+ submitted=True,
+ )
+
+ response = self.client.delete(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ doctorate_admission.delete()
+
+ general_admission = GeneralEducationAdmissionFactory(
+ candidate=experience.person,
+ status=ChoixStatutPropositionGenerale.CONFIRMEE.name,
+ )
+
+ response = self.client.delete(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ general_admission.delete()
+
+ response = self.client.delete(url)
+
+ base_curriculum_url = resolve_url(
+ 'admission:continuing-education:curriculum',
+ uuid=self.other_continuing_admission.uuid,
+ )
+
+ self.assertRedirects(
+ response=response,
+ fetch_redirect_response=False,
+ expected_url=base_curriculum_url,
+ )
+
+ self.assertFalse(EducationalExperience.objects.filter(uuid=experience.uuid).exists())
diff --git a/tests/views/common/form_tabs/curriculum/test_educational_experience_duplicate.py b/tests/views/common/form_tabs/curriculum/test_educational_experience_duplicate.py
index 97bc76c5b..04bf9426b 100644
--- a/tests/views/common/form_tabs/curriculum/test_educational_experience_duplicate.py
+++ b/tests/views/common/form_tabs/curriculum/test_educational_experience_duplicate.py
@@ -35,16 +35,18 @@
from django.test import TestCase
from rest_framework import status
-from admission.contrib.models import DoctorateAdmission
+from admission.contrib.models import DoctorateAdmission, ContinuingEducationAdmission
from admission.contrib.models.base import AdmissionEducationalValuatedExperiences
from admission.contrib.models.general_education import GeneralEducationAdmission
from admission.ddd.admission.doctorat.preparation.domain.model.doctorat import ENTITY_CDE
from admission.ddd.admission.doctorat.preparation.domain.model.enums import ChoixStatutPropositionDoctorale
+from admission.ddd.admission.formation_continue.domain.model.enums import ChoixStatutPropositionContinue
from admission.ddd.admission.formation_generale.domain.model.enums import (
ChoixStatutPropositionGenerale,
)
from admission.ddd.admission.formation_generale.domain.service.checklist import Checklist
from admission.tests.factories import DoctorateAdmissionFactory
+from admission.tests.factories.continuing_education import ContinuingEducationAdmissionFactory
from admission.tests.factories.curriculum import (
EducationalExperienceFactory,
EducationalExperienceYearFactory,
@@ -93,6 +95,12 @@ def setUpTestData(cls):
submitted=True,
)
+ cls.other_continuing_admission: ContinuingEducationAdmission = ContinuingEducationAdmissionFactory(
+ training__management_entity=first_doctoral_commission,
+ training__academic_year=cls.academic_years[0],
+ status=ChoixStatutPropositionContinue.CONFIRMEE.name,
+ )
+
cls.be_country = CountryFactory(iso_code='BE', name='Belgique', name_en='Belgium')
cls.fr_country = CountryFactory(iso_code='FR', name='France', name_en='France')
@@ -120,6 +128,9 @@ def setUpTestData(cls):
cls.doctorate_program_manager_user = ProgramManagerRoleFactory(
education_group=cls.doctorate_admission.training.education_group,
).person.user
+ cls.other_continuing_program_manager_user = ProgramManagerRoleFactory(
+ education_group=cls.other_continuing_admission.training.education_group,
+ ).person.user
cls.files_uuids = [uuid.uuid4() for _ in range(9)]
cls.files_uuids_str = [str(current_uuid) for current_uuid in cls.files_uuids]
@@ -537,3 +548,95 @@ def test_duplicate_experience_from_doctorate_curriculum_is_allowed_for_sic_users
fetch_redirect_response=False,
expected_url=resolve_url('admission:doctorate:checklist', uuid=self.doctorate_admission.uuid),
)
+
+ def test_duplicate_experience_from_continuing_curriculum_for_fac_users(self):
+ self.client.force_login(self.other_continuing_program_manager_user)
+
+ experience = EducationalExperienceFactory(
+ person=self.other_continuing_admission.candidate,
+ )
+
+ url = resolve_url(
+ 'admission:continuing-education:update:curriculum:educational_duplicate',
+ uuid=self.other_continuing_admission.uuid,
+ experience_uuid=experience.uuid,
+ )
+
+ doctorate_admission = DoctorateAdmissionFactory(
+ candidate=experience.person,
+ submitted=True,
+ )
+
+ response = self.client.post(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ doctorate_admission.delete()
+
+ general_admission = GeneralEducationAdmissionFactory(
+ candidate=experience.person,
+ status=ChoixStatutPropositionGenerale.CONFIRMEE.name,
+ )
+
+ response = self.client.post(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ general_admission.delete()
+
+ response = self.client.post(url)
+
+ base_curriculum_url = resolve_url(
+ 'admission:continuing-education:curriculum',
+ uuid=self.other_continuing_admission.uuid,
+ )
+
+ self.assertRedirects(
+ response=response,
+ fetch_redirect_response=False,
+ expected_url=base_curriculum_url,
+ )
+
+ def test_duplicate_experience_from_continuing_curriculum_for_sic_users(self):
+ self.client.force_login(self.sic_manager_user)
+
+ experience = EducationalExperienceFactory(
+ person=self.other_continuing_admission.candidate,
+ )
+
+ url = resolve_url(
+ 'admission:continuing-education:update:curriculum:educational_duplicate',
+ uuid=self.other_continuing_admission.uuid,
+ experience_uuid=experience.uuid,
+ )
+
+ doctorate_admission = DoctorateAdmissionFactory(
+ candidate=experience.person,
+ submitted=True,
+ )
+
+ response = self.client.post(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ doctorate_admission.delete()
+
+ general_admission = GeneralEducationAdmissionFactory(
+ candidate=experience.person,
+ status=ChoixStatutPropositionGenerale.CONFIRMEE.name,
+ )
+
+ response = self.client.post(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ general_admission.delete()
+
+ response = self.client.post(url)
+
+ base_curriculum_url = resolve_url(
+ 'admission:continuing-education:curriculum',
+ uuid=self.other_continuing_admission.uuid,
+ )
+
+ self.assertRedirects(
+ response=response,
+ fetch_redirect_response=False,
+ expected_url=base_curriculum_url,
+ )
diff --git a/tests/views/common/form_tabs/curriculum/test_educational_experience_valuate.py b/tests/views/common/form_tabs/curriculum/test_educational_experience_valuate.py
index 8be92890b..8d55c4625 100644
--- a/tests/views/common/form_tabs/curriculum/test_educational_experience_valuate.py
+++ b/tests/views/common/form_tabs/curriculum/test_educational_experience_valuate.py
@@ -32,15 +32,17 @@
from django.test import TestCase
from rest_framework import status
-from admission.contrib.models import DoctorateAdmission
+from admission.contrib.models import DoctorateAdmission, ContinuingEducationAdmission
from admission.contrib.models.base import AdmissionEducationalValuatedExperiences
from admission.contrib.models.general_education import GeneralEducationAdmission
from admission.ddd.admission.doctorat.preparation.domain.model.enums import ChoixStatutPropositionDoctorale
+from admission.ddd.admission.formation_continue.domain.model.enums import ChoixStatutPropositionContinue
from admission.ddd.admission.formation_generale.domain.model.enums import (
ChoixStatutPropositionGenerale,
)
from admission.ddd.admission.formation_generale.domain.service.checklist import Checklist
from admission.tests.factories import DoctorateAdmissionFactory
+from admission.tests.factories.continuing_education import ContinuingEducationAdmissionFactory
from admission.tests.factories.curriculum import (
EducationalExperienceFactory,
)
@@ -71,6 +73,12 @@ def setUpTestData(cls):
submitted=True,
)
+ cls.other_continuing_admission: ContinuingEducationAdmission = ContinuingEducationAdmissionFactory(
+ training__management_entity=entity,
+ training__academic_year=cls.academic_years[0],
+ status=ChoixStatutPropositionContinue.CONFIRMEE.name,
+ )
+
# Create users
cls.sic_manager_user = SicManagementRoleFactory(entity=entity).person.user
cls.program_manager_user = ProgramManagerRoleFactory(
@@ -79,6 +87,9 @@ def setUpTestData(cls):
cls.doctorate_program_manager_user = ProgramManagerRoleFactory(
education_group=cls.doctorate_admission.training.education_group,
).person.user
+ cls.other_continuing_program_manager_user = ProgramManagerRoleFactory(
+ education_group=cls.other_continuing_admission.training.education_group,
+ ).person.user
def setUp(self):
# Create data
@@ -224,3 +235,95 @@ def test_valuate_experience_from_doctorate_curriculum_is_allowed_for_sic_users(s
response = self.client.post(f'{self.doctorate_valuate_url}?next={admission_url}&next_hash_url=custom_hash')
self.assertRedirects(response=response, fetch_redirect_response=False, expected_url=expected_url)
+
+ def test_valuate_experience_from_continuing_curriculum_for_fac_users(self):
+ self.client.force_login(self.other_continuing_program_manager_user)
+
+ experience = EducationalExperienceFactory(
+ person=self.other_continuing_admission.candidate,
+ )
+
+ url = resolve_url(
+ 'admission:continuing-education:update:curriculum:educational_valuate',
+ uuid=self.other_continuing_admission.uuid,
+ experience_uuid=experience.uuid,
+ )
+
+ doctorate_admission = DoctorateAdmissionFactory(
+ candidate=experience.person,
+ submitted=True,
+ )
+
+ response = self.client.post(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ doctorate_admission.delete()
+
+ general_admission = GeneralEducationAdmissionFactory(
+ candidate=experience.person,
+ status=ChoixStatutPropositionGenerale.CONFIRMEE.name,
+ )
+
+ response = self.client.post(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ general_admission.delete()
+
+ response = self.client.post(url)
+
+ base_curriculum_url = resolve_url(
+ 'admission:continuing-education:checklist',
+ uuid=self.other_continuing_admission.uuid,
+ )
+
+ self.assertRedirects(
+ response=response,
+ fetch_redirect_response=False,
+ expected_url=base_curriculum_url,
+ )
+
+ def test_valuate_experience_from_continuing_curriculum_for_sic_users(self):
+ self.client.force_login(self.sic_manager_user)
+
+ experience = EducationalExperienceFactory(
+ person=self.other_continuing_admission.candidate,
+ )
+
+ url = resolve_url(
+ 'admission:continuing-education:update:curriculum:educational_valuate',
+ uuid=self.other_continuing_admission.uuid,
+ experience_uuid=experience.uuid,
+ )
+
+ doctorate_admission = DoctorateAdmissionFactory(
+ candidate=experience.person,
+ submitted=True,
+ )
+
+ response = self.client.post(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ doctorate_admission.delete()
+
+ general_admission = GeneralEducationAdmissionFactory(
+ candidate=experience.person,
+ status=ChoixStatutPropositionGenerale.CONFIRMEE.name,
+ )
+
+ response = self.client.post(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ general_admission.delete()
+
+ response = self.client.post(url)
+
+ base_curriculum_url = resolve_url(
+ 'admission:continuing-education:checklist',
+ uuid=self.other_continuing_admission.uuid,
+ )
+
+ self.assertRedirects(
+ response=response,
+ fetch_redirect_response=False,
+ expected_url=base_curriculum_url,
+ )
diff --git a/tests/views/common/form_tabs/curriculum/test_non_educational_experience_delete.py b/tests/views/common/form_tabs/curriculum/test_non_educational_experience_delete.py
index 4c1b0155f..68c86ca82 100644
--- a/tests/views/common/form_tabs/curriculum/test_non_educational_experience_delete.py
+++ b/tests/views/common/form_tabs/curriculum/test_non_educational_experience_delete.py
@@ -33,20 +33,25 @@
from django.test import TestCase
from rest_framework import status
-from admission.contrib.models import EPCInjection as AdmissionEPCInjection, DoctorateAdmission
+from admission.contrib.models import (
+ EPCInjection as AdmissionEPCInjection,
+ DoctorateAdmission,
+ ContinuingEducationAdmission,
+)
from admission.contrib.models.base import (
AdmissionProfessionalValuatedExperiences,
)
from admission.contrib.models.epc_injection import EPCInjectionType, EPCInjectionStatus as AdmissionEPCInjectionStatus
from admission.contrib.models.general_education import GeneralEducationAdmission
from admission.ddd.admission.doctorat.preparation.domain.model.doctorat import ENTITY_CDE
-from admission.ddd.admission.doctorat.preparation.domain.model.enums import ChoixStatutPropositionDoctorale
from admission.ddd.admission.enums.emplacement_document import OngletsDemande
+from admission.ddd.admission.formation_continue.domain.model.enums import ChoixStatutPropositionContinue
from admission.ddd.admission.formation_generale.domain.model.enums import (
ChoixStatutPropositionGenerale,
)
from admission.ddd.admission.formation_generale.domain.service.checklist import Checklist
from admission.tests.factories import DoctorateAdmissionFactory
+from admission.tests.factories.continuing_education import ContinuingEducationAdmissionFactory
from admission.tests.factories.curriculum import (
ProfessionalExperienceFactory,
AdmissionProfessionalValuatedExperiencesFactory,
@@ -93,6 +98,12 @@ def setUpTestData(cls):
submitted=True,
)
+ cls.other_continuing_admission: ContinuingEducationAdmission = ContinuingEducationAdmissionFactory(
+ training__management_entity=first_doctoral_commission,
+ training__academic_year=cls.academic_years[0],
+ status=ChoixStatutPropositionContinue.CONFIRMEE.name,
+ )
+
# Create users
cls.sic_manager_user = SicManagementRoleFactory(entity=first_doctoral_commission).person.user
cls.program_manager_user = ProgramManagerRoleFactory(
@@ -101,6 +112,9 @@ def setUpTestData(cls):
cls.doctorate_program_manager_user = ProgramManagerRoleFactory(
education_group=cls.doctorate_admission.training.education_group,
).person.user
+ cls.other_continuing_program_manager_user = ProgramManagerRoleFactory(
+ education_group=cls.other_continuing_admission.training.education_group,
+ ).person.user
cls.file_uuid = uuid.uuid4()
def setUp(self):
@@ -264,31 +278,111 @@ def test_delete_known_experience(self):
def test_delete_experience_from_doctorate_curriculum_is_not_allowed_for_fac_users(self):
self.client.force_login(self.doctorate_program_manager_user)
- other_admission = DoctorateAdmissionFactory(
- training=self.doctorate_admission.training,
- candidate=self.doctorate_admission.candidate,
- status=ChoixStatutPropositionDoctorale.TRAITEMENT_FAC.name,
+ response = self.client.post(self.doctorate_delete_url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ def test_delete_experience_from_doctorate_curriculum_is_not_allowed_for_sic_users(self):
+ self.client.force_login(self.doctorate_program_manager_user)
+ response = self.client.post(self.doctorate_delete_url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ def test_delete_experience_from_continuing_curriculum_is_not_allowed_with_blocking_admissions(self):
+ self.client.force_login(self.doctorate_program_manager_user)
+ response = self.client.post(self.doctorate_delete_url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ def test_delete_experience_from_continuing_curriculum_for_fac_users(self):
+ self.client.force_login(self.other_continuing_program_manager_user)
+
+ experience = ProfessionalExperienceFactory(
+ person=self.other_continuing_admission.candidate,
)
- response = self.client.post(
- resolve_url(
- 'admission:doctorate:update:curriculum:non_educational_delete',
- uuid=other_admission.uuid,
- experience_uuid=self.experience.uuid,
- ),
+
+ url = resolve_url(
+ 'admission:continuing-education:update:curriculum:non_educational_delete',
+ uuid=self.other_continuing_admission.uuid,
+ experience_uuid=experience.uuid,
+ )
+
+ doctorate_admission = DoctorateAdmissionFactory(
+ candidate=experience.person,
+ submitted=True,
)
+
+ response = self.client.delete(url)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
- def test_delete_experience_from_doctorate_curriculum_is_allowed_for_sic_users(self):
+ doctorate_admission.delete()
+
+ general_admission = GeneralEducationAdmissionFactory(
+ candidate=experience.person,
+ status=ChoixStatutPropositionGenerale.CONFIRMEE.name,
+ )
+
+ response = self.client.delete(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ general_admission.delete()
+
+ response = self.client.delete(url)
+
+ base_curriculum_url = resolve_url(
+ 'admission:continuing-education:curriculum',
+ uuid=self.other_continuing_admission.uuid,
+ )
+
+ self.assertRedirects(
+ response=response,
+ fetch_redirect_response=False,
+ expected_url=base_curriculum_url,
+ )
+
+ self.assertFalse(ProfessionalExperience.objects.filter(uuid=experience.uuid).exists())
+
+ def test_delete_experience_from_continuing_curriculum_for_sic_users(self):
self.client.force_login(self.sic_manager_user)
- admission_url = resolve_url('admission')
- expected_url = f'{admission_url}#custom_hash'
+ experience = ProfessionalExperienceFactory(
+ person=self.other_continuing_admission.candidate,
+ )
- response = self.client.delete(f'{self.doctorate_delete_url}?next={admission_url}&next_hash_url=custom_hash')
+ url = resolve_url(
+ 'admission:continuing-education:update:curriculum:non_educational_delete',
+ uuid=self.other_continuing_admission.uuid,
+ experience_uuid=experience.uuid,
+ )
+
+ doctorate_admission = DoctorateAdmissionFactory(
+ candidate=experience.person,
+ submitted=True,
+ )
+
+ response = self.client.delete(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ doctorate_admission.delete()
+
+ general_admission = GeneralEducationAdmissionFactory(
+ candidate=experience.person,
+ status=ChoixStatutPropositionGenerale.CONFIRMEE.name,
+ )
+
+ response = self.client.delete(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ general_admission.delete()
+
+ response = self.client.delete(url)
+
+ base_curriculum_url = resolve_url(
+ 'admission:continuing-education:curriculum',
+ uuid=self.other_continuing_admission.uuid,
+ )
self.assertRedirects(
response=response,
fetch_redirect_response=False,
- expected_url=expected_url,
+ expected_url=base_curriculum_url,
)
- self.assertEqual(response.status_code, status.HTTP_302_FOUND)
+
+ self.assertFalse(ProfessionalExperience.objects.filter(uuid=experience.uuid).exists())
diff --git a/tests/views/common/form_tabs/curriculum/test_non_educational_experience_duplicate.py b/tests/views/common/form_tabs/curriculum/test_non_educational_experience_duplicate.py
index 4e9d488ce..9849eb5b6 100644
--- a/tests/views/common/form_tabs/curriculum/test_non_educational_experience_duplicate.py
+++ b/tests/views/common/form_tabs/curriculum/test_non_educational_experience_duplicate.py
@@ -35,18 +35,20 @@
from django.test import TestCase
from rest_framework import status
-from admission.contrib.models import DoctorateAdmission
+from admission.contrib.models import DoctorateAdmission, ContinuingEducationAdmission
from admission.contrib.models.base import (
AdmissionProfessionalValuatedExperiences,
)
from admission.contrib.models.general_education import GeneralEducationAdmission
from admission.ddd.admission.doctorat.preparation.domain.model.doctorat import ENTITY_CDE
from admission.ddd.admission.doctorat.preparation.domain.model.enums import ChoixStatutPropositionDoctorale
+from admission.ddd.admission.formation_continue.domain.model.enums import ChoixStatutPropositionContinue
from admission.ddd.admission.formation_generale.domain.model.enums import (
ChoixStatutPropositionGenerale,
)
from admission.ddd.admission.formation_generale.domain.service.checklist import Checklist
from admission.tests.factories import DoctorateAdmissionFactory
+from admission.tests.factories.continuing_education import ContinuingEducationAdmissionFactory
from admission.tests.factories.curriculum import (
ProfessionalExperienceFactory,
)
@@ -87,6 +89,12 @@ def setUpTestData(cls):
submitted=True,
)
+ cls.other_continuing_admission: ContinuingEducationAdmission = ContinuingEducationAdmissionFactory(
+ training__management_entity=first_doctoral_commission,
+ training__academic_year=cls.academic_years[0],
+ status=ChoixStatutPropositionContinue.CONFIRMEE.name,
+ )
+
# Create users
cls.sic_manager_user = SicManagementRoleFactory(entity=first_doctoral_commission).person.user
cls.program_manager_user = ProgramManagerRoleFactory(
@@ -95,6 +103,9 @@ def setUpTestData(cls):
cls.doctorate_program_manager_user = ProgramManagerRoleFactory(
education_group=cls.doctorate_admission.training.education_group,
).person.user
+ cls.other_continuing_program_manager_user = ProgramManagerRoleFactory(
+ education_group=cls.other_continuing_admission.training.education_group,
+ ).person.user
cls.file_uuid = uuid.uuid4()
cls.file_uuid_str = str(cls.file_uuid)
cls.duplicate_uuid = uuid.uuid4()
@@ -376,3 +387,95 @@ def test_duplicate_experience_from_doctorate_curriculum_is_allowed_for_sic_users
fetch_redirect_response=False,
expected_url=expected_url,
)
+
+ def test_duplicate_experience_from_continuing_curriculum_for_fac_users(self):
+ self.client.force_login(self.other_continuing_program_manager_user)
+
+ experience = ProfessionalExperienceFactory(
+ person=self.other_continuing_admission.candidate,
+ )
+
+ url = resolve_url(
+ 'admission:continuing-education:update:curriculum:non_educational_duplicate',
+ uuid=self.other_continuing_admission.uuid,
+ experience_uuid=experience.uuid,
+ )
+
+ doctorate_admission = DoctorateAdmissionFactory(
+ candidate=experience.person,
+ submitted=True,
+ )
+
+ response = self.client.post(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ doctorate_admission.delete()
+
+ general_admission = GeneralEducationAdmissionFactory(
+ candidate=experience.person,
+ status=ChoixStatutPropositionGenerale.CONFIRMEE.name,
+ )
+
+ response = self.client.post(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ general_admission.delete()
+
+ response = self.client.post(url)
+
+ base_curriculum_url = resolve_url(
+ 'admission:continuing-education:curriculum',
+ uuid=self.other_continuing_admission.uuid,
+ )
+
+ self.assertRedirects(
+ response=response,
+ fetch_redirect_response=False,
+ expected_url=base_curriculum_url,
+ )
+
+ def test_duplicate_experience_from_continuing_curriculum_for_sic_users(self):
+ self.client.force_login(self.sic_manager_user)
+
+ experience = ProfessionalExperienceFactory(
+ person=self.other_continuing_admission.candidate,
+ )
+
+ url = resolve_url(
+ 'admission:continuing-education:update:curriculum:non_educational_duplicate',
+ uuid=self.other_continuing_admission.uuid,
+ experience_uuid=experience.uuid,
+ )
+
+ doctorate_admission = DoctorateAdmissionFactory(
+ candidate=experience.person,
+ submitted=True,
+ )
+
+ response = self.client.post(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ doctorate_admission.delete()
+
+ general_admission = GeneralEducationAdmissionFactory(
+ candidate=experience.person,
+ status=ChoixStatutPropositionGenerale.CONFIRMEE.name,
+ )
+
+ response = self.client.post(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ general_admission.delete()
+
+ response = self.client.post(url)
+
+ base_curriculum_url = resolve_url(
+ 'admission:continuing-education:curriculum',
+ uuid=self.other_continuing_admission.uuid,
+ )
+
+ self.assertRedirects(
+ response=response,
+ fetch_redirect_response=False,
+ expected_url=base_curriculum_url,
+ )
diff --git a/tests/views/common/form_tabs/curriculum/test_non_educational_experience_update.py b/tests/views/common/form_tabs/curriculum/test_non_educational_experience_update.py
index 88dfafdc5..ff0eaa674 100644
--- a/tests/views/common/form_tabs/curriculum/test_non_educational_experience_update.py
+++ b/tests/views/common/form_tabs/curriculum/test_non_educational_experience_update.py
@@ -96,8 +96,7 @@ def setUpTestData(cls):
status=ChoixStatutPropositionGenerale.CONFIRMEE.name,
)
- cls.continuing_admission: ContinuingEducationAdmission = ContinuingEducationAdmissionFactory(
- candidate=cls.general_admission.candidate,
+ cls.other_continuing_admission: ContinuingEducationAdmission = ContinuingEducationAdmissionFactory(
training__management_entity=entity,
training__academic_year=cls.academic_years[0],
status=ChoixStatutPropositionContinue.CONFIRMEE.name,
@@ -115,8 +114,8 @@ def setUpTestData(cls):
cls.general_program_manager_user = ProgramManagerRoleFactory(
education_group=cls.general_admission.training.education_group,
).person.user
- cls.continuing_program_manager_user = ProgramManagerRoleFactory(
- education_group=cls.continuing_admission.training.education_group,
+ cls.other_continuing_program_manager_user = ProgramManagerRoleFactory(
+ education_group=cls.other_continuing_admission.training.education_group,
).person.user
cls.doctorate_program_manager_user = ProgramManagerRoleFactory(
education_group=cls.doctorate_admission.training.education_group,
@@ -162,15 +161,6 @@ def setUp(self):
'admission:general-education:update:curriculum:non_educational_create',
uuid=self.general_admission.uuid,
)
- self.continuing_form_url = resolve_url(
- 'admission:continuing-education:update:curriculum:non_educational',
- uuid=self.continuing_admission.uuid,
- experience_uuid=self.experience.uuid,
- )
- self.continuing_create_url = resolve_url(
- 'admission:continuing-education:update:curriculum:non_educational_create',
- uuid=self.continuing_admission.uuid,
- )
self.doctorate_form_url = resolve_url(
'admission:doctorate:update:curriculum:non_educational',
uuid=self.doctorate_admission.uuid,
@@ -535,25 +525,93 @@ def test_general_submit_valid_form_for_create_a_new_work_activity(self):
},
)
- def test_continuing_update_curriculum_is_allowed_for_fac_users(self):
- self.client.force_login(self.continuing_program_manager_user)
- response = self.client.get(self.continuing_form_url)
+ def test_continuing_update_curriculum_for_fac_users(self):
+ self.client.force_login(self.other_continuing_program_manager_user)
+
+ experience = ProfessionalExperienceFactory(
+ person=self.other_continuing_admission.candidate,
+ )
+
+ url = resolve_url(
+ 'admission:continuing-education:update:curriculum:non_educational',
+ uuid=self.other_continuing_admission.uuid,
+ experience_uuid=experience.uuid,
+ )
+
+ response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
- def test_continuing_update_curriculum_is_allowed_for_sic_users(self):
+ doctorate_admission = DoctorateAdmissionFactory(
+ candidate=experience.person,
+ submitted=True,
+ )
+
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ doctorate_admission.delete()
+
+ general_admission = GeneralEducationAdmissionFactory(
+ candidate=experience.person,
+ status=ChoixStatutPropositionGenerale.CONFIRMEE.name,
+ )
+
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ def test_continuing_update_curriculum_for_sic_users(self):
self.client.force_login(self.sic_manager_user)
- response = self.client.get(self.continuing_form_url)
+
+ experience = ProfessionalExperienceFactory(
+ person=self.other_continuing_admission.candidate,
+ )
+
+ url = resolve_url(
+ 'admission:continuing-education:update:curriculum:non_educational',
+ uuid=self.other_continuing_admission.uuid,
+ experience_uuid=experience.uuid,
+ )
+
+ response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
form = response.context['form']
self.assertEqual(form.fields['certificate'].disabled, True)
self.assertIsInstance(form.fields['certificate'].widget, MultipleHiddenInput)
+ doctorate_admission = DoctorateAdmissionFactory(
+ candidate=experience.person,
+ submitted=True,
+ )
+
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ doctorate_admission.delete()
+
+ general_admission = GeneralEducationAdmissionFactory(
+ candidate=experience.person,
+ status=ChoixStatutPropositionGenerale.CONFIRMEE.name,
+ )
+
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
def test_continuing_submit_form(self):
self.client.force_login(self.sic_manager_user)
+ experience = ProfessionalExperienceFactory(
+ person=self.other_continuing_admission.candidate,
+ )
+
+ url = resolve_url(
+ 'admission:continuing-education:update:curriculum:non_educational',
+ uuid=self.other_continuing_admission.uuid,
+ experience_uuid=experience.uuid,
+ )
+
response = self.client.post(
- self.continuing_form_url,
+ url,
{
'start_date_month': 1,
'start_date_year': 2020,
@@ -564,28 +622,28 @@ def test_continuing_submit_form(self):
'sector': ActivitySector.PRIVATE.name,
'institute_name': 'Institute',
'activity': 'Activity',
- 'certificate_0': 'certificate-token',
+ 'certificate_0': [self.file_uuid],
},
)
self.assertEqual(response.status_code, status.HTTP_302_FOUND)
# Check the experience
- self.experience.refresh_from_db()
+ experience.refresh_from_db()
- self.assertEqual(self.experience.start_date, datetime.date(2020, 1, 1))
- self.assertEqual(self.experience.end_date, datetime.date(2020, 5, 31))
- self.assertEqual(self.experience.type, ActivityType.INTERNSHIP.name)
- self.assertEqual(self.experience.role, '')
- self.assertEqual(self.experience.sector, '')
- self.assertEqual(self.experience.institute_name, '')
- self.assertEqual(self.experience.activity, '')
- self.assertEqual(self.experience.certificate, [self.file_uuid])
+ self.assertEqual(experience.start_date, datetime.date(2020, 1, 1))
+ self.assertEqual(experience.end_date, datetime.date(2020, 5, 31))
+ self.assertEqual(experience.type, ActivityType.INTERNSHIP.name)
+ self.assertEqual(experience.role, '')
+ self.assertEqual(experience.sector, '')
+ self.assertEqual(experience.institute_name, '')
+ self.assertEqual(experience.activity, '')
+ self.assertEqual(experience.certificate, [])
# Check the admission
- self.continuing_admission.refresh_from_db()
- self.assertEqual(self.continuing_admission.modified_at, datetime.datetime.now())
- self.assertEqual(self.continuing_admission.last_update_author, self.sic_manager_user.person)
+ self.other_continuing_admission.refresh_from_db()
+ self.assertEqual(self.other_continuing_admission.modified_at, datetime.datetime.now())
+ self.assertEqual(self.other_continuing_admission.last_update_author, self.sic_manager_user.person)
def test_doctorate_update_curriculum_is_allowed_for_fac_users(self):
other_admission = DoctorateAdmissionFactory(
diff --git a/tests/views/common/form_tabs/test_education.py b/tests/views/common/form_tabs/test_education.py
index 7f3d126b9..9a162e3b7 100644
--- a/tests/views/common/form_tabs/test_education.py
+++ b/tests/views/common/form_tabs/test_education.py
@@ -38,10 +38,12 @@
from admission.contrib.models.epc_injection import EPCInjectionType, EPCInjectionStatus as AdmissionEPCInjectionStatus
from admission.contrib.models.general_education import GeneralEducationAdmission
from admission.ddd.admission.doctorat.preparation.domain.model.doctorat import ENTITY_CDE
+from admission.ddd.admission.doctorat.preparation.domain.model.enums import ChoixStatutPropositionDoctorale
from admission.ddd.admission.enums import Onglets
from admission.ddd.admission.enums.emplacement_document import OngletsDemande
from admission.ddd.admission.formation_continue.domain.model.enums import ChoixStatutPropositionContinue
from admission.ddd.admission.formation_generale.domain.model.enums import ChoixStatutPropositionGenerale
+from admission.tests.factories import DoctorateAdmissionFactory
from admission.tests.factories.continuing_education import ContinuingEducationAdmissionFactory
from admission.tests.factories.form_item import TextAdmissionFormItemFactory, AdmissionFormItemInstantiationFactory
from admission.tests.factories.general_education import GeneralEducationAdmissionFactory
@@ -723,18 +725,70 @@ def setUp(self):
patched = patcher.start()
patched.side_effect = lambda _, value, __: value
- def test_update_education_is_allowed_for_fac_users(self):
+ def test_update_education_for_fac_users(self):
self.client.force_login(self.program_manager_user)
+
response = self.client.get(self.form_url)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+ continuing_admission = ContinuingEducationAdmissionFactory(
+ candidate=self.continuing_admission.candidate,
+ status=ChoixStatutPropositionContinue.CONFIRMEE.name,
+ )
+ response = self.client.get(self.form_url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
- def test_update_education_is_allowed_for_sic_users(self):
+ doctorate_admission = DoctorateAdmissionFactory(
+ candidate=self.continuing_admission.candidate,
+ status=ChoixStatutPropositionDoctorale.CONFIRMEE.name,
+ )
+
+ response = self.client.get(self.form_url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ doctorate_admission.delete()
+
+ general_admission = GeneralEducationAdmissionFactory(
+ candidate=self.continuing_admission.candidate,
+ status=ChoixStatutPropositionGenerale.CONFIRMEE.name,
+ )
+
+ response = self.client.get(self.form_url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ def test_update_education_for_sic_users(self):
self.client.force_login(self.sic_manager_user)
response = self.client.get(self.form_url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
+ continuing_admission = ContinuingEducationAdmissionFactory(
+ candidate=self.continuing_admission.candidate,
+ status=ChoixStatutPropositionContinue.CONFIRMEE.name,
+ )
+
+ response = self.client.get(self.form_url)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+ doctorate_admission = DoctorateAdmissionFactory(
+ candidate=self.continuing_admission.candidate,
+ status=ChoixStatutPropositionDoctorale.CONFIRMEE.name,
+ )
+
+ response = self.client.get(self.form_url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ doctorate_admission.delete()
+
+ general_admission = GeneralEducationAdmissionFactory(
+ candidate=self.continuing_admission.candidate,
+ status=ChoixStatutPropositionGenerale.CONFIRMEE.name,
+ )
+
+ response = self.client.get(self.form_url)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
def test_submit_valid_data(self):
self.client.force_login(self.sic_manager_user)
diff --git a/tests/views/common/form_tabs/test_person.py b/tests/views/common/form_tabs/test_person.py
index fe9c6e382..da685844a 100644
--- a/tests/views/common/form_tabs/test_person.py
+++ b/tests/views/common/form_tabs/test_person.py
@@ -53,7 +53,11 @@
AdmissionEducationalValuatedExperiencesFactory,
)
from admission.tests.factories.general_education import GeneralEducationAdmissionFactory
-from admission.tests.factories.roles import SicManagementRoleFactory, ProgramManagerRoleFactory
+from admission.tests.factories.roles import (
+ SicManagementRoleFactory,
+ ProgramManagerRoleFactory,
+ CentralManagerRoleFactory,
+)
from base.forms.utils import FIELD_REQUIRED_MESSAGE
from base.models.enums.civil_state import CivilState
from base.models.enums.person_address_type import PersonAddressType
@@ -125,7 +129,6 @@ def setUpTestData(cls):
cls.continuing_admission: ContinuingEducationAdmission = ContinuingEducationAdmissionFactory(
training__management_entity=first_doctoral_commission,
training__academic_year=academic_years[0],
- candidate=cls.general_admission.candidate,
status=ChoixStatutPropositionContinue.CONFIRMEE.name,
)
@@ -141,7 +144,6 @@ def setUpTestData(cls):
cls.doctorate_admission: DoctorateAdmission = DoctorateAdmissionFactory(
training__management_entity=first_doctoral_commission,
training__academic_year=academic_years[0],
- candidate=cls.general_admission.candidate,
status=ChoixStatutPropositionDoctorale.CONFIRMEE.name,
)
@@ -838,6 +840,35 @@ def test_continuing_person_form_on_get_sic_manager(self):
self.assertEqual(response.status_code, 200)
+ continuing_admission = ContinuingEducationAdmissionFactory(
+ candidate=self.continuing_admission.candidate,
+ status=ChoixStatutPropositionContinue.CONFIRMEE.name,
+ )
+
+ response = self.client.get(self.continuing_url)
+
+ self.assertEqual(response.status_code, 200)
+
+ doctorate_admission = DoctorateAdmissionFactory(
+ candidate=self.continuing_admission.candidate,
+ status=ChoixStatutPropositionDoctorale.CONFIRMEE.name,
+ )
+
+ response = self.client.get(self.continuing_url)
+
+ self.assertEqual(response.status_code, 403)
+
+ doctorate_admission.delete()
+
+ general_admission = GeneralEducationAdmissionFactory(
+ candidate=self.continuing_admission.candidate,
+ status=ChoixStatutPropositionDoctorale.CONFIRMEE.name,
+ )
+
+ response = self.client.get(self.continuing_url)
+
+ self.assertEqual(response.status_code, 403)
+
def test_continuing_person_form_on_get_program_manager(self):
self.client.force_login(user=self.continuing_program_manager_user)
@@ -845,12 +876,34 @@ def test_continuing_person_form_on_get_program_manager(self):
self.assertEqual(response.status_code, 200)
- def test_continuing_person_form_on_post_program_manager_is_allowed(self):
- self.client.force_login(user=self.continuing_program_manager_user)
+ continuing_admission = ContinuingEducationAdmissionFactory(
+ candidate=self.continuing_admission.candidate,
+ status=ChoixStatutPropositionContinue.CONFIRMEE.name,
+ )
- response = self.client.post(self.continuing_url, self.form_data)
+ response = self.client.get(self.continuing_url)
- self.assertEqual(response.status_code, 302)
+ self.assertEqual(response.status_code, 200)
+
+ doctorate_admission = DoctorateAdmissionFactory(
+ candidate=self.continuing_admission.candidate,
+ status=ChoixStatutPropositionDoctorale.CONFIRMEE.name,
+ )
+
+ response = self.client.get(self.continuing_url)
+
+ self.assertEqual(response.status_code, 403)
+
+ doctorate_admission.delete()
+
+ general_admission = GeneralEducationAdmissionFactory(
+ candidate=self.continuing_admission.candidate,
+ status=ChoixStatutPropositionDoctorale.CONFIRMEE.name,
+ )
+
+ response = self.client.get(self.continuing_url)
+
+ self.assertEqual(response.status_code, 403)
def test_continuing_person_form_on_get(self):
self.client.force_login(user=self.sic_manager_user)
@@ -901,14 +954,47 @@ def test_continuing_person_form_post_with_invalid_data(self):
response = self.client.post(self.continuing_url, {})
self.assertEqual(response.status_code, 200)
+ def test_doctorate_person_form_on_get_program_manager(self):
+ self.client.force_login(user=self.doctorate_program_manager_user)
+
+ response = self.client.get(self.doctorate_url)
+
+ self.assertEqual(response.status_code, 403)
+
def test_doctorate_person_form_on_get_sic_manager(self):
self.client.force_login(user=self.sic_manager_user)
- # No residential address
response = self.client.get(self.doctorate_url)
self.assertEqual(response.status_code, 200)
+ ContinuingEducationAdmissionFactory(
+ candidate=self.doctorate_admission.candidate,
+ status=ChoixStatutPropositionContinue.CONFIRMEE.name,
+ )
+
+ response = self.client.get(self.doctorate_url)
+
+ self.assertEqual(response.status_code, 200)
+
+ DoctorateAdmissionFactory(
+ candidate=self.doctorate_admission.candidate,
+ status=ChoixStatutPropositionDoctorale.CONFIRMEE.name,
+ )
+
+ response = self.client.get(self.doctorate_url)
+
+ self.assertEqual(response.status_code, 200)
+
+ GeneralEducationAdmissionFactory(
+ candidate=self.doctorate_admission.candidate,
+ status=ChoixStatutPropositionDoctorale.CONFIRMEE.name,
+ )
+
+ response = self.client.get(self.doctorate_url)
+
+ self.assertEqual(response.status_code, 403)
+
def test_doctorate_person_form_on_get(self):
self.client.force_login(user=self.sic_manager_user)
diff --git a/tests/views/continuing_education/checklist/test_decision.py b/tests/views/continuing_education/checklist/test_decision.py
index 3e1e8f118..0d63d276c 100644
--- a/tests/views/continuing_education/checklist/test_decision.py
+++ b/tests/views/continuing_education/checklist/test_decision.py
@@ -65,7 +65,7 @@
from base.models.enums.education_group_types import TrainingType
from base.tests.factories.academic_year import AcademicYearFactory
from base.tests.factories.entity import EntityWithVersionFactory
-from education_group.auth.scope import Scope
+from admission.auth.scope import Scope
class ChecklistViewTestCase(TestCase):
diff --git a/tests/views/continuing_education/test_list.py b/tests/views/continuing_education/test_list.py
index 646a34f2c..c38915a8b 100644
--- a/tests/views/continuing_education/test_list.py
+++ b/tests/views/continuing_education/test_list.py
@@ -58,7 +58,7 @@
from base.tests.factories.person import PersonFactory
from base.tests.factories.student import StudentFactory
from base.tests.factories.user import UserFactory
-from education_group.auth.scope import Scope
+from admission.auth.scope import Scope
@freezegun.freeze_time('2023-01-01')
@@ -235,7 +235,7 @@ def test_list_central_manager_scoped_iufc_not_entity(self):
self.assertEqual(len(response.context['object_list']), 0)
def test_list_central_manager_scoped_all_not_entity(self):
- manager = CentralManagerRoleFactory(scopes=[Scope.ALL.name])
+ manager = CentralManagerRoleFactory(scopes=[Scope.GENERAL.name])
self.client.force_login(user=manager.person.user)
response = self._do_request(allowed_sql_surplus=1)
@@ -249,7 +249,7 @@ def test_list_central_manager_scoped_doctorate_not_entity(self):
self.assertEqual(response.status_code, 403)
def test_list_central_manager_scoped_all_on_entity(self):
- manager = CentralManagerRoleFactory(scopes=[Scope.ALL.name], entity=self.first_entity)
+ manager = CentralManagerRoleFactory(scopes=[Scope.GENERAL.name], entity=self.first_entity)
self.client.force_login(user=manager.person.user)
response = self._do_request(allowed_sql_surplus=1)
diff --git a/tests/views/test_list.py b/tests/views/test_list.py
index d4bd5e382..0fe97c2b0 100644
--- a/tests/views/test_list.py
+++ b/tests/views/test_list.py
@@ -73,7 +73,7 @@
from base.tests.factories.person import PersonFactory
from base.tests.factories.student import StudentFactory
from base.tests.factories.user import UserFactory
-from education_group.auth.scope import Scope
+from admission.auth.scope import Scope
from program_management.models.education_group_version import EducationGroupVersion
from reference.tests.factories.country import CountryFactory
@@ -281,7 +281,7 @@ def test_list_initialization_just_after_academic_year_change(self):
self.assertEqual(form['annee_academique'].initial, 2024)
def test_list_central_manager_scoped_not_entity(self):
- manager = CentralManagerRoleFactory(scopes=[Scope.ALL.name])
+ manager = CentralManagerRoleFactory(scopes=[Scope.GENERAL.name])
self.client.force_login(user=manager.person.user)
response = self._do_request(allowed_sql_surplus=1)
@@ -289,7 +289,7 @@ def test_list_central_manager_scoped_not_entity(self):
self.assertEqual(len(response.context['object_list']), 0)
def test_list_central_manager_scoped_on_entity(self):
- manager = CentralManagerRoleFactory(scopes=[Scope.ALL.name], entity=self.first_entity)
+ manager = CentralManagerRoleFactory(scopes=[Scope.GENERAL.name], entity=self.first_entity)
self.client.force_login(user=manager.person.user)
response = self._do_request(allowed_sql_surplus=1)
diff --git a/views/common/form_tabs/curriculum_global.py b/views/common/form_tabs/curriculum_global.py
index 5941a12e8..b5523e683 100644
--- a/views/common/form_tabs/curriculum_global.py
+++ b/views/common/form_tabs/curriculum_global.py
@@ -53,7 +53,7 @@
class CurriculumGlobalFormView(AdmissionFormMixin, CurriculumGlobalCommonViewMixin, FormView):
urlpatterns = {'curriculum': 'curriculum'}
template_name = 'admission/forms/curriculum.html'
- permission_required = 'admission.change_admission_curriculum'
+ permission_required = 'admission.change_admission_global_curriculum'
form_class = GlobalCurriculumForm
extra_context = {
'force_form': True,