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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
# ##############################################################################
from drf_spectacular.generators import SchemaGenerator

ADMISSION_SDK_VERSION = "1.1.16"
ADMISSION_SDK_VERSION = "1.1.17.dev1651"


class AdmissionSchemaGenerator(SchemaGenerator):
Expand Down
37 changes: 13 additions & 24 deletions api/serializers/pool_questions.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@
from admission.ddd.admission.shared_kernel.domain.service.i_calendrier_inscription import (
ICalendrierInscription,
)
from admission.ddd.admission.shared_kernel.domain.validator.exceptions import (
ResidenceAuSensDuDecretNonDisponiblePourInscriptionException,
)
from admission.models import GeneralEducationAdmission


Expand All @@ -42,26 +39,10 @@ class PoolQuestionsSerializer(serializers.ModelSerializer):
reorientation_pool_academic_year = serializers.IntegerField(read_only=True, allow_null=True)
modification_pool_end_date = serializers.DateTimeField(read_only=True, allow_null=True)
modification_pool_academic_year = serializers.IntegerField(read_only=True, allow_null=True)
forbid_enrolment_limited_course_for_non_resident = serializers.SerializerMethodField()

@extend_schema_field(OpenApiTypes.STR)
def get_forbid_enrolment_limited_course_for_non_resident(self, obj):
return ResidenceAuSensDuDecretNonDisponiblePourInscriptionException.get_message(
nom_formation_fr=obj.training.title,
nom_formation_en=obj.training.title_english,
)

def get_field_names(self, *args, **kwargs):
field_names = super().get_field_names(*args, **kwargs)

# Add or remove the forbid enrolment limited course for non resident message depending of what is desired
if 'forbid_enrolment_limited_course_for_non_resident' in field_names:
if not ICalendrierInscription.INTERDIRE_INSCRIPTION_ETUDES_CONTINGENTES_POUR_NON_RESIDENT:
field_names.remove('forbid_enrolment_limited_course_for_non_resident')
elif ICalendrierInscription.INTERDIRE_INSCRIPTION_ETUDES_CONTINGENTES_POUR_NON_RESIDENT:
field_names.append('forbid_enrolment_limited_course_for_non_resident')

return field_names
non_resident_quota_pool_start_date = serializers.DateField(read_only=True, allow_null=True)
non_resident_quota_pool_start_time = serializers.TimeField(read_only=True, allow_null=True)
non_resident_quota_pool_end_date = serializers.DateField(read_only=True, allow_null=True)
non_resident_quota_pool_end_time = serializers.TimeField(read_only=True, allow_null=True)

class Meta:
model = GeneralEducationAdmission
Expand All @@ -74,9 +55,17 @@ class Meta:
'registration_change_form',
'regular_registration_proof_for_registration_change',
'is_non_resident',
'residence_certificate',
'residence_student_form',
'non_resident_file',
'non_resident_with_second_year_enrolment',
'non_resident_with_second_year_enrolment_form',
'reorientation_pool_end_date',
'reorientation_pool_academic_year',
'modification_pool_end_date',
'modification_pool_academic_year',
'forbid_enrolment_limited_course_for_non_resident',
'non_resident_quota_pool_start_date',
'non_resident_quota_pool_start_time',
'non_resident_quota_pool_end_date',
'non_resident_quota_pool_end_time',
]
2 changes: 2 additions & 0 deletions api/serializers/submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class PropositionErrorsSerializer(serializers.Serializer):
errors = PropositionErrorSerializer(many=True)
pool_start_date = serializers.DateField(allow_null=True, required=False)
pool_end_date = serializers.DateField(allow_null=True, required=False)
pool_start_time = serializers.TimeField(allow_null=True, required=False)
pool_end_time = serializers.TimeField(allow_null=True, required=False)
access_conditions_url = serializers.CharField(allow_null=True, required=False)
elements_confirmation = ElementConfirmationSerializer(many=True, allow_null=True, required=False)

Expand Down
44 changes: 41 additions & 3 deletions api/views/pool_questions.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,17 @@
from rest_framework.generics import RetrieveAPIView
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.views import APIView

from admission.api.serializers.pool_questions import PoolQuestionsSerializer
from admission.calendar.admission_calendar import SIGLES_WITH_QUOTA
from admission.ddd import CODE_BACHELIER_VETERINAIRE
from admission.ddd.admission.formation_generale.commands import VerifierPropositionQuery
from admission.ddd.admission.shared_kernel.domain.validator.exceptions import (
ModificationInscriptionExterneNonConfirmeeException,
ReorientationInscriptionExterneNonConfirmeeException,
ResidenceAuSensDuDecretNonRenseigneeException,
)
from admission.ddd.admission.formation_generale.commands import VerifierPropositionQuery
from admission.models import GeneralEducationAdmission
from admission.utils import (
gather_business_exceptions,
Expand Down Expand Up @@ -129,8 +131,39 @@ def get(self, request, *args, **kwargs):
field_questions_to_display.append(academic_year_field_name)

# Build relevant field list
if self.get_permission_object().training.acronym in SIGLES_WITH_QUOTA:
field_questions_to_display.append('is_non_resident')
if admission.training.acronym in SIGLES_WITH_QUOTA:
# Get dates for non-resident
calendar = (
AcademicCalendar.objects.filter(
end_date__gte=datetime.date.today(),
reference=AcademicCalendarTypes.ADMISSION_POOL_NON_RESIDENT_QUOTA.name,
)
.order_by('end_date')
.values('start_date', 'start_time', 'end_date', 'end_time')
.first()
)

setattr(admission, 'non_resident_quota_pool_start_date', calendar['start_date'])
setattr(admission, 'non_resident_quota_pool_start_time', calendar['start_time'])
setattr(admission, 'non_resident_quota_pool_end_date', calendar['end_date'])
setattr(admission, 'non_resident_quota_pool_end_time', calendar['end_time'])

field_questions_to_display += [
'is_non_resident',
'residence_certificate',
'residence_student_form',
'non_resident_file',
'non_resident_quota_pool_start_date',
'non_resident_quota_pool_start_time',
'non_resident_quota_pool_end_date',
'non_resident_quota_pool_end_time',
]
if admission.training.acronym not in CODE_BACHELIER_VETERINAIRE:
field_questions_to_display += [
'non_resident_with_second_year_enrolment',
'non_resident_with_second_year_enrolment_form',
]

if admission.reorientation_pool_end_date is not None:
field_questions_to_display += [
'is_belgian_bachelor',
Expand Down Expand Up @@ -161,6 +194,11 @@ def put(self, request, *args, **kwargs):
data = {
# Reset to default if not defined
'is_non_resident': None,
'residence_certificate': [],
'residence_student_form': [],
'non_resident_file': [],
'non_resident_with_second_year_enrolment': None,
'non_resident_with_second_year_enrolment_form': [],
'is_belgian_bachelor': None,
'is_external_modification': None,
'is_external_reorientation': None,
Expand Down
13 changes: 12 additions & 1 deletion api/views/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
from admission.ddd.admission.formation_generale.commands import (
ListerPropositionsCandidatQuery as ListerPropositionsFormationGeneraleCandidatQuery,
)
from admission.models import DoctorateAdmission
from admission.models import DoctorateAdmission, GeneralEducationAdmission
from admission.utils import get_cached_admission_perm_obj
from backoffice.settings.rest_framework.common_views import (
DisplayExceptionsByFieldNameAPIMixin,
Expand Down Expand Up @@ -127,6 +127,17 @@ def list(self, request, **kwargs):
]
)

# Set _perm_obj to handle permissions in action_links
queryset = (
GeneralEducationAdmission.objects.select_related(
'candidate',
)
.filter(uuid__in=[p.uuid for p in general_education_list])
.in_bulk(field_name='uuid')
)
for proposition in general_education_list:
proposition._perm_obj = queryset[proposition.uuid]

serializer = serializers.PropositionSearchSerializer(
instance={
"doctorate_propositions": doctorate_list,
Expand Down
22 changes: 22 additions & 0 deletions api/views/proposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
# see http://www.gnu.org/licenses/.
#
# ##############################################################################
from typing import Optional

from django.db.models import Q
from django.utils import timezone
from drf_spectacular.utils import extend_schema
from rest_framework import mixins, status
from rest_framework.generics import GenericAPIView, RetrieveAPIView
Expand All @@ -36,11 +40,15 @@
from admission.ddd.admission.formation_generale import (
commands as general_education_commands,
)
from admission.ddd.admission.shared_kernel.domain.validator.exceptions import \
PoolNonResidentContingenteNonOuvertException
from admission.utils import (
get_cached_admission_perm_obj,
get_cached_continuing_education_admission_perm_obj,
get_cached_general_education_admission_perm_obj,
)
from base.models.academic_calendar import AcademicCalendar
from base.models.enums.academic_calendar_type import AcademicCalendarTypes
from infrastructure.messages_bus import message_bus_instance
from osis_role.contrib.views import APIPermissionRequiredMixin

Expand All @@ -54,6 +62,20 @@ class GeneralPropositionView(APIPermissionRequiredMixin, RetrieveAPIView):
'DELETE': 'admission.delete_generaleducationadmission',
}

def check_method_permissions(self, user, method, obj=None) -> Optional[str]:
""" Check calendar is open for non-resident submitted proposition with quota """
error_message = super().check_method_permissions(user, method, obj)
if error_message or method != 'DELETE':
return error_message
now = timezone.now()
if not AcademicCalendar.objects.filter(
Q(start_date__lt=now) | Q(start_date=now, start_time__lte=now),
Q(end_date__gt=now) | Q(end_date=now, end_time__gt=now),
reference=AcademicCalendarTypes.ADMISSION_POOL_NON_RESIDENT_QUOTA.name,
).exists():
return PoolNonResidentContingenteNonOuvertException().message
return None

def get_permission_object(self):
return get_cached_general_education_admission_perm_obj(self.kwargs['uuid'])

Expand Down
10 changes: 6 additions & 4 deletions api/views/submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,6 @@
SoumettrePropositionCommand as SoumettrePropositionDoctoratCommand,
)
from admission.ddd.admission.doctorat.preparation.commands import VerifierProjetQuery
from admission.ddd.admission.shared_kernel.domain.validator.exceptions import (
ConditionsAccessNonRempliesException,
PoolNonResidentContingenteNonOuvertException,
)
from admission.ddd.admission.formation_continue.commands import (
RecupererElementsConfirmationQuery as RecupererElementsConfirmationContinueQuery,
)
Expand All @@ -57,6 +53,10 @@
from admission.ddd.admission.formation_generale.commands import (
SoumettrePropositionCommand as SoumettrePropositionGeneraleCommand,
)
from admission.ddd.admission.shared_kernel.domain.validator.exceptions import (
ConditionsAccessNonRempliesException,
PoolNonResidentContingenteNonOuvertException,
)
from admission.models import (
ContinuingEducationAdmission,
DoctorateAdmission,
Expand Down Expand Up @@ -243,6 +243,8 @@ def get(self, request, *args, **kwargs):
)
data['pool_start_date'] = period.start_date
data['pool_end_date'] = period.end_date
data['pool_start_time'] = period.start_time
data['pool_end_time'] = period.end_time

self.add_access_conditions_url(data)
if not data['errors']:
Expand Down
13 changes: 13 additions & 0 deletions auth/predicates/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from rules import predicate

from admission.auth.predicates import not_in_general_statuses_predicate_message
from admission.calendar.admission_calendar import SIGLES_WITH_QUOTA
from admission.models import GeneralEducationAdmission
from admission.ddd.admission.formation_generale.domain.model.enums import (
ChoixStatutPropositionGenerale,
Expand Down Expand Up @@ -98,6 +99,12 @@ def can_view_payment(self, user: User, obj: GeneralEducationAdmission):
}


@predicate(bind=True)
@predicate_failed_msg(_("The proposition must be confirmed to realize this action."))
def is_confirmed(self, user: User, obj: GeneralEducationAdmission):
return obj.status == ChoixStatutPropositionGenerale.CONFIRMEE.name


@predicate(bind=True)
@predicate_failed_msg(message=_("The proposition must be submitted to realize this action."))
def is_submitted(self, user: User, obj: GeneralEducationAdmission):
Expand Down Expand Up @@ -187,3 +194,9 @@ def is_general(self, user: User, obj: GeneralEducationAdmission):
from admission.constants import CONTEXT_GENERAL

return obj.admission_context == CONTEXT_GENERAL


@predicate(bind=True)
@predicate_failed_msg(_("The proposition must be for a contingent training for a non-resident candidat."))
def is_contingent_non_resident(self, user: User, obj: GeneralEducationAdmission):
return obj.training.acronym in SIGLES_WITH_QUOTA and obj.is_non_resident
4 changes: 3 additions & 1 deletion auth/roles/candidate.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@
'change_generaleducationadmission_accounting': common.is_admission_request_author & general.in_progress,
'change_generaleducationadmission_specific_question': common.is_admission_request_author & general.in_progress,
'change_generaleducationadmission': common.is_admission_request_author & general.in_progress,
'delete_generaleducationadmission': common.is_admission_request_author & general.in_progress,
'delete_generaleducationadmission': common.is_admission_request_author & (
general.in_progress | (general.is_confirmed & general.is_contingent_non_resident)
),
'submit_generaleducationadmission': common.is_admission_request_author & general.in_progress,
# A candidate can edit some tabs after the proposition has been submitted
'view_generaleducationadmission_documents': common.is_admission_request_author & general.is_invited_to_complete,
Expand Down
41 changes: 33 additions & 8 deletions calendar/admission_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from admission.ddd.admission.doctorat.preparation.domain.model.doctorat_formation import (
DoctoratFormation,
)
from admission.ddd import CODE_BACHELIER_VETERINAIRE
from admission.ddd.admission.doctorat.preparation.domain.validator.exceptions import (
AdresseDomicileLegalNonCompleteeException,
)
Expand Down Expand Up @@ -60,6 +61,7 @@
from base.models.enums.education_group_types import TrainingType

__all__ = [
"AdmissionNonResidentQuotaResultPublication",
"AdmissionPoolExternalEnrollmentChangeCalendar",
"AdmissionPoolExternalReorientationCalendar",
"AdmissionPoolHue5BelgiumResidencyCalendar",
Expand Down Expand Up @@ -94,7 +96,7 @@
ConditionAccess.ALTERNATIVE_ETUDES_SECONDAIRES,
]

SIGLES_WITH_QUOTA = ['KINE1BA', 'VETE1BA', 'LOGO1BA']
SIGLES_WITH_QUOTA = ['KINE1BA', CODE_BACHELIER_VETERINAIRE, 'LOGO1BA']

SECOND_CYCLE_TYPES = [
TrainingType.AGGREGATION.name,
Expand All @@ -111,6 +113,8 @@ def ensure_consistency_until_n_plus_6(
cutover_date: Date,
title: str,
end_date: Optional[Date] = DAY_BEFORE_NEXT,
start_time: Optional[datetime.time] = None,
end_time: Optional[datetime.time] = None,
):
current_academic_year = AcademicYear.objects.current()
academic_years = AcademicYear.objects.min_max_years(current_academic_year.year - 1, current_academic_year.year + 6)
Expand All @@ -129,6 +133,8 @@ def ensure_consistency_until_n_plus_6(
defaults={
'start_date': datetime.date(ac_year.year + cutover_date.annee, cutover_date.mois, cutover_date.jour),
'end_date': ac_end_date,
'start_time': start_time,
'end_time': end_time,
'title': title,
},
)
Expand Down Expand Up @@ -558,6 +564,8 @@ def ensure_consistency_until_n_plus_6(cls):
event_reference=cls.event_reference,
cutover_date=cls.cutover_date,
end_date=cls.end_date,
start_time=datetime.time(9, 0),
end_time=datetime.time(16, 0),
title="Admission - Contingenté non-résident (au sens du décret)",
)

Expand All @@ -575,6 +583,7 @@ class AdmissionPoolMedicineDentistryStandardPeriodCalendar(PoolCalendar):
cutover_date = Date(jour=6, mois=9, annee=0)
end_date = Date(jour=30, mois=9, annee=0)


@classmethod
def ensure_consistency_until_n_plus_6(cls):
ensure_consistency_until_n_plus_6(
Expand All @@ -586,14 +595,30 @@ def ensure_consistency_until_n_plus_6(cls):

@classmethod
def matches_criteria(
cls,
proposition: 'PropositionGenerale',
formation: Formation | DoctoratFormation,
**kwargs,
cls,
proposition: 'PropositionGenerale',
formation: Formation | DoctoratFormation,
**kwargs,
) -> bool:
"""Candidat souhaitant s'inscrire à un bachelier en médecine ou dentisterie"""
return (
isinstance(proposition, PropositionGenerale)
and formation.type == TrainingType.BACHELOR
and formation.est_formation_medecine_ou_dentisterie is True
isinstance(proposition, PropositionGenerale)
and formation.type == TrainingType.BACHELOR
and formation.est_formation_medecine_ou_dentisterie is True
)


class AdmissionNonResidentQuotaResultPublication(AcademicEventSessionCalendarHelper):
event_reference = AcademicCalendarTypes.ADMISSION_NON_RESIDENT_QUOTA_RESULT_PUBLICATION.name
cutover_date = Date(jour=2, mois=9, annee=0)
end_date = None

@classmethod
def ensure_consistency_until_n_plus_6(cls):
ensure_consistency_until_n_plus_6(
event_reference=cls.event_reference,
cutover_date=cls.cutover_date,
start_time=datetime.time(18, 0),
end_date=cls.end_date,
title="Admission: publication of the result of the random draw for the non-resident quota holder trainings",
)
Loading