79

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 ?

2
  • stackoverflow.com/questions/14573102/… - The answer cannot get any better - it if from the author himself. Commented Jul 7, 2016 at 13:47
  • @karthikr This doesn't really explain how to do what I want. It just explains how to nest M2M. I don't want to do that. I want to include related fields and the all tag in the serializer 'fields' Commented Jul 8, 2016 at 10:40

8 Answers 8

73

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']
Sign up to request clarification or add additional context in comments.

3 Comments

I just want to let you know that you are a lifesaver and thank you so much for this snippet.
Why on earth isn't this built-in. Or why aren't reverse relationships included in 'all'
I'm sitting here in 2024 and this solution still works for Django 4.2 and DRF 3.14.0. Thanks, @hugoruscitti!
34

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.

3 Comments

this would not work in case of reverse relations -> django-rest-framework.org/api-guide/relations/…
@IshanKhare Updated to show that this can also work with reverse relationships.
This should really be accepted as the correct answer, as it solves the problem within the provided functionality of the framework without additional hacking.
32

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...

1 Comment

man, this is such a basic feature, every time I look into django it looks like everything is broken or set on stone, no way to make smart factories or anything, just write a ton of useless code for any simple thing you want to do. thanks for the answer
30

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!

2 Comments

this one is so clean!
This won't work if you declare extra fields in your serializer, that are not in the model. E.g. if you need to send extra data at the point of serialization. Therefore, it shouldn't be accepted as a generally working solution.
10

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__'

1 Comment

This is a read-only field!
3

to include all the fields and the other fields defined in your serializer you can just say exclude = ()

class ToppingSerializer(serializers.HyperlinkedModelSerializer):
   pizzas = '<>' #the extra attribute value
    class Meta:
        model = Topping
        exclude = ()

This will list all the field values with the extra argument pizzas

Comments

0

This is how i did it, much more easier

class OperativeForm(forms.ModelForm):
    class Meta:
        model = Operative
        fields = '__all__'
        exclude = ('name','objective',)
        widgets = {'__all__':'required'}

2 Comments

Getting this error : Cannot set both 'fields' and 'exclude' options on serializer
error in setting both fields and exclude options. but thanks for your effort.
0

Building on top of @Wand's wonderful answer:

def build_fields(mdl,extra=[],exclude=[]):
    fields = [field.name for field in mdl._meta.fields if field.name not in exclude]
    fields += extra
    return fields

Usage:

model = User
fields = build_fields(model, ['snippets'], ['password'])

Will return all fields from the User model, with the related field snippets, without the password field.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.