Search code examples
pythonpython-3.xdjangodjango-rest-frameworkdjango-serializer

Django REST: endpoint for creating multiple models


In my service, when registering a user, two entities must be created: the user and the company, to which the user entity will be immediately linked.

To do this, I created an endpoint in which I use two serializers (for the user and for the company). But there are some problems with this solution:

  • Fields for one of the serializers are not displayed in Django Spectacular (Swagger);
  • Fields for one of the serializers are not available in the DRF BRowsable API;
  • Ugly code;
class UserCreateViewSet(mixins.CreateModelMixin,
                        viewsets.GenericViewSet):
    """
    Creates user accounts
    """
    queryset = User.objects.all()
    serializer_class = CreateUserSerializer
    permission_classes = (AllowAny,)
    REQUIRED_FIELDS = (
        'email', 'password',
        'position', 'company_name',
        'phone_number'
    )
    
    def create(self, request, *args, **kwargs):
        data = request.data
        provided_fields = set(data.keys())
        for field in self.REQUIRED_FIELDS:
            if field not in provided_fields:
                return Response(
                    {"detail":f"{field} not provided"},
                    status=400
                )
                
        # --- Creating company for user
        data['name'] = data['company_name']
        company_serializer = CompanyReducedSerializer(data=request.data)
        company_serializer.is_valid(raise_exception=True)
        self.perform_create(company_serializer)
        data['company'] = company_serializer.data['id']
        # ---
        
        # Code from super()
        # --- Creating user
        user_serializer = self.get_serializer(data=request.data)
        user_serializer.is_valid(raise_exception=True)
        self.perform_create(user_serializer)
        headers = self.get_success_headers(user_serializer.data)
        # ---
        
        return Response(
            status=201,
            data=user_serializer.data,
            headers=headers
        )

I considered options using multi-table inheritance, but for some reasons, this solution does not suit me. The company and user entities must be separated.

Is it possible to write a serializer to create two entities? Or is there an even better solution?


Solution

  • I'd suggest a few changes for your code.

    One of the ways can be as follows.

    • You create a serializer to validate the required fields
    • If required data is valid, validate company serializer and create if valid and repeat for user.

    serializers.py

    from rest_framework import serializers
    
    class CreateUserSerializer(serializers.Serializer):
        email = serializers.EmailField(required=True)
        password = serializers.CharField(required=True)
        position = serializers.CharField(required=True)
        company_name = serializers.CharField(required=True)
        phone_number = serializers.CharField(required=True)
    

    views.py

    from rest_framework import mixins, viewsets
    from rest_framework.status import HTTP_201_CREATED
    
    class UserViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
        ...
    
        def create(self, request, *args, **kwargs):
    
            # this serializer will take care of your required fields        
            data_serializer = CreateUserSerializer(request.data)
            data_serializer.is_valid(raise_exception=True)
    
            company_serializer = CompanyCreateSerializer(data=request.data)
            company_serializer.is_valid(raise_exception=True)
            company = company_serializer.save()
    
            # format data required for user creation
            data = {"company": company, "email": email}
            user_serializer = UserCreateSerializer(data=data)
            user_serializer.is_valid(raise_exception=True)
            user_serializer.save()
    
            return Response(
                data=user_serializer.data,
                status=HTTP_201_CREATED
            )
    
    

    Other ways can be in the create method of company or user serializer based on how you want to create the related objects

    e.g.

    class UserCreateSerializer(ModelSerializer):
        ...
    
        def create(self, validated_data: dict):
            company_sz = CompanySerializer(data=<YOUR_DATA>)
            compant_sz.is_valid(raise_exception=True)
            company_sz.save()
            user = super().create(validated_data)
            return user