Skip to content

Commit

Permalink
[OS-664] Doctorate > create an admission based on a pre admission
Browse files Browse the repository at this point in the history
  • Loading branch information
jcougnaud committed Dec 5, 2024
1 parent 64636f2 commit 8dc705a
Show file tree
Hide file tree
Showing 34 changed files with 1,226 additions and 220 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,6 @@ repos:
- id: check_app_messages
name: Check messages
language: system
entry: bash -c '(cd .. && ./manage.py check_app_messages parcours_doctoral)'
entry: bash -c '(cd .. && ./manage.py check_app_messages admission)'
always_run: true
pass_filenames: false
13 changes: 6 additions & 7 deletions admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ class DoctorateAdmissionAdmin(AdmissionAdminMixin):
'thesis_language',
'prerequisite_courses',
'refusal_reasons',
'related_pre_admission',
]
list_display = ['reference', 'candidate_fmt', 'doctorate', 'type', 'status', 'view_on_portal']
list_filter = ['status', 'type']
Expand Down Expand Up @@ -667,15 +668,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_established_on__isnull=True
generaleducationadmission__financability_established_on__isnull=True,
)
| Q(
checklist__current__financabilite__status='GEST_REUSSITE',
generaleducationadmission__financability_established_by_id__isnull=True
generaleducationadmission__financability_established_by_id__isnull=True,
),
generaleducationadmission__isnull=False,
then=Value(False),
Expand Down Expand Up @@ -758,13 +759,10 @@ def get_queryset(self, request):
)
)

@admin.display(
ordering='_noma_sent_to_digit'
)
@admin.display(ordering='_noma_sent_to_digit')
def noma_sent_to_digit(self, obj):
return obj._noma_sent_to_digit


@admin.action(description='Injecter la demande dans EPC')
def injecter_dans_epc(self, request, queryset):
for demande in queryset.exclude(
Expand Down Expand Up @@ -922,6 +920,7 @@ def has_add_permission(self, request) -> bool:
def has_change_permission(self, request, obj=None) -> bool:
return False


# ##############################################################################
# Roles

Expand Down
2 changes: 1 addition & 1 deletion api/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from backoffice.settings.rest_framework.fields import ActionLinksField
from base.models.utils.utils import ChoiceEnum

ADMISSION_SDK_VERSION = "1.0.110"
ADMISSION_SDK_VERSION = "1.0.111"


class AdmissionSchemaGenerator(SchemaGenerator):
Expand Down
61 changes: 59 additions & 2 deletions api/serializers/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
RelatedInstituteField,
)
from admission.api.serializers.mixins import IncludedFieldsMixin
from admission.models import DoctorateAdmission, GeneralEducationAdmission
from admission.ddd.admission.doctorat.preparation.commands import CompleterPropositionCommand, InitierPropositionCommand
from admission.ddd.admission.doctorat.preparation.domain.model.enums import (
ChoixCommissionProximiteCDEouCLSM,
Expand All @@ -49,11 +48,13 @@
DoctoratFormationDTO,
PropositionDTO as DoctoratPropositionDTO,
)
from admission.ddd.admission.dtos.campus import CampusDTO
from admission.ddd.admission.dtos.formation import FormationDTO
from admission.ddd.admission.formation_continue.domain.model.enums import ChoixStatutPropositionContinue
from admission.ddd.admission.formation_continue.dtos import PropositionDTO as FormationContinuePropositionDTO
from admission.ddd.admission.formation_generale.domain.model.enums import ChoixStatutPropositionGenerale
from admission.ddd.admission.formation_generale.dtos import PropositionDTO as FormationGeneralePropositionDTO
from admission.models import DoctorateAdmission, GeneralEducationAdmission
from backoffice.settings.rest_framework.fields import ActionLinksField
from base.utils.serializers import DTOSerializer

Expand All @@ -76,6 +77,7 @@
"ContinuingEducationPropositionDTOSerializer",
"PROPOSITION_ERROR_SCHEMA",
"GeneralEducationPropositionIdentityWithStatusSerializer",
"DoctoratePreAdmissionSearchDTOSerializer",
]

from reference.api.serializers.language import RelatedLanguageField
Expand Down Expand Up @@ -245,6 +247,7 @@ class Meta:
source = DoctoratPropositionDTO
fields = [
'uuid',
'pre_admission_associee',
'reference',
'type_admission',
'doctorat',
Expand Down Expand Up @@ -465,6 +468,7 @@ class Meta:
source = DoctoratPropositionDTO
fields = [
'uuid',
'pre_admission_associee',
'type_admission',
'reference',
'justification',
Expand Down Expand Up @@ -710,7 +714,7 @@ class Meta:
)


class CompleterPropositionCommandSerializer(InitierPropositionCommandSerializer):
class CompleterPropositionCommandSerializer(DTOSerializer):
documents_projet = serializers.ListField(child=serializers.CharField())
graphe_gantt = serializers.ListField(child=serializers.CharField())
proposition_programme_doctoral = serializers.ListField(child=serializers.CharField())
Expand All @@ -722,6 +726,12 @@ class CompleterPropositionCommandSerializer(InitierPropositionCommandSerializer)
)
langue_redaction_these = RelatedLanguageField(required=False)
institut_these = RelatedInstituteField(required=False)
commission_proximite = serializers.ChoiceField(
choices=ChoixCommissionProximiteCDEouCLSM.choices()
+ ChoixCommissionProximiteCDSS.choices()
+ ChoixSousDomaineSciences.choices(),
allow_blank=True,
)
type_admission = None
matricule_auteur = None

Expand All @@ -732,3 +742,50 @@ class Meta:
class SectorDTOSerializer(serializers.Serializer):
sigle = serializers.ReadOnlyField()
intitule = serializers.ReadOnlyField()


class CampusDTOSerializer(IncludedFieldsMixin, DTOSerializer):
class Meta:
source = CampusDTO
fields = [
'uuid',
'nom',
]


class DoctoratSearchDTOSerializer(IncludedFieldsMixin, DTOSerializer):
campus = CampusDTOSerializer()
date_debut = None
intitule_fr = None
intitule_en = None
credits = None

class Meta:
source = DoctoratFormationDTO
fields = [
'sigle',
'code',
'annee',
'intitule',
'sigle_entite_gestion',
'campus',
]


class DoctoratePreAdmissionSearchDTOSerializer(IncludedFieldsMixin, DTOSerializer):
doctorat = DoctoratSearchDTOSerializer()
# This is to prevent schema from breaking on JSONField
erreurs = None
reponses_questions_specifiques = None
elements_confirmation = None
documents_demandes = None

class Meta:
source = DoctoratPropositionDTO
fields = [
'uuid',
'reference',
'doctorat',
'code_secteur_formation',
'intitule_secteur_formation',
]
1 change: 1 addition & 0 deletions api/url_v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ def path(pattern, view, name=None):
path('curriculum', views.PersonCurriculumView),
# Admission-related
path('propositions/doctorate', views.DoctorateTrainingChoiceAPIView),
path('propositions/doctorate/pre-admission-list', views.DoctoratePreAdmissionList),
path('propositions/doctorate/<uuid:uuid>', views.DoctoratePropositionView),
_path('propositions/doctorate/<uuid:uuid>/', include(person_tabs)),
path('propositions/doctorate/<uuid:uuid>/project', views.ProjectViewSet),
Expand Down
40 changes: 39 additions & 1 deletion api/views/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,23 @@
from admission.api import serializers
from admission.api.permissions import IsListingOrHasNotAlreadyCreatedPermission, IsSupervisionMember
from admission.api.schema import ResponseSpecificSchema
from admission.models import DoctorateAdmission
from admission.ddd.admission.doctorat.preparation.commands import (
CompleterPropositionCommand,
ListerPropositionsCandidatQuery as ListerPropositionsDoctoralesCandidatQuery,
ListerPropositionsSuperviseesQuery,
)
from admission.ddd.admission.doctorat.preparation.domain.model.enums import (
ChoixTypeAdmission,
ChoixStatutPropositionDoctorale,
)
from admission.ddd.admission.doctorat.preparation.domain.validator.exceptions import JustificationRequiseException
from admission.ddd.admission.formation_continue.commands import (
ListerPropositionsCandidatQuery as ListerPropositionsFormationContinueCandidatQuery,
)
from admission.ddd.admission.formation_generale.commands import (
ListerPropositionsCandidatQuery as ListerPropositionsFormationGeneraleCandidatQuery,
)
from admission.models import DoctorateAdmission
from admission.utils import get_cached_admission_perm_obj
from backoffice.settings.rest_framework.common_views import DisplayExceptionsByFieldNameAPIMixin
from infrastructure.messages_bus import message_bus_instance
Expand All @@ -56,6 +60,7 @@
"PropositionListView",
"SupervisedPropositionListView",
"ProjectViewSet",
"DoctoratePreAdmissionList",
]


Expand Down Expand Up @@ -229,3 +234,36 @@ def put(self, request, *args, **kwargs):
self.get_permission_object().update_detailed_status(request.user.person)
serializer = serializers.PropositionIdentityDTOSerializer(instance=result)
return Response(serializer.data, status=status.HTTP_200_OK)


class DoctoratePreAdmissionListSchema(ResponseSpecificSchema):
operation_id_base = '_doctorate_pre_admission'
serializer_mapping = {
'GET': serializers.DoctoratePreAdmissionSearchDTOSerializer,
}


class DoctoratePreAdmissionList(APIPermissionRequiredMixin, DisplayExceptionsByFieldNameAPIMixin, ListAPIView):
name = "doctorate_pre_admission_list"
schema = DoctoratePreAdmissionListSchema()
pagination_class = None
filter_backends = []
permission_classes = [IsListingOrHasNotAlreadyCreatedPermission]

def list(self, request, **kwargs):
"""List the propositions of the logged in user"""
doctorate_list = message_bus_instance.invoke(
ListerPropositionsDoctoralesCandidatQuery(
matricule_candidat=request.user.person.global_id,
type_admission=ChoixTypeAdmission.PRE_ADMISSION.name,
statut=ChoixStatutPropositionDoctorale.INSCRIPTION_AUTORISEE.name,
est_pre_admission_d_une_admission_en_cours=False,
),
)

serializer = serializers.DoctoratePreAdmissionSearchDTOSerializer(
instance=doctorate_list,
many=True,
)

return Response(serializer.data)
7 changes: 7 additions & 0 deletions auth/predicates/doctorate.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def signing_in_progress(self, user: User, obj: DoctorateAdmission):
def is_invited_to_complete(self, user: User, obj: DoctorateAdmission):
return obj.status in STATUTS_PROPOSITION_DOCTORALE_SOUMISE_POUR_CANDIDAT


@predicate(bind=True)
@predicate_failed_msg(message=_("The proposition has already been confirmed or is cancelled"))
def unconfirmed_proposition(self, user: User, obj: DoctorateAdmission):
Expand Down Expand Up @@ -214,3 +215,9 @@ def can_send_to_fac_faculty_decision(self, user: User, obj: DoctorateAdmission):
isinstance(obj, DoctorateAdmission)
and obj.status in STATUTS_PROPOSITION_DOCTORALE_ENVOYABLE_EN_CDD_POUR_DECISION
)


@predicate(bind=True)
@predicate_failed_msg(message=_("The admission must not follow a pre-admission"))
def must_not_follow_a_pre_admission(self, user: User, obj: DoctorateAdmission):
return not bool(obj.related_pre_admission_id)
4 changes: 3 additions & 1 deletion auth/roles/candidate.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@
'change_admission_languages': common.is_admission_request_author & doctorate.unconfirmed_proposition,
'change_admission_accounting': common.is_admission_request_author & doctorate.unconfirmed_proposition,
# Project tabs and supervision group edition are accessible as long as signing has not begun
'change_admission_training_choice': common.is_admission_request_author & doctorate.in_progress,
'change_admission_training_choice': common.is_admission_request_author
& doctorate.in_progress
& doctorate.must_not_follow_a_pre_admission,
'change_admission_project': common.is_admission_request_author & doctorate.in_progress,
'change_admission_cotutelle': common.is_admission_request_author & doctorate.in_progress & doctorate.is_admission,
'change_admission_supervision': common.is_admission_request_author & doctorate.in_progress,
Expand Down
46 changes: 41 additions & 5 deletions ddd/admission/doctorat/preparation/builder/proposition_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
# see http://www.gnu.org/licenses/.
#
##############################################################################
from abc import abstractmethod
from typing import Optional, Union

from admission.ddd.admission.doctorat.preparation.builder.proposition_identity_builder import PropositionIdentityBuilder
Expand All @@ -39,15 +40,16 @@
from admission.ddd.admission.doctorat.preparation.domain.model.proposition import (
Proposition,
)
from admission.ddd.admission.doctorat.preparation.domain.model.proposition import PropositionIdentity
from admission.ddd.admission.doctorat.preparation.domain.service.i_doctorat import IDoctoratTranslator
from admission.ddd.admission.doctorat.preparation.domain.validator.validator_by_business_action import (
InitierPropositionValidatorList,
)
from admission.ddd.admission.doctorat.preparation.repository.i_proposition import IPropositionRepository
from admission.ddd.admission.domain.model.formation import FormationIdentity
from osis_common.ddd import interface


class PropositionBuilder(interface.RootEntityBuilder):
class IPropositionBuilder(interface.RootEntityBuilder):
@classmethod
def build_from_repository_dto(cls, dto_object: 'interface.DTO') -> 'Proposition':
raise NotImplementedError
Expand All @@ -60,9 +62,30 @@ def build_from_command(cls, cmd: 'InitierPropositionCommand'): # type: ignore[o
def initier_proposition(
cls,
cmd: 'InitierPropositionCommand',
doctorat: 'DoctoratFormation',
doctorat_translator: 'IDoctoratTranslator',
proposition_repository: 'IPropositionRepository',
) -> 'Proposition':
) -> 'PropositionIdentity ':
if cmd.pre_admission_associee:
return cls.initier_nouvelle_proposition_attachee_a_pre_admission(
cmd,
doctorat_translator,
proposition_repository,
)
else:
return cls.initier_nouvelle_proposition_non_attachee_a_pre_admission(
cmd,
doctorat_translator,
proposition_repository,
)

@classmethod
def initier_nouvelle_proposition_non_attachee_a_pre_admission(
cls,
cmd: 'InitierPropositionCommand',
doctorat_translator: 'IDoctoratTranslator',
proposition_repository: 'IPropositionRepository',
) -> 'PropositionIdentity':
doctorat = doctorat_translator.get(cmd.sigle_formation, cmd.annee_formation)
InitierPropositionValidatorList(
type_admission=cmd.type_admission,
justification=cmd.justification,
Expand All @@ -79,7 +102,7 @@ def initier_proposition(
elif cmd.commission_proximite and cmd.commission_proximite in ChoixSousDomaineSciences.get_names():
commission_proximite = ChoixSousDomaineSciences[cmd.commission_proximite]
reference = proposition_repository.recuperer_reference_suivante()
return Proposition(
proposition = Proposition(
entity_id=PropositionIdentityBuilder.build(),
reference=reference,
statut=ChoixStatutPropositionDoctorale.EN_BROUILLON,
Expand All @@ -91,3 +114,16 @@ def initier_proposition(
projet=projet_non_rempli,
auteur_derniere_modification=cmd.matricule_candidat,
)
proposition_repository.save(proposition)

return proposition.entity_id

@classmethod
@abstractmethod
def initier_nouvelle_proposition_attachee_a_pre_admission(
cls,
cmd: 'InitierPropositionCommand',
doctorat_translator: 'IDoctoratTranslator',
proposition_repository: 'IPropositionRepository',
) -> 'PropositionIdentity':
raise NotImplementedError
Loading

0 comments on commit 8dc705a

Please sign in to comment.