import datetime from django.utils.timezone import now from rest_framework import serializers from rest_framework.exceptions import ValidationError from greg.models import Consent, ConsentChoice, ConsentType, Identity, Person from gregui.validation import ( validate_phone_number, validate_norwegian_national_id_number, ) # pylint: disable=W0223 class GuestConsentChoiceSerializer(serializers.Serializer): type = serializers.CharField(required=True) choice = serializers.CharField(required=True) def validate(self, attrs): """Check that the combination of consent type and choice exists.""" choice = ConsentChoice.objects.filter( consent_type__identifier=attrs["type"], value=attrs["choice"] ) if not choice.exists(): raise serializers.ValidationError("invalid consent type or choice") return attrs class GuestRegisterSerializer(serializers.ModelSerializer): first_name = serializers.CharField(required=False, min_length=1) last_name = serializers.CharField(required=False, min_length=1) # E-mail set to not required to avoid raising exception if it is not included in input. It is not given that # the guest should be allowed to update it email = serializers.CharField(required=False) mobile_phone = serializers.CharField( required=True, validators=[validate_phone_number] ) fnr = serializers.CharField( required=False, validators=[validate_norwegian_national_id_number] ) passport = serializers.CharField(required=False) date_of_birth = serializers.DateField(required=False) consents = GuestConsentChoiceSerializer(required=False, many=True, write_only=True) def update(self, instance, validated_data): if "email" in validated_data: email = validated_data.pop("email") create_identity_or_update( Identity.IdentityType.PRIVATE_EMAIL, email, instance ) if "mobile_phone" in validated_data: mobile_phone = validated_data.pop("mobile_phone") create_identity_or_update( Identity.IdentityType.PRIVATE_MOBILE_NUMBER, mobile_phone, instance ) if "fnr" in validated_data: fnr = validated_data.pop("fnr") create_identity_or_update( Identity.IdentityType.NORWEGIAN_NATIONAL_ID_NUMBER, fnr, instance ) if "passport" in validated_data: passport = validated_data.pop("passport") create_identity_or_update( Identity.IdentityType.PASSPORT_NUMBER, passport, instance ) # If the name is not allowed to be updated, the request has already been denied at an earlier stage if "first_name" in validated_data: instance.first_name = validated_data["first_name"] if "last_name" in validated_data: instance.last_name = validated_data["last_name"] if "date_of_birth" in validated_data: instance.date_of_birth = validated_data["date_of_birth"] consents = validated_data.get("consents", {}) self._handle_consents(person=instance, consents=consents) return instance def _handle_consents(self, person, consents): consent_types = [x["type"] for x in consents] mandatory_consents = ConsentType.objects.filter(mandatory=True).values_list( "identifier", flat=True ) missing_consents = list(set(mandatory_consents) - set(consent_types)) if missing_consents: raise ValidationError(f"missing mandatory consents {missing_consents}") for consent in consents: consent_type = ConsentType.objects.get(identifier=consent["type"]) choice = ConsentChoice.objects.get( consent_type=consent_type, value=consent["choice"] ) consent_instance, created = Consent.objects.get_or_create( type=consent_type, person=person, choice=choice, defaults={"consent_given_at": now()}, ) if not created and consent_instance.choice != choice: consent_instance.choice = choice consent_instance.save() def validate_date_of_birth(self, date_of_birth): today = datetime.date.today() # Check that the date of birth is between the interval starting about 100 years ago and last year if ( not today - datetime.timedelta(weeks=100 * 52) < date_of_birth < today - datetime.timedelta(weeks=52) ): raise serializers.ValidationError("Invalid date of birth") return date_of_birth class Meta: model = Person fields = ( "id", "first_name", "last_name", "email", "mobile_phone", "fnr", "passport", "date_of_birth", "consents", ) read_only_fields = ("id",) def create_identity_or_update( identity_type: Identity.IdentityType, value: str, person: Person ): existing_identity = person.identities.filter(type=identity_type).first() if not existing_identity: Identity.objects.create( person=person, type=identity_type, value=value, ) else: existing_identity.value = value existing_identity.save()