简体   繁体   中英

Django Rest Framework (DRF) Refuses to Validate Data with Foreign Keys in Update (PUT) Request

Using Django REST Framework (DRF), I am trying to follow the DRF documentation for nested serializers provided by this link . For the moment, let's assume that my code looks like the following:

models.py

class PvlEntry(models.Model):

    pvl_project = models.OneToOneField("review.ProjectList", on_delete=models.CASCADE, related_name='pvl_project')
    pvl_reviewer = models.ForeignKey('auth.User', on_delete=models.CASCADE, related_name='+')
    pvl_worktype_is_correct = models.BooleanField(blank=False, null=False)
    pvl_hw_description = models.TextField(blank=False, null=False)

class ProjectList(models.Model):
    """ 
    """
    project_number = models.IntegerField(blank=False, null=False, unique=True)
    project_manager = models.CharField(blank=False, max_length=255, null=False)
    project_name = models.CharField(blank=False,
                                          max_length=255,
                                          null=False)
    project_description = models.CharField(blank=True,
                                         max_length=1024,
                                         null=True)

views.py

class PvlEntryListCreateAPIView(ListCreateAPIView):
    """ This view is leveraged for jsGrid so that we can have jsGrid produce
        a JavaScript enabled view for actions like editing and filtering of
        the project vetting list.
    """
    queryset = PvlEntry.objects.all()
    serializer_class = PvlEntrySerializer
    name = 'listcreate-pvlentry'

    def get_queryset(self):
        qs = self.queryset.all()

        return qs

class PvlEntryRetrieveUpdateDestroyAPIView(RetrieveUpdateDestroyAPIView):
    """ Leveraged for jsGrid
    """
    queryset = PvlEntry.objects.all()
    serializer_class = PvlEntrySerializer
    name = 'rud-pvlentry'

serializers.py

class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = User
        fields = [
            'id',
            'first_name',
            'last_name',
            'email'
        ]

class ProjectListSerializer(serializers.ModelSerializer):

    class Meta:
        model = ProjectList
        fields = '__all__'

class PvlEntrySerializer(serializers.ModelSerializer):

    pvl_project = ProjectListSerializer()
    pvl_reviewer = UserSerializer()
   
    def update(self, instance, validated_data):
        print(validated_data)
        return super(PvlEntrySerializer, self).update(self, instance, validated_data)

    class Meta:
        model = PvlEntry
        fields = '__all__'

Now, I understand that as this code sits now, it's not a writeable serializer. However, with the above code in place, should I not at least get past the serializer's validation of the data?

Using the DRF browsable API, when I attempt a PUT operation which is using the RetriveUpdateDestroy built-in API view, I get an error similar to the following:

PUT /pvl/grid/11    
HTTP 400 Bad Request
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "pvl_project": {
        "project_number": [
            "pvl entry with this project number already exists."
        ]
    }
}

Again, I understand that I can't perform an update (PUT) with the code as it is now, but shouldn't it at least pass the serialization/validation phase? I'm not performing the creation of a new record, either. I'm only attempting to perform an update. So, why should validation be concerned with whether or not a "pvl entry with this project number" already exists?

There are a lot of posts on stackoverflow that touch on or dance around this issue but I've not been able to lean on any of them for a resolution for some reason.

I have also tried going back and replacing the nested serializers with PrimaryKeyRelatedFields but that approach doesn't return the related data, only references to the related data.

I have also experimented with separate serializers but that approach doesn't work well for the jsGrid JavaScript that I've implemented to consume the data in my template.

Surely there's a simple resolution?

First solution is that you can use PATCH method instead of PUT and don't send pvl_project in form/ajax data (remember to set required attibute like pvl_project = ProjectListSerializer(required=False) .

Problem is that in drf PUT request method tries to replace all provided data in the given instance at one - which means it will firstly analyze that there is no PvlEntry.pvl_project property duplicates because its OneToOneField .

PATCH on the other hand can update data partialy (u can analyze this problem) and you don't even care about required=False serializer attribute because it will only update data provided in request (how to update partial by PUT method is answered in my last annotation).

Second

Now when we have first concept resolved we can move on and make this serializer to work. Lets' assume that you have uuid attribute set on every model (using id field is not the best practice) and you want to set pvl_project by the given uuid attribute value.

You can override to_internal_value method in ProjectListSerializer which is used on saving instance and simply search for the given object by the given data.

class ProjectListSerializer(serializers.ModelSerializer):
    class Meta:
        model = ProjectList
        fields = '__all__'
    
    def to_internal_value(self, data):
        # you can validate data here for sure that provided data is correct uuid string
        try:
            return ProjectList.objects.get(uuid=data)
        except ProjectList.DoesNotExist:
            raise ValidationError(['Project list with the given uuid does not exist'])

This serializer works now like a good elastic switch between json <-> api <-> django model instance world.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM