Kyle O'Brien
Kyle O'Brien

Reputation: 952

Django Rest Framework - Using Reverse Relationships with __all__

So, I completely get the use of reverse relationships in the serializer for Django Rest Framework. And I've successfully implemented a solution that gives me what I want to see in the data output.

However, the final implementation makes me a little unhappy.

Essentially, I wanted to have an API result that gave me complete nested information of my related model - so Modules with all its fields and then its reverse relationship, SubModules, with all its fields.

So, in my example, in order we have Modules which in turn have many SubModules.

The SubModules models.py has a relationship like:

module = models.ForeignKey(Module, related_name='sub_modules')

Obviously, there is no direct sub_modules property defined in the Modules models.py.

So, in the Modules serializer.py, I wanted to make sure that the full sub_modules objects were returned in the API call to /api/modules - not URLs or IDs.

So, my initial implementation (not what I wanted) was something like:

class ModuleSerializer(serializers.ModelSerializer):

    class Meta:
        model = Module
        fields = '__all__'
        depth = 1

However, this returns the following (notice, no sub_modules):

{
    "id": 1,
    "created_at": "2017-02-13T11:19:28.665000Z",
    "updated_at": "2017-02-13T11:19:28.665000Z",
    "order": 1,
    "inactive": null,
    "name": "Module 1",
    "description": "Module 1",
    "price": "100.0000000000"
},

The implementation that eventually worked (but whose pattern I'm not happy with) was something like:

class ModuleSerializer(serializers.ModelSerializer):

    class Meta:
        model = Module
        fields = ('id', 'created_at', 'updated_at', 'order', 'inactive',
                  'price', 'name', 'description', 'sub_modules')
        depth = 1

This, correctly, gave me:

{
    "id": 1,
    "created_at": "2017-02-13T11:19:28.665000Z",
    "updated_at": "2017-02-13T11:19:28.665000Z",
    "order": 1,
    "inactive": null,
    "price": "100.0000000000",
    "name": "Module 1",
    "description": "Module 1",
    "sub_modules": [
        {
            "id": 1,
            "created_at": "2017-02-13T14:16:00.478429Z",
            "updated_at": "2017-02-13T14:16:00.478471Z",
            "order": 1,
            "inactive": null,
            "name": "SubModule 1",
            "description": "SubModule 1",
            "price": "100.0000000",
            "module": 1
        }
    ]
},

Why don't I like this pattern?

The thing that makes Django Rest Framework my goto REST implementation of choice (over say, Flask RESTful for example) is that I can usually rely on my pattern involving DRY code and the implementation of consistent single sources of truth.

I want to be able to keep my data layer where it should be - at my model level and change only that and have it feedback, by default, to the serializer. Obviously, then I'd manage exceptions on the serializer level when I want the data to transform or be absent or whatever (a good example is keeping the password field off of the users endpoint data).

If I have to maintain the fields value in the Meta class, I'm inherently managing two sources of truth for my Modules entity.

What I'd like to be able to do

I'd like my sub_modules object to be returned by default by the fields = '__all__' expression. But, it's explicitly stated, this isn't done by default in the DRF docs.

So, I tried a couple of implementations of a manual sub_modules field on the Modules serializer.py (an acceptable implementation in my opinion since it is a reverse relationship).

Remember, the following are on the Modules serializer.py:

RelatedField

sub_modules = serializers.RelatedField()
Result

AssertionError: Relational field must provide a queryset argument, override get_queryset, or set read_only=True.

RelatedField - with some params

sub_modules = serializers.RelatedField(read_only=True)
Result

NotImplementedError at /modules/ RelatedField.to_representation() must be implemented for field sub_modules

Say what??

So, what is my final question?

Basically, I'm looking for a pattern that allows me to link my serializer to my model AND include the reverse relationships in a manner where I don't have to define my field specification explicitly on the serializer.py.

I'm the biggest advocate of explicit over implicit but, the model is quite explicit enough, in my opinion.

Upvotes: 3

Views: 2122

Answers (1)

zubhav
zubhav

Reputation: 1559

Create a serializer for SubModules with fields set to all. Then, refer to this serializer from your ModuleSerializer like so:

class ModuleSerializer(serializers.ModelSerializer):
  sub_modules = SubModuleSerializer(many=True)

  class Meta:
      fields = '__all __'
      model = Modules
      depth = 1
  ...

This should give you the nested representation that you are looking for while preserving a DRY format.

Haven't actually tried this out but should work

Upvotes: 4

Related Questions