Files
2026-09-22 00:50:30 +05:00

171 lines
5.5 KiB
Python

from datetime import date
from django.core.exceptions import ValidationError as DjangoValidationError
from django.db import transaction
from django.utils import timezone
from rest_framework import serializers
from locations.models import Atoll, Island
from .mobile import normalize_mobile
from .models import RegistrationTicket, User
MAX_AGE_YEARS = 120
class MobileField(serializers.CharField):
"""Accepts 7712345 / 9607712345 / +960 771 2345 and stores +9607712345."""
def to_internal_value(self, data):
value = super().to_internal_value(data)
try:
return normalize_mobile(value)
except DjangoValidationError as exc:
raise serializers.ValidationError(exc.messages) from exc
class AuthStartSerializer(serializers.Serializer):
mobile = MobileField()
class PasswordLoginSerializer(serializers.Serializer):
mobile = MobileField()
password = serializers.CharField(trim_whitespace=False, write_only=True)
class OtpVerifySerializer(serializers.Serializer):
mobile = MobileField()
code = serializers.RegexField(r"^\d{6}$", write_only=True)
class UserSerializer(serializers.ModelSerializer):
is_admin = serializers.BooleanField(source="is_admin_user", read_only=True)
has_password = serializers.SerializerMethodField()
atoll_name = serializers.CharField(source="atoll.name", default=None, read_only=True)
island_name = serializers.CharField(
source="island.name", default=None, read_only=True
)
class Meta:
model = User
fields = [
"id",
"mobile",
"full_name",
"email",
"idnumber",
"date_of_birth",
"atoll",
"atoll_name",
"island",
"island_name",
"auth_method",
"status",
"rejection_reason",
"mobile_verified",
"is_admin",
"has_password",
"date_joined",
]
read_only_fields = fields
def get_has_password(self, obj) -> bool:
return obj.has_usable_password()
class RegistrationSerializer(serializers.Serializer):
"""The registration form.
The mobile number is not accepted from the client: it comes from the
`registration_token`, which is only issued after that number confirmed an
SMS code. So the number on the account is always one the applicant proved
they control, and the form's prefilled field can't be tampered with.
"""
registration_token = serializers.CharField(write_only=True)
full_name = serializers.CharField(max_length=255)
idnumber = serializers.CharField(max_length=32)
date_of_birth = serializers.DateField()
atoll = serializers.PrimaryKeyRelatedField(
queryset=Atoll.objects.filter(is_active=True)
)
island = serializers.PrimaryKeyRelatedField(
queryset=Island.objects.filter(is_active=True)
)
terms_accepted = serializers.BooleanField()
policy_accepted = serializers.BooleanField()
def validate_registration_token(self, value):
ticket = RegistrationTicket.objects.active().filter(key=value).first()
if ticket is None:
raise serializers.ValidationError(
"Your number needs to be verified again."
)
return ticket
def validate_full_name(self, value):
name = " ".join(value.split())
if len(name) < 3:
raise serializers.ValidationError("Enter your full name.")
return name
def validate_idnumber(self, value):
return value.strip().upper()
def validate_date_of_birth(self, value):
today = date.today()
if value > today:
raise serializers.ValidationError("Date of birth can't be in the future.")
if value.year < today.year - MAX_AGE_YEARS:
raise serializers.ValidationError("Enter a valid date of birth.")
return value
def validate_terms_accepted(self, value):
if not value:
raise serializers.ValidationError(
"You must agree to the terms and conditions."
)
return value
def validate_policy_accepted(self, value):
if not value:
raise serializers.ValidationError(
"You must confirm you understand the privacy policy."
)
return value
def validate(self, attrs):
island = attrs["island"]
if island.atoll_id != attrs["atoll"].pk:
raise serializers.ValidationError(
{"island": "That island isn't in the selected atoll."}
)
ticket = attrs["registration_token"]
if User.objects.filter(mobile=ticket.mobile).exists():
raise serializers.ValidationError(
{"mobile": "An account already exists for this number."}
)
return attrs
@transaction.atomic
def create(self, validated_data):
ticket = validated_data["registration_token"]
now = timezone.now()
user = User.objects.create_user(
mobile=ticket.mobile,
full_name=validated_data["full_name"],
idnumber=validated_data["idnumber"],
date_of_birth=validated_data["date_of_birth"],
atoll=validated_data["atoll"],
island=validated_data["island"],
auth_method=User.AuthMethod.OTP,
status=User.Status.PENDING,
mobile_verified=True,
terms_accepted_at=now,
policy_accepted_at=now,
)
ticket.consume()
return user