ModelSerializer: include all fields and all properties of a model [duplicate] - django

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 ?

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

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

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.

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!

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

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

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

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.

Related

When don't we use "__all__" in ModelSerializer in Django Rest Framework

This is just my curiosity but I will be very happy if anyone answers my question.
I am using Django Rest Framework but I'm a beginner. In serializers.py, I use ModelSerializer and "all" to fields attribute.
This is an example.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__"
And then, I just thought
when don't we use "__all__" in serializers.py??
As long as we create models.py in advance, I think we usually use all fields in each Model.
I would like you to teach me when we omit specific fields that come from each Model.
Thank you.
So the second question is a bit harder to explain in a comment:
If we use some fields of all fields in Model, how do we store information of the rest of fields?
Various cases:
Fields with defaults:
class Log(models.Model):
message = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class LogSerializer(serializers.ModelSerializer):
class Meta:
model = Log
fields = ('message',)
For autogenerated, think user profile models via the post_save signal or calculated fields:
class OrderLine(models.Model):
order = models.ForeignKey(Order)
name = models.CharField(max_length=200)
quantity = models.IntegerField()
price = models.DecimalField()
class OrderLineSerializer(serializers.ModelSerializer):
order = serializers.PrimaryKeyRelatedField()
product = serializers.IntegerField()
class Meta:
model = OrderLine
fields = ('quantity', 'product', 'order')
In this case, the product is a primary key for a product. The serializer will have a save method that looks up the product and put it's name and price on the OrderLine. This is standard practice as you cannot reference a product in your orders, else your orders would change if you change (the price of) your product.
And derived from request:
class BlogPost(models.Model):
author = models.ForeignKey(User)
post = models.TextField()
class BlogPostSerializer(serializers.ModelSerializer):
class Meta:
model = BlogPost
fields = ('post',)
def create(self, validated_data):
instance = BlogPost(**validated_data)
instance.author = self.context['request'].user
instance.save()
return instance
This is pretty much the common cases.
There are many cases, but I think the two main ones are:
When you don't want all fields to be returned by the serializer.
When you need some method of the serializer to know its fields. In such case, you should traverse fields array, but it doesn't work if you use __all__, only if you have an actual list of fields.

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

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 ?
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']
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...
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.
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!
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__'
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
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'}
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.

Add Serializer on Reverse Relationship - Django Rest Framework

I have a Cart model and a CartItem model. The CartItem model has a ForeignKey to the Cart model.
Using Django Rest Framework I have a view where the API user can display the Cart, and obviously then I want to include the CartItem in the respone.
I set up my Serializer like this:
class CartSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
cartitem_set = CartItemSerializer(read_only=True)
class Meta:
model = Cart
depth = 1
fields = (
'id',
'user',
'date_created',
'voucher',
'carrier',
'currency',
'cartitem_set',
)
My problem is the second line, cartitem_set = CartItemSerializer(read_only=True).
I get AttributeErrors saying 'RelatedManager' object has no attribute 'product'. ('product' is a field in the CartItem model. If I exclude product from the CartItemSerializer I just get a new AttributeError with the next field and so on. No matter if I only leave 1 or all fields in the Serializer, I will get a error.
My guess is that for some reason Django REST Framework does not support adding Serializers to reverse relationships like this. Am I wrong? How should I do this?
PS
The reason why I want to use the CartItemSerializer() is because I want to have control of what is displayed in the response.
Ahmed Hosny was correct in his answer. It required the many parameter to be set to True to work.
So final version of the CartSerializer looked like this:
class CartSerializer(serializers.ModelSerializer):
cartitem_set = CartItemSerializer(read_only=True, many=True) # many=True is required
class Meta:
model = Cart
depth = 1
fields = (
'id',
'date_created',
'voucher',
'carrier',
'currency',
'cartitem_set',
)
It's important to define a related name in your models, and to use that related name in the serializer relationship:
class Cart(models.Model):
name = models.CharField(max_length=500)
class CartItem(models.Model):
cart = models.ForeignKey(Cart, related_name='cart_items')
items = models.IntegerField()
Then in your serializer definition you use those exact names:
class CartSerializer(serializers.ModelSerializer):
cart_items = CartItemSerializer(read_only=True)
class Meta:
model = Cart
fields = ('name', 'cart_items',)
It would be wise to share your whole code, that is model and serializers classes. However, perhaps this can help debug your error,
My serializer classes
class CartItemSerializer(serializers.ModelSerializer):
class Meta:
model = CartItem
fields = ('id')
class CartSerializer(serializers.ModelSerializer):
#take note of the spelling of the defined var
_cartItems = CartItemSerializer()
class Meta:
model = Cart
fields = ('id','_cartItems')
Now for the Models
class CartItem(models.Model):
_cartItems = models.ForeignKey(Subject, on_delete=models.PROTECT)
#Protect Forbids the deletion of the referenced object. To delete it you will have to delete all objects that reference it manually. SQL equivalent: RESTRICT.
class Meta:
ordering = ('id',)
class Cart(models.Model):
class Meta:
ordering = ('id',)
For a detailed overview of relationships in django-rest-framework, please refer their official documentation

How do you filter a nested serializer in Django Rest Framework?

In Django Rest Framework, how do you filter a serializer when it's nested in another serializer?
My filters are imposed in the DRF viewsets, but when you call a serializer from inside another serializer, the viewset of the nested serializer never gets called, so the nested results appear unfiltered.
I have tried adding a filter on originating viewset, but it doesn't seem to filter the nested results because the nested results get called as a separate pre-fretched query. (The nested serializer is a reverse lookup, you see.)
Is it possible to add a get_queryset() override in the nested serializer itself (moving it out of the viewset), to add the filter there? I've tried that, too, with no luck.
This is what I tried, but it doesn't even seem to get called:
class QuestionnaireSerializer(serializers.ModelSerializer):
edition = EditionSerializer(read_only=True)
company = serializers.StringRelatedField(read_only=True)
class Meta:
model = Questionnaire
def get_queryset(self):
query = super(QuestionnaireSerializer, self).get_queryset(instance)
if not self.request.user.is_staff:
query = query.filter(user=self.request.user, edition__hide=False)
return query
You can subclass the ListSerializer and overwrite the to_representation method.
By default the to_representation method calls data.all() on the nested queryset. So you effectively need to make data = data.filter(**your_filters) before the method is called. Then you need to add your subclassed ListSerializer as the list_serializer_class on the meta of the nested serializer.
subclass ListSerializer, overwriting to_representation and then calling super
add subclassed ListSerializer as the meta list_serializer_class on the nested Serializer
Here is the relevant code for your sample.
class FilteredListSerializer(serializers.ListSerializer):
def to_representation(self, data):
data = data.filter(user=self.context['request'].user, edition__hide=False)
return super(FilteredListSerializer, self).to_representation(data)
class EditionSerializer(serializers.ModelSerializer):
class Meta:
list_serializer_class = FilteredListSerializer
model = Edition
class QuestionnaireSerializer(serializers.ModelSerializer):
edition = EditionSerializer(read_only=True)
company = serializers.StringRelatedField(read_only=True)
class Meta:
model = Questionnaire
While all the above answers work, I find the use of Django's Prefetch object the easiest way of all.
Say a Restaurant obj has a lot of MenuItems, some of which are is_removed == True, and you only want those that are not removed.
In RestaurantViewSet, do something like
from django.db.models import Prefetch
queryset = Restaurant.objects.prefetch_related(
Prefetch('menu_items', queryset=MenuItem.objects.filter(is_removed=False), to_attr='filtered_menu_items')
)
In RestaurantSerializer, do something like
class RestaurantSerializer(serializers.ModelSerializer):
menu_items = MenuItemSerializer(source='filtered_menu_items', many=True, read_only=True)
Tested many solutions from SO and other places.
Found only one working solution for Django 2.0 + DRF 3.7.7.
Define a method in model which has nested class. Craft a filter that will fit your needs.
class Channel(models.Model):
name = models.CharField(max_length=40)
number = models.IntegerField(unique=True)
active = models.BooleanField(default=True)
def current_epg(self):
return Epg.objects.filter(channel=self, end__gt=datetime.now()).order_by("end")[:6]
class Epg(models.Model):
start = models.DateTimeField()
end = models.DateTimeField(db_index=True)
title = models.CharField(max_length=300)
description = models.CharField(max_length=800)
channel = models.ForeignKey(Channel, related_name='onair', on_delete=models.CASCADE)
.
class EpgSerializer(serializers.ModelSerializer):
class Meta:
model = Epg
fields = ('channel', 'start', 'end', 'title', 'description',)
class ChannelSerializer(serializers.ModelSerializer):
onair = EpgSerializer(many=True, read_only=True, source="current_epg")
class Meta:
model = Channel
fields = ('number', 'name', 'onair',)
Pay attention to source="current_epg" and you'll get the point.
I find it easier, and more straight forward, to use a SerializerMethodField on the serializer field you want to filter.
So you would do something like this.
class CarTypesSerializer(serializers.ModelSerializer):
class Meta:
model = CarType
fields = '__all__'
class CarSerializer(serializers.ModelSerializer):
car_types = serializers.SerializerMethodField()
class Meta:
model = Car
fields = '__all__'
def get_car_types(self, instance):
# Filter using the Car model instance and the CarType's related_name
# (which in this case defaults to car_types_set)
car_types_instances = instance.car_types_set.filter(brand="Toyota")
return CarTypesSerializer(car_types_instances, many=True).data
This saves you from having to create many overrides of the serializers.ListSerializer if you need different filtering criteria for different serializers.
It also has the extra benefit of seeing exactly what the filter does within the serializer instead of diving into a subclass definition.
Of course the downside is if you have a serializer with many nested objects that all need to be filtered in some way. It could cause the serializer code to greatly increase. It's up to you how you would like to filter.
Hope this helps!
When a serializer is instantiated and many=True is passed, a
ListSerializer instance will be created. The serializer class then
becomes a child of the parent ListSerializer
This method takes the target of the field as the value argument, and
should return the representation that should be used to serialize the
target. The value argument will typically be a model instance.
Below is the example of the nested serializer
class UserSerializer(serializers.ModelSerializer):
""" Here many=True is passed, So a ListSerializer instance will be
created"""
system = SystemSerializer(many=True, read_only=True)
class Meta:
model = UserProfile
fields = ('system', 'name')
class FilteredListSerializer(serializers.ListSerializer):
"""Serializer to filter the active system, which is a boolen field in
System Model. The value argument to to_representation() method is
the model instance"""
def to_representation(self, data):
data = data.filter(system_active=True)
return super(FilteredListSerializer, self).to_representation(data)
class SystemSerializer(serializers.ModelSerializer):
mac_id = serializers.CharField(source='id')
system_name = serializers.CharField(source='name')
serial_number = serializers.CharField(source='serial')
class Meta:
model = System
list_serializer_class = FilteredListSerializer
fields = (
'mac_id', 'serial_number', 'system_name', 'system_active',
)
In view:
class SystemView(viewsets.GenericViewSet, viewsets.ViewSet):
def retrieve(self, request, email=None):
data = get_object_or_404(UserProfile.objects.all(), email=email)
serializer = UserSerializer(data)
return Response(serializer.data)
The following worked for me, from self.context['view'], You can get the filter params inside the serializer and use it however you want.
class ShipmentDocumentSerializer(serializers.ModelSerializer):
class Meta:
model = Document
fields = ['id', 'created_date', 'consignment', 'document', 'org', 'title' ]
class ShipmentDocumentTypeSerializer(serializers.ModelSerializer):
documents = serializers.SerializerMethodField()
class Meta:
model = DocumentType
fields = ['id', 'type', 'documents']
def get_documents(self, instance):
consignment_id=self.context['view'].kwargs['consignment_id']
queryset = Document.objects.filter(consignment__id=consignment_id)
return ShipmentDocumentSerializer(queryset, many=True).data

django use model choices in modelform

I was wondering how i should use my model choices options, in my ModelForm.
Example(model):
class NPCGuild(models.Model):
CATEGORIES=(
('COM', 'Combat'),
('CRA', 'Crafting'),
('WAR', 'Warfare'),
)
faction = models.ForeignKey(Faction)
category = models.CharField(max_length=3, choices=CATEGORIES)
name = models.CharField(max_length=63)
My Form:
class NPCGuildForm(forms.ModelForm):
name = forms.CharField()
category = forms.CharField(
some widget?)
faction_set = Faction.objects.all()
faction = forms.ModelChoiceField(queryset=faction_set, empty_label="Faction", required=True)
class Meta:
model = NPCGuild
fields = ['name', 'category', 'faction']
As you can see, im not sure what i should be doing to get my choices from my model as a choicefield. Maybe it can be done with a ModelChoiceField as well, but then how to get the choices in it?
You should specify model in the Meta class and fields will be generated automatically:
class NPCGuildForm(forms.ModelForm):
class Meta:
model = NPCGuild
You can add extra form fields if you want to. Read more about ModelFroms.
Update As karthikr mentioned in the comment. If you want to set available choices in a form field, you have to use forms.ChoiceField, like this:
category = forms.ChoiceField(choices=NPCGuild.CATEGORIES)
I will do it differently, just keep in mind that you can generate the whole form from the model automatically, without having to create each field again, and this is one of the beauty of Django which save you time, here is how I will do it, in your form file:
class NPCGuildForm(forms.ModelForm):
class Meta:
model = NPCGuild
fields = ['name', 'category', 'faction']
widgets = {
'category' : forms.Select()
}