Dynamically extend serializer field list in Django REST fw

I’d like to have a model serializer in which some fields are only displayed if the request.user in the serializer context is a staff member.

I know I could override the __init__ method and do something like:

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.context["request"].user.is_staff:
            self.fields["staff_only_field1"] = serializers.BooleanField()
            self.fields["staff_only_field2"] = serializers.CharField()

However, the issue with this is that I have to manually initialize each field, thus losing the handy ability of model serializers to automatically instantiate the fields that have the same name as the corresponding model’s fields.

What I’d like to be able to do is just dynamically extend the initial list of fields conditionally, and then have the serializer take care of the rest during initialization.

This is the closest I’ve come to it:

class MySerializer(serializers.ModelSerializer):
    class Meta:
        model = SomeModel
        fields = ["public_field1", "public_field2"]
        staff_only_fields = ["staff_only_field1", "staff_only_field2"]

    def __init__(self, *args, **kwargs):
        self.Meta.fields.extend(self.Meta.staff_only_fields)
        super().__init__(*args, **kwargs)

        if not self.context["request"].user.is_staff:
            for field in self.Meta.staff_only_fields:
                self.fields.pop(field)

The good thing about this approach is it achieves what I want—the serializer doesn’t have to know the type of the fields and if I want to change anything about them I just need to edit the two lists in Meta.

The bad thing is that this is a little too contrived (hacky?) for my liking: I first add all the private fields to the field list, then call __init__, and then, if the user isn’t a staff member, I pop them back off. The problem is essentially that I have no access to self.context until I have init'ed the serializer. At which point, it’s too late to work on Meta.fields because an OrderedDict with all the actual fields has been created.

Is there anything more elegant (and possibly efficient) that I can do to achieve this?

I think the issue you think you’re running into is that you don’t believe you have access to self.context["request"].user.is_staff before you call super(), preventing you from modifying self.fields before it.

However (if I’m reading this right), you can reference the user object through kwargs['context']['request'].user to move that if statement before the call to super, allowing you to add the staff_only_fields only when necessary.

You understood me right!

I’ll give what you said a try and report back.