Django Rest framework, how to include '__all__' fields and a related field in ModelSerializer ?

46,696

Solution 1

I just checked the source code of Django Rest Framework. The behaviour you want seems not to be supported in the Framework.

The fields option must be a list, a tuple or the text __all__.

Here is a snippet of the relevant source code:

    ALL_FIELDS = '__all__'
    if fields and fields != ALL_FIELDS and not isinstance(fields, (list, tuple)):
        raise TypeError(
            'The `fields` option must be a list or tuple or "__all__". '
            'Got %s.' % type(fields).__name__
        )

You cannot add 'all' additionally to the tuple or list with fields...

Solution 2

Like @DanEEStart said, DjangoRestFramework don't have a simple way to extend the 'all' value for fields, because the get_field_names methods seems to be designed to work that way.

But fortunately you can override this method to allow a simple way to include all fields and relations without enumerate a tons of fields.

I override this method like this:

class ToppingSerializer(serializers.ModelSerializer):

    class Meta:
        model = Topping
        fields = '__all__'
        extra_fields = ['pizzas']

    def get_field_names(self, declared_fields, info):
        expanded_fields = super(ToppingSerializer, self).get_field_names(declared_fields, info)

        if getattr(self.Meta, 'extra_fields', None):
            return expanded_fields + self.Meta.extra_fields
        else:
            return expanded_fields

Note that this method only change the behaviour of this serializer, and the extra_fields attribute only works on this serializer class.

If you have a tons of serializer like this, you can create a intermediate class to include this get_fields_names method in one place and reuse'em many times. Some like this:

class CustomSerializer(serializers.HyperlinkedModelSerializer):

    def get_field_names(self, declared_fields, info):
        expanded_fields = super(CustomSerializer, self).get_field_names(declared_fields, info)

        if getattr(self.Meta, 'extra_fields', None):
            return expanded_fields + self.Meta.extra_fields
        else:
            return expanded_fields


class ToppingSerializer(CustomSerializer):

    class Meta:
        model = Topping
        fields = '__all__'
        extra_fields = ['pizzas']

class AnotherSerializer(CustomSerializer):

    class Meta:
        model = Post
        fields = '__all__'
        extra_fields = ['comments']

Solution 3

The fields="__all__" option can work by specifying an additional field manually as per the following examples. This is by far the cleanest solution around for this issue.

Nested Relationships

http://www.django-rest-framework.org/api-guide/relations/#nested-relationships

class TrackSerializer(serializers.ModelSerializer):
    class Meta:
        model = Track
        fields = '__all__'

class AlbumSerializer(serializers.ModelSerializer):
    tracks = TrackSerializer(many=True, read_only=True)

    class Meta:
        model = Album
        fields = '__all__'

I would assume this would work for any of the other related field options listed on the same page: http://www.django-rest-framework.org/api-guide/relations/#serializer-relations

Reverse relation example

class TrackSerializer(serializers.ModelSerializer):
    album = AlbumSerializer(source='album_id')

    class Meta:
        model = Track
        fields = '__all__'

Note: Created using Django Rest Framework version 3.6.2, subject to change. Please add a comment if any future changes break any examples posted above.

Solution 4

Hi I could achieve the expected result by using Django's _meta API , which seems to be available since Django 1.11. So in my serializer I did:

model = MyModel
fields = [field.name for field in model._meta.fields]
fields.append('any_other_field')

In programming there's always many ways to achieve the same result, but this one above, has really worked for me.

Cheers!

Solution 5

If you are trying to basically just add extra piece of information into the serialized object, you don't need to change the fields part at all. To add a field you do:

class MySerializer(serializers.ModelSerializer):
   ...
   new_field = serializers.SerializerMethodField('new_field_method')

   def new_field_method(self, modelPointer_):
      return "MY VALUE"

Then you can still use

class Meta:
   fields = '__all__'
Share:
46,696
Curtwagner1984
Author by

Curtwagner1984

Updated on July 08, 2022

Comments

  • Curtwagner1984
    Curtwagner1984 almost 2 years

    I have two models, one with M2M relation and a related name. I want to include all fields in the serializer and the related field.

    models.py:

    class Pizza(models.Model):
        name = models.CharField(max_length=50, unique=True)
        toppings = models.ManyToManyField(Topping, null=True, blank=True, related_name='pizzas')
    
    class Topping(models.Model):
        name = models.CharField(max_length=50, unique=True)
        price = models.IntegerField(default=0)
    

    serializer.py:

    class ToppingSerializer(serializers.ModelSerializer):
        class Meta:
            model = Topping
            fields = '__all__' 
    

    This works but it doesn't include the related field.

     fields = ['name', 'price', 'pizzas'] 
    

    This works exactly as I want, but what happens when Toppings model has a lot of fields. I want to do something like :

    fields = ['__all__', 'pizzas']
    

    This syntax results in an error saying:

    Field name __all__ is not valid for model

    Is there a way to achieve the wanted behavior? Or the fields must be typed manually when using a related name ?