djangodjango-rest-frameworkdjango-rest-framework-simplejwt

Creating user and user profile with one request which has nested JSON body in Django rest framework


I want to make api to create user and disease instance.

accounts/models.py:

class Disease(models.Model):
    user = models.ForeignKey('MyUser', related_name='diseases', on_delete=models.CASCADE)
    arrhythmia = models.BooleanField(default=False)
    ...

# there's no Disease's attribute in this class. 
class MyUser(AbstractUser):
    identifier = models.CharField(
        max_length=User_Constant.MAX_IDENTIFIER_LENGTH,
        unique=True,
    )

    ...

    REQUIRED_FIELDS = [
        'username', ...
    ]

accounts/serializers.py:

class MyUserSerializer(serializers.ModelSerializer):
    user_disease = DiseaseSerializer(many=False, read_only=False)

    class Meta:
        model = MyUser
        fields = '__all__'


    def create(self, validated_data):
        print("validated_data : ", validated_data)
        user = MyUser.objects.create(**validated_data)
        # disease_data = validated_data['disease']
        # disease = Disease.objects.create(user, **disease_data)

        return user

accounts/views.py:

class SignUpAPIView(APIView):
    permission_classes = [AllowAny]

    def post(self, request):
        serializer = MyUserSerializer(data=request.data)
    
        if serializer.is_valid():
            user = serializer.save()

            # After validation about user, generate token pair
            token = TokenObtainPairSerializer.get_token(user)
            refresh_token = str(token)
            access_token = str(token.access_token)

            res = Response(
                {
                    "user": serializer.data,
                    "message": "sign_up success",
                    "token": {
                        "access": access_token,
                        "refresh": refresh_token
                    }
                },
                status=status.HTTP_201_CREATED
            )

            # Store JWT token in cookie
            res.set_cookie("access", access_token, httponly=True)
            res.set_cookie("refresh", refresh_token, httponly=True)

            return res
        else:
            print("is_valid() is false!")
    
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


    def perform_create(self, serializer):
        serializer.save(user=self.request.user)

when I'm trying to send with POST method, This is a JSON Content:

{
  "identifier": "test1",
  "password": "test1",
  "username": "username1",
  "email": "test1@naver.com",
  "birth": "19960314",
  "gender": true,
  "isForeigner": false,
  "height": 169,
  "weight": 60,
  "pw_queiston_idx": 0,
  "pw_answer": "test_answer",
  "disease": 
  {
    "arrhythmia": true
  }

}

And its response :

Status: 400 Bad Request
Size: 44 Bytes
Time: 15 ms

{
  "user_disease": [
    "This field is required."
  ]
}

What I want to do

  1. Making api to create User record and Disease record in DB.
  2. I want to know what is wrong in this code.

Could you help me out?


Update: Actual my goal is to create Disease(kind of user profile) instance during User instance creation.

What I'm thinking:

  1. Create User instance
  2. With user instance, create Disease instance at the same time.
  3. But I don't know the way to make Disease instance with user instance in this code.

Solution

  • In the first line of your MyUserSerializer you actually add a required field by name user_disease to the ModelSerializer: user_disease = DiseaseSerializer(many=False, read_only=False)

    When you then send a request containing disease instead of user_disease, the Serializer rightfully complains that a required field is missing.

    The DiseaseSerializer will also only do as advertised by default, i.e. it will only serialize the data, not create entries for you, you have to do this yourself:

    class MyUserSerializer(serializers.ModelSerializer):
        disease = DiseaseSerializer(many=False, read_only=False)
    
        class Meta:
            model = MyUser
            fields = '__all__'
    
        def create(self, validated_data):
            disease_data = validated_data.pop('disease')
            user = MyUser.objects.create(**validated_data)
            Disease.objects.create(user=user, **disease_data)
            return user
    

    You have to create the Disease objects after you create the user, otherwise the foreign key cannot be set.