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

Viewed 9199

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.

3 Answers

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)

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

To answer your question: just override save() method for your models as written in docs. There you can assign any values to your model instance directly before saving it in database.

Also, you should probably use models.EmailField for your email fields which will get rid of your lower() check.

Related