schon/vibes_auth/serializers.py
Egor fureunoir Gorbunov eb68132dd3 Features: 1) None;
Fixes: 1) Correct formatting of multi-line expressions for better readability; 2) Ensure consistent use of single-line expressions where appropriate; 3) Fix minor spacing issues in text fields; 4) Adjust admin model field `general_fields` to include `priority`;

Extra: Refactored several multi-line statements to improve consistency and code style.
2025-06-29 19:08:17 +03:00

239 lines
7.8 KiB
Python

import logging
from collections.abc import Collection
from contextlib import suppress
from typing import Any
from constance import config
from django.contrib.auth import authenticate
from django.contrib.auth.models import update_last_login
from django.contrib.auth.password_validation import validate_password
from django.utils.translation import gettext_lazy as _
from drf_spectacular.utils import extend_schema_field
from rest_framework.exceptions import AuthenticationFailed, ValidationError
from rest_framework.fields import (
BooleanField,
CharField,
EmailField,
ListField,
SerializerMethodField,
)
from rest_framework.serializers import ModelSerializer, Serializer
from rest_framework_simplejwt.exceptions import TokenError
from rest_framework_simplejwt.serializers import AuthUser, PasswordField
from rest_framework_simplejwt.settings import api_settings
from rest_framework_simplejwt.token_blacklist.models import BlacklistedToken
from rest_framework_simplejwt.tokens import RefreshToken, Token, UntypedToken
from core.models import Product
from core.serializers import ProductSimpleSerializer
from core.utils.security import is_safe_key
from evibes import settings
from vibes_auth.models import User
logger = logging.getLogger(__name__)
class UserSerializer(ModelSerializer):
avatar_url = SerializerMethodField(required=False, read_only=True)
password = CharField(write_only=True, required=False)
is_staff = BooleanField(read_only=True)
recently_viewed = SerializerMethodField(required=False, read_only=True)
@staticmethod
def get_avatar_url(obj) -> str:
if obj.avatar:
return f"https://api.{config.BASE_DOMAIN}/media/{obj.avatar!s}"
return f"https://api.{config.BASE_DOMAIN}/static/person.png"
class Meta:
model = User
fields = [
"uuid",
"email",
"avatar_url",
"is_staff",
"recently_viewed",
"first_name",
"last_name",
"password",
"phone_number",
"is_subscribed",
"modified",
"created",
]
def create(self, validated_data):
user = User.objects.create(
email=validated_data["email"],
first_name=validated_data["first_name"],
last_name=validated_data["last_name"],
)
user.set_password(validated_data.pop("password"))
for attr, value in validated_data.items():
if is_safe_key(attr):
setattr(user, attr, value)
user.save()
return user
def update(self, instance, validated_data):
for attr, value in validated_data.items():
if is_safe_key(attr):
setattr(instance, attr, value)
if attr == "password":
instance.set_password(value)
instance.save()
return instance
def validate(self, attrs):
if "password" in attrs:
validate_password(attrs["password"])
return attrs
@extend_schema_field(ProductSimpleSerializer(many=True))
def get_recently_viewed(self, obj) -> Collection[Any]:
"""
Returns a list of serialized ProductSimpleSerializer representations
for the UUIDs in obj.recently_viewed.
"""
# noinspection PyTypeChecker
return ProductSimpleSerializer(
instance=Product.objects.filter(uuid__in=obj.recently_viewed, is_active=True),
many=True,
).data
class TokenObtainSerializer(Serializer):
username_field = User.USERNAME_FIELD
token_class: type[Token] | None = None
default_error_messages = {"no_active_account": _("no active account")}
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.user = None
self.fields[self.username_field] = CharField(write_only=True)
self.fields["password"] = PasswordField()
def validate(self, attrs: dict[str, Any]) -> dict[Any, Any]:
authenticate_kwargs = {
self.username_field: attrs[self.username_field],
"password": attrs["password"],
}
with suppress(KeyError):
authenticate_kwargs["request"] = self.context["request"]
self.user = authenticate(**authenticate_kwargs) # type: ignore
if not api_settings.USER_AUTHENTICATION_RULE(self.user):
raise AuthenticationFailed(
self.error_messages["no_active_account"],
_("no active account"), # type: ignore
)
return {}
@classmethod
def get_token(cls, user: AuthUser) -> Token:
return cls.token_class.for_user(user) # type: ignore
class TokenObtainPairSerializer(TokenObtainSerializer):
token_class = RefreshToken
def validate(self, attrs: dict[str, Any]) -> dict[str, str]:
data = super().validate(attrs)
logger.debug("Data validated")
refresh = self.get_token(self.user) # type: ignore
data["refresh"] = str(refresh)
data["access"] = str(refresh.access_token) # type: ignore
data["user"] = UserSerializer(self.user).data
logger.debug("Data formed")
if api_settings.UPDATE_LAST_LOGIN:
update_last_login(self.user, self.user) # type: ignore
logger.debug("Updated last login")
logger.debug("Returning data")
return data
class TokenRefreshSerializer(Serializer):
refresh = CharField()
access = CharField(read_only=True)
token_class = RefreshToken
def validate(self, attrs: dict[str, Any]) -> dict[str, str]:
refresh = self.token_class(attrs["refresh"])
data = {"access": str(refresh.access_token)}
if api_settings.ROTATE_REFRESH_TOKENS:
if api_settings.BLACKLIST_AFTER_ROTATION:
with suppress(AttributeError):
refresh.blacklist()
refresh.set_jti()
refresh.set_exp()
refresh.set_iat()
data["refresh"] = str(refresh)
user = User.objects.get(uuid=refresh.payload["user_uuid"])
data["user"] = UserSerializer(user).data # type: ignore
return data
class TokenVerifySerializer(Serializer):
token = CharField(write_only=True)
def validate(self, attrs: dict[str, None]) -> dict[Any, Any]:
token = UntypedToken(attrs["token"])
if (
api_settings.BLACKLIST_AFTER_ROTATION
and "rest_framework_simplejwt.token_blacklist" in settings.INSTALLED_APPS
):
jti = token.get(api_settings.JTI_CLAIM)
if BlacklistedToken.objects.filter(token__jti=jti).exists():
raise ValidationError(_("token_blacklisted"))
try:
payload = UntypedToken(attrs["token"]).payload
except TokenError:
raise ValidationError(_("invalid token"))
try:
user_uuid = payload["user_uuid"]
user = User.objects.get(uuid=user_uuid)
except KeyError:
raise ValidationError(_("no user uuid claim present in token"))
except User.DoesNotExist:
raise ValidationError(_("user does not exist"))
attrs["user"] = UserSerializer(user).data # type: ignore
return attrs
class ConfirmPasswordResetSerializer(Serializer):
uidb64 = CharField(write_only=True, required=True)
token = CharField(write_only=True, required=True)
password = CharField(write_only=True, required=True)
confirm_password = CharField(write_only=True, required=True)
class ResetPasswordSerializer(Serializer):
email = EmailField(write_only=True, required=True)
class ActivateEmailSerializer(Serializer):
uidb64 = CharField(required=True)
token = CharField(required=True)
class MergeRecentlyViewedSerializer(Serializer):
product_uuids = ListField(required=True, child=CharField(required=True))