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

create multiple objects in model serializer create method


I need the below DRF API View to list and create Withdraw records in my Django project.

class WithdrawListCreateAPIView(PartnerAware, WithdrawQuerySetViewMixin, generics.ListCreateAPIView):
    permission_classes = (TokenMatchesOASRequirements,)
    required_alternate_scopes = {
        "GET": [[OTCScopes.WITHDRAW_READ]],
        "POST": [[OTCScopes.WITHDRAW_CREATE]],
    }

    def get_serializer_class(self, *args, **kwargs):
        if self.request.method == "POST":
            return WithdrawCreateSerializer
        return WithdrawSerializer

    def initial(self, request, *args, **kwargs):
        super().initial(request, *args, **kwargs)
        self.get_partner_info()

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

My API construction must be for a single record, but it is possible that this amount may be greater than a specific amount called SETTLEMENT_MAX_AMOUNT, and I have to break it down to more than one Withdraw record and also I must create a separate record for each one, and I should return all these records that have been created in one list to the user.Here is the related serializer I've implemented for this case:

class WithdrawCreateSerializer(serializers.ModelSerializer):
    target_uuid = serializers.UUIDField(write_only=True)

    def validate_target_uuid(self, value):
        partner = self.context["view"].partner
        try:
            target = WithdrawTarget.objects.get(active=True, uuid=value, partner=partner)
        except WithdrawTarget.DoesNotExist:
            raise serializers.ValidationError("Target does not exist for the current partner.")
        return target.uuid

    def create(self, validated_data):
        target_uuid = validated_data.pop("target_uuid")
        partner = self.context["view"].partner
        target = WithdrawTarget.objects.get(uuid=target_uuid, partner=partner)

        amount = validated_data["amount"]
        num_withdrawals = amount // SETTLEMENT_MAX_AMOUNT
        remaining_amount = amount % SETTLEMENT_MAX_AMOUNT

        withdrawals = []
        for _ in range(num_withdrawals):
            withdraw_data = {
                "target": target,
                "amount": SETTLEMENT_MAX_AMOUNT,
                "partner": validated_data["partner"],
                "created_by": validated_data["created_by"],
                "description": validated_data.get("description"),
            }
            withdrawal = Withdraw.objects.create(**withdraw_data)
            withdrawals.append(withdrawal)

        if remaining_amount > 0:
            withdraw_data = {
                "target": target,
                "amount": remaining_amount,
                "partner": validated_data["partner"],
                "created_by": validated_data["created_by"],
                "description": validated_data.get("description"),
            }
            withdrawal = Withdraw.objects.create(**withdraw_data)
            withdrawals.append(withdrawal)

        return withdrawals

    class Meta:
        model = Withdraw
        fields = ("uuid",
                  "amount",
                  "target_uuid",
                  "description",
                  "status",
                  "tracker_id",
                  "created_at",)
        extra_kwargs = {
            "amount": {"required": True, "allow_null": False},
            "target_uuid": {"required": True, "allow_null": False},
        }
        read_only_fields = ("state", "tracker_id", "created_at")

Now by sending this request:

curl --location '127.0.0.1:8000/withdraws/' \
--header 'Authorization: Bearer blob' \
--form 'amount="2240"' \
--form 'target_uuid="d4d92a38-4193-443c-b11e-8022e64543a4"'

for example, the value of SETTLEMENT_MAX_AMOUNT is 1000, and with the above request I should get 3 "Withdraw" records with amount of: 1000, 1000, 240.

I receive the following error in response:

AttributeError at /withdraws/
Got AttributeError when attempting to get a value for field `amount` on serializer `WithdrawCreateSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `list` instance.

The original exception text was: 'list' object has no attribute 'amount'.

How can I change the above view or serializer the system behaves as I want?


Solution

  • by changing my API view to below view I resolve this problem:

    class WithdrawListCreateAPIView(PartnerAware, WithdrawQuerySetViewMixin, generics.ListCreateAPIView):
        permission_classes = (TokenMatchesOASRequirements,)
        required_alternate_scopes = {
            "GET": [[OTCScopes.WITHDRAW_READ]],
            "POST": [[OTCScopes.WITHDRAW_CREATE]],
        }
    
        def get_serializer_class(self, *args, **kwargs):
            if self.request.method == "POST":
                return WithdrawCreateSerializer
            return WithdrawSerializer
    
        def initial(self, request, *args, **kwargs):
            super().initial(request, *args, **kwargs)
            self.get_partner_info()
    
        def perform_create(self, serializer):
            return serializer.save(partner=self.partner, created_by=self.request.user)
    
        def create(self, request, *args, **kwargs):
            serializer = self.get_serializer(data=request.data)
            serializer.is_valid(raise_exception=True)
            withdraws = self.perform_create(serializer)
            return Response(WithdrawSerializer(withdraws, many=True).data, status=status.HTTP_201_CREATED)