register and sign in pages
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
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
|
||||
Reference in New Issue
Block a user