Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DRF: how to change the value of the model fields before saving to the database

If I need to change some field values before saving to the database as I think models method clear() is suitable. But I can't call him despite all my efforts.

For example fields email I need set to lowercase and fields nda I need set as null

models.py

class Vendors(models.Model):

    nda = models.DateField(blank=True, null=True)
    parent = models.OneToOneField('Vendors', models.DO_NOTHING, blank=True, null=True)

    def clean(self):
        if self.nda == "":
            self.nda = None

class VendorContacts(models.Model):
    ....
    vendor = models.ForeignKey('Vendors', related_name='contacts', on_delete=models.CASCADE)
    email = models.CharField(max_length=80, blank=True, null=True, unique=True)

    def clean(self):
        if self.email:
            self.email = self.email.lower()

serializer.py

class VendorContactSerializer(serializers.ModelSerializer):
    class Meta:
        model = VendorContacts
        fields = (
                  ...
                  'email',)

class VendorsSerializer(serializers.ModelSerializer):
    contacts = VendorContactSerializer(many=True)

    class Meta:
        model = Vendors
        fields = (...
                  'nda',
                  'contacts',
                  )

    def create(self, validated_data):
        contact_data = validated_data.pop('contacts')
        vendor = Vendors.objects.create(**validated_data)
        for data in contact_data:
            VendorContacts.objects.create(vendor=vendor, **data)

        return vendor

views.py

class VendorsCreateView(APIView):
    """Create new vendor instances from form"""
    permission_classes = (permissions.AllowAny,)
    serializer_class = VendorsSerializer

    def post(self, request, *args, **kwargs):
        serializer = VendorsSerializer(data=request.data)
        try:
            serializer.is_valid(raise_exception=True)
            serializer.save()
        except ValidationError:
            return Response({"errors": (serializer.errors,)},
                            status=status.HTTP_400_BAD_REQUEST)
        else:
            return Response(request.data, status=status.HTTP_200_OK)

As I learned from the documentation

Django Rest Framework serializers do not call the Model.clean when validating model serializers

In dealing with this problem, I found two ways to solve it. 1. using the custom method at serializer. For my case, it looks like

class VendorsSerializer(serializers.ModelSerializer): contacts = VendorContactSerializer(many=True)

class Meta:
    model = Vendors
    fields = (...
              'nda',
              'contacts',
              )

def create(self, validated_data):
    contact_data = validated_data.pop('contacts')
    vendor = Vendors.objects.create(**validated_data)
    for data in contact_data:
        VendorContacts.objects.create(vendor=vendor, **data)

    return vendor

def validate(self, attrs):
    instance = Vendors(**attrs)
    instance.clean()
    return attrs
  1. Using full_clean() method. For me, it looks like

class VendorsSerializer(serializers.ModelSerializer): contacts = VendorContactSerializer(many=True)

class Meta:
    model = Vendors
    fields = (...
              'nda',
              'contacts',
              )

def create(self, validated_data):
    contact_data = validated_data.pop('contacts')
    vendor = Vendors(**validated_data)
    vendor.full_clean()
    vendor.save()
    for data in contact_data:
        VendorContacts.objects.create(vendor=vendor, **data)

    return vendor

But in both cases, the clean() method is not called. I really don't understand what I'm doing wrong.

like image 798
Jekson Avatar asked Mar 03 '23 17:03

Jekson


2 Answers

In my case I had the same problem but with validation feature I used the way below and it works for me (not excludes the way found above):

class CustomViewClass(APIView):

    def post(self, request, format=None):
        
        prepared_data_variable = 'some data in needed format'
        serializer = CustomSerializer(data=request.data)
    
        if serializer.is_valid(self):
            serializer.validated_data['field_name'] = prepared_data_variable
            serializer.save()
            return Response(data=serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

This string is key for my solution serializer.validated_data['field_name'] = prepared_data_variable

like image 178
AlexKh Avatar answered Mar 05 '23 16:03

AlexKh


For DRF you can change your serializer before save as below...

First of all, you should check that serializer is valid or not, and if it is valid then change the required object of the serializer and then save that serializer.

if serializer.is_valid():
    serializer.object.user_id = 15  # For example 
    serializer.save() 

UPD! views.py

class VendorsCreateView(APIView):
    """Create new vendor instances from form"""
    permission_classes = (permissions.AllowAny,)
    serializer_class = VendorsSerializer

    def post(self, request, *args, **kwargs):
        data = request.data
        if data['nda'] == '':
            data['nda'] = None
        for contact in data['contacts']:
            if contact['email']:
                print(contact['email'])
                contact['email'] = contact['email'].lower()
        serializer = VendorsSerializer(data=request.data)
        try:
            serializer.is_valid(raise_exception=True)
            serializer.save()
        except ValidationError:
            return Response({"errors": (serializer.errors,)},
                            status=status.HTTP_400_BAD_REQUEST)
like image 22
MK Patel Avatar answered Mar 05 '23 15:03

MK Patel