2

Running into a little snag here with my DRF backend.

I am populating fields with choices on certain models.

I have a foreign key requirement on one model. When I create the model I want to save it under the foreign id.

When I request the models, I want the model with whatever the choice field maps to.

I was able to do this with SerializerMethodField, however when I try to create a model, I get a 400 error because the block is not valid. If I remove the SerializerMethodField, I can save, but get the number stored in the db from the request.

Any help would be appreciated.

class BlockViewSet(ModelViewSet):
    model = apps.get_model('backend', 'Block')
    queryset = model.objects.all()
    serializer_class = serializers.BlockSerializer
    permissions = ('All',)

    def create(self, request, format=None):
        data = request.data 
        data['user'] = request.user.id
        data['goal'] = WorkoutGoal.objects.get(goal=data['goal']).id
        block = serializers.BlockSerializer(data=data, context={'request': request})
        if block.is_valid():
            new_block = block.save()
            return Response({'block': {'name': new_block.name, 'id': new_block.id}}, status=status.HTTP_201_CREATED)
        else:
            return Response(block.errors, status=status.HTTP_400_BAD_REQUEST)

class WorkoutGoalSerializer(serializers.ModelSerializer):
    class Meta:
        model = apps.get_model('backend', 'WorkoutGoal')
        fields = ('goal',)

    goal = serializers.SerializerMethodField(read_only=True, source='get_goal')

    def get_goal(self, obj):
        return dict(WorkoutGoal.GOALS).get(obj.goal)

class BlockSerializer(serializers.ModelSerializer):
    workout_count = serializers.IntegerField(required=False)
    completed_workouts = serializers.IntegerField(required=False)
    goal = WorkoutGoalSerializer()

    class Meta:
        model = apps.get_model('backend', 'Block')
        read_only_fields = ('workout_count', 'completed_workouts')
        fields = read_only_fields + ('id', 'name', 'user', 'created', 'goal')

The above code returns the correct choice, but I can't save under it. Remove the goal = WorkoutGoalSerializer() and it saves but doesn't return the mapped choice.

hancho
  • 1,345
  • 3
  • 19
  • 39
  • I haven't played around with this, but why is `goal` not in the `read_only_fields` of `BlockSerializer`? – RishiG Jul 30 '18 at 19:59
  • It's a foreign key field on the Block model that is required. Wouldn't read only be for things you were not writing to the db? – hancho Jul 30 '18 at 20:09
  • I see. I misunderstood on my first read through. – RishiG Jul 30 '18 at 20:22
  • I haven't tried it, but this looks like a duplicate of [this question](https://stackoverflow.com/q/26561640/2715819)... in particular, [this answer](https://stackoverflow.com/a/37529772/2715819) looks like it should do the trick. – RishiG Jul 30 '18 at 20:30

2 Answers2

6

I think this will work like a charm,

class WorkoutGoalSerializer(serializers.ModelSerializer):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if 'request' in self.context and self.context['request'].method == 'GET':
            self.fields['goal'] = serializers.SerializerMethodField(read_only=True, source='get_goal')

    class Meta:
        model = apps.get_model('backend', 'WorkoutGoal')
        fields = ('goal',)

    goal = serializers.SerializerMethodField(read_only=True, source='get_goal')  # remove this line

    def get_goal(self, obj):
        return dict(WorkoutGoal.GOALS).get(obj.goal)

How this Work?
It will re-initiate the goal field with SerializerMethodField, if the reuested method is GET.

Remember one thing, you should remove the line,

goal = serializers.SerializerMethodField(read_only=True, source='get_goal')

JPG
  • 82,442
  • 19
  • 127
  • 206
0

serializers.py

class BlockCreateSerializer(serializers.ModelSerializer):
    workout_count = serializers.IntegerField(required=False)
    completed_workouts = serializers.IntegerField(required=False)

    class Meta:
        model = apps.get_model('backend', 'Block')
        read_only_fields = ('workout_count', 'completed_workouts')
        fields = read_only_fields + ('id', 'name', 'user', 'created', 'goal')

class BlockSerializer(serializers.ModelSerializer):
    workout_count = serializers.IntegerField(required=False)
    completed_workouts = serializers.IntegerField(required=False)
    goal = WorkoutGoalSerializer()

    class Meta:
        model = apps.get_model('backend', 'Block')
        read_only_fields = ('workout_count', 'completed_workouts')
        fields = read_only_fields + ('id', 'name', 'user', 'created', 'goal')

views.py

class BlockViewSet(ModelViewSet):
    model = apps.get_model('backend', 'Block')
    queryset = model.objects.all()
    serializer_class = serializers.BlockSerializer
    permissions = ('All',)

    def get_serializer_class(self):
        if self.action == 'create':
            return serializers.BlockCreateSerializer
        else:
            return self.serializer_class

    def create(self, request, format=None):
        data = request.data 
        data['user'] = request.user.id
        data['goal'] = WorkoutGoal.objects.get(goal=data['goal']).id
        block = self.get_serializer(data=data)
        if block.is_valid():
            new_block = block.save()
            return Response({'block': {'name': new_block.name, 'id': new_block.id}}, status=status.HTTP_201_CREATED)
        else:
            return Response(block.errors, status=status.HTTP_400_BAD_REQUEST)

override get_serializer_class to return different serializer_class for create and other action(list\retrieve\update\partial_update)

Ykh
  • 7,567
  • 1
  • 22
  • 31