Alex
Alex

Reputation: 135

Django Rest Framework serializer returning nested records that should be filtered out on update

I override my FormSerializer's update method. It marks some nested field records as is_deprecated if necessary. However, the Api call returns all records, even though my get_queryset filters out everything is_deprecated. As far as I can tell, the get_queryset is being called to get the instance, which is then passed to the serializer. This instance does not contain any previously deprecated records.

After the update method completes, it looks like another query must be run that gets ALL of these nested records and serializes them. This only occurs on updates. This may be occurring when serializer is saved in UpdateModelMixin.update() when self.perform_update() is called.

I get the correct records on a standard GET request. Any idea where this second query is being called and how to override it? Alternatively, I should be able to edit the serialized data in to_representation, but this would be inefficient and I'd like to understand what is going on here first.

View

class FormViewSet(LoginRequiredMixin, viewsets.ModelViewSet):
    serializer_class = FormSerializer
    queryset = Form.objects.all()

    def get_queryset(self):
        if 'pk' in self.kwargs:
            qs = Form.objects.filter(id=self.kwargs['pk'])
        else:
            qs = Form.objects.filter(id__in=name_dict.values()).order_by('name')
        queryset = FormSerializer.eager_loading(qs)
        return queryset

Serializer

class FormSerializer(serializers.ModelSerializer):
  id = serializers.IntegerField(required=False, allow_null=True)
  fields = FieldSerializer(many=True)

  class Meta:
      model = Form
      fields = '__all__'

  @staticmethod
  def eager_loading(queryset):
      return queryset.prefetch_related(Prefetch('fields',queryset=Field.objects.filter(is_deprecated=False).order_by('field_order')))

Upvotes: 3

Views: 808

Answers (2)

Ken4scholars
Ken4scholars

Reputation: 6296

If you look at the update method for the modelviewset, you will see that it invalidates the prefetch_related data so your eager loading doesn't work.

def update(self, request, *args, **kwargs):
    partial = kwargs.pop('partial', False)
    instance = self.get_object()
    serializer = self.get_serializer(instance, data=request.data, partial=partial)
    serializer.is_valid(raise_exception=True)
    self.perform_update(serializer)

    if getattr(instance, '_prefetched_objects_cache', None):
        # If 'prefetch_related' has been applied to a queryset, we need to
        # forcibly invalidate the prefetch cache on the instance.
        instance._prefetched_objects_cache = {}

    return Response(serializer.data)

This is done because the prefetched data could become obsolete after the update. So you can override the update method and redo the prefetch after the update

Upvotes: 1

Marat Ablayev
Marat Ablayev

Reputation: 101

I think, this answer should be a comment, but have no enough reputation.

get_queryset method called before update method of serializer.

What I mean by that:

  1. Call for update -> PUT for view
  2. View calls get_queryset -> Now we have not updated is_deprecated yet
  3. View finds object to update and calls update of serializer -> You make changes
  4. View returns instance from update method of serializer

So your fields field is same as on 2 point. Doesn't eager_loading, prefetch works like that? I mean loaded once, and that's it.

So you need to somehow manage to update your prefetched fields field.

Upvotes: 1

Related Questions