Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Demandeur d’emploi : Ajout lieu de naissance dans le formulaire de création par un tiers [GEN-1944] #4732

Merged
merged 2 commits into from
Sep 26, 2024
Merged
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
72 changes: 63 additions & 9 deletions itou/asp/forms.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
from django import forms
from django.urls import reverse
from django.core.exceptions import ValidationError
from django.urls import reverse_lazy
from django.utils.functional import SimpleLazyObject

from itou.asp.models import Commune, Country
from itou.utils.validators import validate_birth_location
from itou.utils.widgets import RemoteAutocompleteSelect2Widget


def formfield_for_birth_place(*, with_birthdate_field, **kwargs):
with_birthdate_attr = {"data-select2-link-with-birthdate": "id_birthdate"} if with_birthdate_field else {}
france = Country.objects.get(code=Country._CODE_FRANCE)
return forms.ModelChoiceField(
class BirthPlaceAndCountryMixin(forms.ModelForm):
with_birthdate_field = None

birth_country = forms.ModelChoiceField(Country.objects, label="Pays de naissance", required=False)
birth_place = forms.ModelChoiceField(
queryset=Commune.objects,
label="Commune de naissance",
help_text=(
Expand All @@ -18,15 +22,65 @@ def formfield_for_birth_place(*, with_birthdate_field, **kwargs):
required=False,
widget=RemoteAutocompleteSelect2Widget(
attrs={
"data-ajax--url": reverse("autocomplete:communes"),
"data-ajax--url": reverse_lazy("autocomplete:communes"),
"data-ajax--cache": "true",
"data-ajax--type": "GET",
"data-minimum-input-length": 1,
"data-placeholder": "Nom de la commune",
"data-disable-target": "#id_birth_country",
"data-target-value": f"{france.pk}",
**with_birthdate_attr,
"data-target-value": SimpleLazyObject(lambda: f"{Country.france_id}"),
}
),
**kwargs,
)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

# with_birthdate_field indicates if JS is needed to link birthdate & birth_place fields
if self.with_birthdate_field is None:
self.with_birthdate_field = "birthdate" in self.fields

if self.with_birthdate_field:
self.fields["birth_place"].widget.attrs |= {"data-select2-link-with-birthdate": "id_birthdate"}

def get_birth_date(self):
return self.cleaned_data.get("birthdate", getattr(self, "birthdate", None))

def clean(self):
super().clean()

birth_place = self.cleaned_data.get("birth_place")
birth_country = self.cleaned_data.get("birth_country")
birth_date = self.get_birth_date()

if not birth_country:
# Selecting a birth place sets the birth country field to France and disables it.
# However, disabled fields are ignored by Django.
# That's also why we can't make it mandatory.
# See utils.js > toggleDisableAndSetValue
if birth_place:
self.cleaned_data["birth_country"] = Country.objects.get(code=Country.INSEE_CODE_FRANCE)
else:
# Display the error above the field instead of top of page.
self.add_error("birth_country", "Le pays de naissance est obligatoire.")

# Country coherence is done at model level (users.User)
# Here we must add coherence between birthdate and communes
# existing at this period (not a simple check of existence)
if birth_place and birth_date:
try:
self.cleaned_data["birth_place"] = Commune.objects.by_insee_code_and_period(
birth_place.code, birth_date
)
except Commune.DoesNotExist:
msg = (
f"Le code INSEE {birth_place.code} n'est pas référencé par l'ASP en date du {birth_date:%d/%m/%Y}"
)
self.add_error("birth_place", msg)

def _post_clean(self):
super()._post_clean()
try:
validate_birth_location(self.cleaned_data.get("birth_country"), self.cleaned_data.get("birth_place"))
except ValidationError as e:
self._update_errors(e)
11 changes: 9 additions & 2 deletions itou/asp/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from django.db import models
from django.db.models import Q
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.functional import cached_property, classproperty
from unidecode import unidecode

from itou.utils.models import DateRange
Expand Down Expand Up @@ -463,7 +463,8 @@ class Country(PrettyPrintMixin, models.Model):
Imported from ASP reference file: ref_grp_pays_v1, ref_insee_pays_v4.csv
"""

_CODE_FRANCE = "100"
INSEE_CODE_FRANCE = "100"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je ne vois pas pourquoi ce champ ne soit pas public ?

_ID_FRANCE = None

class Group(models.TextChoices):
FRANCE = "1", "France"
Expand All @@ -485,6 +486,12 @@ class Meta:
verbose_name_plural = "pays"
ordering = ["name"]

@classproperty
def france_id(cls):
if cls._ID_FRANCE is None:
cls._ID_FRANCE = Country.objects.get(code=Country.INSEE_CODE_FRANCE).pk
return cls._ID_FRANCE


class SiaeMeasure(models.TextChoices):
"""
Expand Down
10 changes: 10 additions & 0 deletions itou/templates/apply/includes/profile_infos.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ <h2>État civil</h2>
<li class="mb-3">
Date de naissance : <b>le {{ profile.birthdate }}</b>
</li>
{% if profile.birth_place %}
<li class="mb-3">
Commune de naissance : <b>{{ profile.birth_place.name }} {{ profile.birth_place.department_code }}</b>
</li>
{% endif %}
{% if profile.birth_country %}
<li class="mb-3">
Pays de naissance : <b>{{ profile.birth_country.name }}</b>
</li>
{% endif %}
</ul>
<hr class="my-4">

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
{% bootstrap_field form.lack_of_nir %}
{% bootstrap_field form.lack_of_nir_reason wrapper_class=form.lack_of_nir_reason.field.form_group_class %}
{% bootstrap_field form.birthdate %} {# TODO: Duet border-color button is not of the correct color #}
{% if form.birth_place %}
{% bootstrap_field form.birth_place %}
{% bootstrap_field form.birth_country %}
{% endif %}
</fieldset>

{% if confirmation_needed %}
Expand Down
3 changes: 2 additions & 1 deletion itou/users/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ def __init__(self, *args, instance=None, **kwargs):
initial = kwargs.pop("initial", {})
super().__init__(*args, instance=instance, initial=profile_initial | initial, **kwargs)
for field in self.PROFILE_FIELDS:
self.fields[field] = JobSeekerProfile._meta.get_field(field).formfield()
if field not in self.fields:
self.fields[field] = JobSeekerProfile._meta.get_field(field).formfield()

def save(self, commit=True):
user = super().save(commit=commit)
Expand Down
18 changes: 2 additions & 16 deletions itou/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
from itou.asp.models import (
AllocationDuration,
Commune,
Country,
EducationLevel,
LaneExtension,
LaneType,
Expand All @@ -35,7 +34,7 @@
from itou.utils.db import or_queries
from itou.utils.models import UniqueConstraintWithErrorCode
from itou.utils.templatetags.str_filters import mask_unless
from itou.utils.validators import validate_birthdate, validate_nir, validate_pole_emploi_id
from itou.utils.validators import validate_birth_location, validate_birthdate, validate_nir, validate_pole_emploi_id

from .enums import IdentityProvider, LackOfNIRReason, LackOfPoleEmploiId, Title, UserKind

Expand Down Expand Up @@ -772,17 +771,10 @@ class JobSeekerProfile(models.Model):
with Hexa norms (but compliant enough to be accepted by ASP backend).
"""

# Used for validation of birth country / place
INSEE_CODE_FRANCE = Country._CODE_FRANCE

ERROR_NOT_RESOURCELESS_IF_OETH_OR_RQTH = "La personne n'est pas considérée comme sans ressources si OETH ou RQTH"
ERROR_UNEMPLOYED_BUT_RQTH_OR_OETH = (
"La personne ne peut être considérée comme sans emploi si employée OETH ou RQTH"
)
ERROR_MUST_PROVIDE_BIRTH_PLACE = "Si le pays de naissance est la France, la commune de naissance est obligatoire"
ERROR_BIRTH_COMMUNE_WITH_FOREIGN_COUNTRY = (
"Il n'est pas possible de saisir une commune de naissance hors de France"
)

ERROR_HEXA_LANE_TYPE = "Le type de voie est obligatoire"
ERROR_HEXA_LANE_NAME = "Le nom de voie est obligatoire"
Expand Down Expand Up @@ -1121,13 +1113,7 @@ def _clean_birth_fields(self):
Mainly coherence checks for birth country / place.
Must be non blocking if these fields are not provided.
"""
# If birth country is France, then birth place must be provided
if self.birth_country and self.birth_country.code == self.INSEE_CODE_FRANCE and not self.birth_place:
raise ValidationError(self.ERROR_MUST_PROVIDE_BIRTH_PLACE)

# If birth country is not France, do not fill a birth place (no ref file)
if self.birth_country and self.birth_country.code != self.INSEE_CODE_FRANCE and self.birth_place:
raise ValidationError(self.ERROR_BIRTH_COMMUNE_WITH_FOREIGN_COUNTRY)
validate_birth_location(self.birth_country, self.birth_place)

# This used to be the `clean` method for the global model validation
# when using forms.
Expand Down
12 changes: 12 additions & 0 deletions itou/utils/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator
from django.utils import timezone

from itou.asp.models import Country


alphanumeric = RegexValidator(r"^[0-9a-zA-Z]*$", "Seuls les caractères alphanumériques sont autorisés.")

Expand Down Expand Up @@ -90,6 +92,16 @@ def validate_birthdate(birthdate):
raise ValidationError("La personne doit avoir plus de 16 ans.")


def validate_birth_location(birth_country, birth_place):
# If birth country is France, then birth place must be provided
if birth_country and birth_country.code == Country.INSEE_CODE_FRANCE and not birth_place:
raise ValidationError("Il n'est pas possible de saisir une commune de naissance hors de France.")

# If birth country is not France, do not fill a birth place (no ref file)
if birth_country and birth_country.code != Country.INSEE_CODE_FRANCE and birth_place:
raise ValidationError("Si le pays de naissance est la France, la commune de naissance est obligatoire.")


AF_NUMBER_PREFIX_REGEXPS = [
r"^ACI\d{2}[A-Z\d]\d{6}$",
r"^EI\d{2}[A-Z\d]\d{6}$",
Expand Down
58 changes: 8 additions & 50 deletions itou/www/apply/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from itou.approvals.models import Approval
from itou.asp import models as asp_models
from itou.asp.forms import formfield_for_birth_place
from itou.asp.forms import BirthPlaceAndCountryMixin
from itou.common_apps.address.departments import DEPARTMENTS
from itou.common_apps.address.forms import JobSeekerAddressForm
from itou.common_apps.nir.forms import JobSeekerNIRUpdateMixin
Expand Down Expand Up @@ -161,15 +161,17 @@ def clean(self):
JobSeekerProfile.clean_pole_emploi_fields(self.cleaned_data)


class CreateOrUpdateJobSeekerStep1Form(JobSeekerNIRUpdateMixin, JobSeekerProfileFieldsMixin, forms.ModelForm):
class CreateOrUpdateJobSeekerStep1Form(
JobSeekerNIRUpdateMixin, JobSeekerProfileFieldsMixin, BirthPlaceAndCountryMixin, forms.ModelForm
):
REQUIRED_FIELDS = [
"title",
"first_name",
"last_name",
"birthdate",
]

PROFILE_FIELDS = ["birthdate", "nir", "lack_of_nir_reason"]
PROFILE_FIELDS = ["birth_country", "birthdate", "birth_place", "nir", "lack_of_nir_reason"]

class Meta:
model = User
Expand Down Expand Up @@ -878,63 +880,19 @@ def clean(self):
JobSeekerProfile.clean_pole_emploi_fields(self.cleaned_data)


class CertifiedCriteriaInfoRequiredForm(forms.ModelForm):
class CertifiedCriteriaInfoRequiredForm(BirthPlaceAndCountryMixin, forms.ModelForm):
"""API Particulier required information.
https://github.com/etalab/siade_staging_data/blob/develop/payloads/api_particulier_v2_cnav_allocation_adulte_handicape/200_beneficiaire.yaml
"""

birth_country = forms.ModelChoiceField(
asp_models.Country.objects,
label="Pays de naissance",
required=False,
)

class Meta:
model = JobSeekerProfile
fields = ("birth_place", "birth_country")

def __init__(self, birthdate, *args, with_birthdate_field, **kwargs):
super().__init__(*args, **kwargs)
# with_birthdate_field indicates if JS is needed to link birthdate & birth_place fields
self.fields["birth_place"] = formfield_for_birth_place(with_birthdate_field=with_birthdate_field)
self.with_birthdate_field = with_birthdate_field
self.birthdate = birthdate

def clean(self):
super().clean()

birthdate = self.birthdate
birth_place = self.cleaned_data.get("birth_place")
birth_country = self.cleaned_data.get("birth_country")
if not birth_country:
# Selecting a birth place sets the birth country field to France and disables it.
# However, disabled fields are ignored by Django.
# That's also why we can't make it mandatory.
# See utils.js > toggleDisableAndSetValue
if birth_place:
self.cleaned_data["birth_country"] = asp_models.Country.objects.get(
code=asp_models.Country._CODE_FRANCE
)
else:
# Display the error above the field instead of top of page.
self.add_error("birth_country", "Le pays de naissance est obligatoire.")

if birth_place and birthdate:
try:
self.cleaned_data["birth_place"] = asp_models.Commune.objects.by_insee_code_and_period(
birth_place.code, birthdate
)
except asp_models.Commune.DoesNotExist:
msg = f"Le code INSEE {birth_place.code} n'est pas référencé par l'ASP en date du {birthdate:%d/%m/%Y}"
self.add_error("birth_place", msg)

def _post_clean(self):
super()._post_clean()
try:
self.instance.birth_place = self.cleaned_data.get("birth_place")
self.instance.birth_country = self.cleaned_data.get("birth_country")
self.instance._clean_birth_fields()
except ValidationError as e:
self._update_errors(e)
super().__init__(*args, **kwargs)


class FilterJobApplicationsForm(forms.Form):
Expand Down
Loading