Search code examples
pythondjangodjango-modelsdjango-rest-frameworkdjango-views

How to return actual data from a serializer Django


I'm trying to test a register_customer api endpoint I'm working on on Django, but I'm getting the the user_id as a response instead of the actual user's data.

{
    "data": {
        "user_id": 15
    },
    "message": "Thank you for registering"
}

I have a Customer model that has a OneToOne relationship with the CustomUser

class Customer(models.Model):
    user_id = models.OneToOneField(CustomUser, on_delete=models.CASCADE)

And here's my serializer:

from rest_framework import serializers
from .models import Customer, CustomUser
import logging
logger = logging.getLogger(__name__)
class UserSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True, required=True)
    confirm_password = serializers.CharField(write_only=True, required=True)
    
    class Meta:
        model = CustomUser
        fields = [
            "email",
            "first_name",
            "last_name",
            "phone_number",
            "password",
            "confirm_password",
        ]

    def validate_password(self, value):
        # Password must be at least 8 characters long
        if len(value) < 8:
            raise serializers.ValidationError(
                "Password must be at least 8 characters long."
            )

        # Check for at least one uppercase character
        if not any(char.isupper() for char in value):
            raise serializers.ValidationError(
                "Password must contain at least one uppercase character."
            )

        # Check for at least one special character
        special_characters = "!@#$%^&*()-_=+[]{}|;:'\",.<>/?"
        if not any(char in special_characters for char in value):
            raise serializers.ValidationError(
                "Password must contain at least one special character."
            )

        # Check for at least one number
        if not any(char.isdigit() for char in value):
            raise serializers.ValidationError(
                "Password must contain at least one number."
            )

        return value

    def validate_email(self, value):
        if CustomUser.objects.filter(email=value).exists():
            raise serializers.ValidationError("This email is already in use.")
        return value

    def validate(self, data):
        # Check if password and confirm_password match
        password = data.get("password")
        confirm_password = data.get("confirm_password")
        logger.debug({password, confirm_password})
        if password != confirm_password:
            raise serializers.ValidationError("Passwords do not match.")

        return data

    def create(self, validated_data):
        # Remove 'confirm_password' from the data before creating the user
        validated_data.pop("confirm_password", None)
        # Retrieve password directly from validated_data
        password = validated_data.get("password")
        # Ensure that password is not None before validating its length
        if password is None:
            raise serializers.ValidationError("Password cannot be empty.")
        self.validate_password(password)

        # Create the user without 'confirm_password'
        user = CustomUser.objects.create_user(**validated_data)

        # Set the password for the user
        user.set_password(password)

        user.save()
        return user

class CustomerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Customer
        fields = ["user_id"]

And here's the view I'm testing:

class RegisterCustomerView(APIView):
    @transaction.atomic
    def post(self, request, *args, **kwargs):
        logger.debug(f"Request_data: {request.data}")
        user_data = request.data
        # Validate UserSerializer first
        user_serializer = UserSerializer(data=user_data)
        
        if user_serializer.is_valid():
            # Access validated data after validation
            user_data = user_serializer.validated_data
            if user_data:
                try:
                    user = user_serializer.save()
                    customer_data = {"user_id": user.id}
                    customer_serializer = CustomerSerializer(data=customer_data)
                    if customer_serializer.is_valid():
                        customer_serializer.save()
                        # Automatically send verification email upon successful registration
                        send_verification_email(user)
                        return Response(
                            {
                                "data": customer_serializer.data,
                                "message": "Thank you for registering",
                            },
                            status=status.HTTP_201_CREATED,
                        )
                    else:
                        raise ValidationError(detail=customer_serializer.errors)

                except IntegrityError as e:
                    logger.error(f"Integrity error: {e}")
                    return Response(
                        {"detail": "Integrity error. Please ensure the data is unique."},
                        status=status.HTTP_400_BAD_REQUEST,
                    )
                except Exception as e:
                    logger.error(f"An unexpected error occurred: {e}")
                    return Response(
                        {"detail": "An unexpected error occurred."},
                        status=status.HTTP_500_INTERNAL_SERVER_ERROR,
                    )
        else:
            raise ValidationError(detail=user_serializer.errors)

I've tried this on the CustomerSerializer

class CustomerSerializer(serializers.ModelSerializer):
    email = serializers.EmailField(source='user.email')
    first_name = serializers.CharField(source='user.first_name')
    last_name = serializers.CharField(source='user.last_name')
    phone_number = serializers.CharField(source='user.phone_number')
    
    class Meta:
        model = Customer
        fields = ["email", "first_name", "last_name", "phone_number"]

But I didn't get the actual response as expected rather

{
    "detail": {
        "email": [
            "This field is required."
        ],
        "first_name": [
            "This field is required."
        ],
        "last_name": [
            "This field is required."
        ],
        "phone_number": [
            "This field is required."
        ]
    }
}

I'd appreciate any assistance. Thanks


Solution

  • If you want to get CustomUser data in Customer model, you can define nested serializer for CustomUser model and add it to CustomerSerializer:

    class CustomUserNestedSerializer(serializers.ModelSerializer):
        class Meta:
            model = CustomUser
            fields = ('pk', 'email', 'first_name', 'last_name', 'phone_number',)
    
    
    class CustomerSerializer(serializers.ModelSerializer):
        user_id = CustomUserNestedSerializer(read_only=True)
    
        class Meta:
            model = Customer
            fields = ('pk', 'user_id',)
    

    also in your view, you are creating Customer object by using the serializer. Instead of that, just create customer directly with model and then pass the object to serializer to return it:

    customer_obj = Customer.objects.create(user_id=user)
    customer_serialized = CustomerSerializer(customer_obj).data
    return Response(customer_serialized, status=status.HTTP_20_CREATED)
    

    Your modified serializer for Customer is ok just modify the source to user_id instead of user as your field name is user_id in Customer model and also and read_only=True to fields.