workout-challenge/src-backend/custom_user/serializers.py
2025-09-27 18:19:06 +01:00

118 lines
No EOL
4.3 KiB
Python

from rest_framework import serializers
from django.conf import settings
from django.contrib.auth.tokens import default_token_generator
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.utils.encoding import force_bytes
from django.template.loader import render_to_string
from .models import CustomUser
from .emails.multipurpose import send_email
class CustomUserSerializer(serializers.ModelSerializer):
my = serializers.SerializerMethodField()
class Meta:
model = CustomUser
fields = ['id', 'my', 'email', 'first_name', 'last_name', 'gender', 'username', 'password', 'is_verified', 'email_mid_week', 'strava_athlete_id', 'strava_allow_follow', 'strava_last_synced_at', 'my_competitions', 'my_teams', 'goal_active_days', 'goal_workout_minutes', 'goal_distance', 'scaling_kcal', 'scaling_distance']
read_only_fields = ['is_verified', 'strava_athlete_id', 'strava_last_synced_at']
extra_kwargs = {
'password': {'write_only': True},
}
def get_my(self, obj):
user = self.context['request'].user
return obj.pk == user.pk
def create(self, validated_data):
user = CustomUser.objects.create_user(
email=validated_data.get('email'),
first_name=validated_data.get('first_name'),
last_name=validated_data.get('last_name', None),
password=validated_data.get('password'),
gender=validated_data.get('gender', None),
)
return user
def to_representation(self, instance):
rep = super().to_representation(instance)
user = self.context['request'].user
# Omit 'secret' fields of other users that this user is not allowed to see
if instance.pk != user.pk:
rep.pop('email', None)
rep.pop('first_name', None)
rep.pop('last_name', None)
rep.pop('gender', None)
rep.pop('password', None)
rep.pop('strava_last_synced_at', None)
if not rep['strava_allow_follow']:
rep.pop('strava_athlete_id', None)
return rep
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# If instance exists, it's an update (PUT/PATCH), make fields optional
if self.instance:
self.fields['email'].required = False
self.fields['password'].required = False
self.fields['first_name'].required = False
self.fields['last_name'].required = False
class PasswordResetSerializer(serializers.Serializer):
email = serializers.EmailField()
def validate_email(self, value):
if not CustomUser.objects.filter(email=value).exists():
# To avoid leaking info
return value
return value
def save(self, request):
email = self.validated_data['email']
users = CustomUser.objects.filter(email=email)
for user in users:
uid = urlsafe_base64_encode(force_bytes(user.pk))
token = default_token_generator.make_token(user)
reset_url = f"{settings.MAIN_HOST}/password/reset/{uid}/{token}/"
email_subject = "Workout Challenge - Reset Your Password"
email_body = render_to_string(
"email_password_reset.html",
{
'first_name': user.first_name,
'MAIN_HOST': settings.MAIN_HOST,
'RESET_URL': reset_url,
'EMAIL_REPLY_TO': settings.EMAIL_REPLY_TO,
}
)
send_email(subject=email_subject, body=email_body, to_email=user.email)
class PasswordResetConfirmSerializer(serializers.Serializer):
uid = serializers.CharField()
token = serializers.CharField()
new_password = serializers.CharField(write_only=True)
def validate(self, attrs):
try:
uid = urlsafe_base64_decode(attrs['uid']).decode()
self.user = CustomUser.objects.get(pk=uid)
except (CustomUser.DoesNotExist, ValueError):
raise serializers.ValidationError("Invalid user.")
if not default_token_generator.check_token(self.user, attrs['token']):
raise serializers.ValidationError("Invalid or expired token.")
return attrs
def save(self):
self.user.set_password(self.validated_data['new_password'])
self.user.save()