I have Language model like this
class Language(models.Model):
name = models.CharField()
class Post(models.Model)
title = models.CharField()
language = models.ForeignKey(Language, models.SET_DEFAULT, default=1)
class PostSerializer(serializers.ModelSerializer):
language = serializers.CharField(source='language.name')
class PostCreateAPIView(generics.ListCreateAPIView):
serializer_class = serializers.PostSerializer
Post payload
{
"title":"title_1",
"language": "en" // instead of primary key
}
I wanted to map language name into object without using pk in payload.
Inside PostSerializer, change the representation of languge into SlugRelatedField and set slug field to 'name'. This accepts "name" from Language Model in PostSerializer language field.
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['title', 'languge']
def get_fields(self):
fields = super().get_fields()
view = self.context.get('view')
if view and view.action == 'create':
fields['language'] = serializers.SlugRelatedField(slug_field='name', queryset=Language.objects.all())
return fields
One way to do this is that, you pass the language with context to the serializer, and in the serializer, you use SerializerMethodField to get the pk/object using the language value.
class PostSerializer(serializers.ModelSerializer):
language = serializers.SerializerMethodField()
def get_language(self):
return Language.objects.get(name=self.context["language_name"]) # return anything you want from here.
To pass serializer
serialized_data = PostSerializer(data, context={"language_name": self.request.data["language_name"]})
Need to use the SlugRelatedField
class PostSerializer(serializers.ModelSerializer):
language = serializers.SlugRelatedField(slug_field='name', queryset=Language.objects.all())
Related
Trying out serialising parent and child model.Here are my models:
class HealthQuotation(models.Model):
quotation_no = models.CharField(max_length=50)
insuredpersons = models.IntegerField()
mobile_no = models.CharField(max_length=10)
def __str__(self):
return self.quotation_no
class HealthQuotationMember(models.Model):
premium = models.FloatField(null=True)
suminsured = models.FloatField()
quotation = models.ForeignKey(HealthQuotation,on_delete=models.CASCADE)
def __str__(self):
return str(self.quotation)
Here are my serializers:
class HealthQuotationMemberSerializer(serializers.ModelSerializer):
class Meta:
model = HealthQuotationMember
fields= "__all__"
class HealthQuotationSerializer(serializers.ModelSerializer):
members = HealthQuotationMemberSerializer(many=True)
class Meta:
model = HealthQuotation
fields = ['id','members']
On Serialising parent model with parent serializer, Django throws error "Got AttributeError when attempting to get a value for field members on serializer HealthQuotationSerializer. The serializer field might be named incorrectly and not match any attribute or key on the HealthQuotation instance. Original exception text was: 'HealthQuotation' object has no attribute".
Because you don't have members field in your model... Try to change your serializer as following and see if it works:
class HealthQuotationSerializer(serializers.ModelSerializer):
quotation = HealthQuotationMemberSerializer()
class Meta:
model = HealthQuotation
fields = ['id','quotation']
Note that I've removed many=True because there will be always one object per this data (ForeignKey). when you have more than one object such as Many2Many you should use many=True.
You have "HealthQuotation" as a parent and "HealthQuotationMember" as a child.
Now, you have decided to retrieve data from parent "HealthQuotation"
and its associated children which will come from "HealthQuotationMember", right?
To achieve that you can use Django SerializerMethodField():
Your serializers.py should look like:
class HealthQuotationMemberSerializer(serializers.ModelSerializer):
class Meta:
model = HealthQuotationMember
fields= '__all__'
class HealthQuotationSerializer(serializers.ModelSerializer):
members = serializers.SerializerMethodField() # I am using SerializerMethodField()
class Meta:
model = HealthQuotation
fields = '__all__'
def get_members(self, quotation):
q = HealthQuotationMember.objects.filter(quotation = quotation)
serializer = HealthQuotationMemberSerializer(q, many=True)
return serializer.data
Your views.py
class GetHealthQuotationList(ListAPIView):
serializer_class = HealthQuotationSerializer
queryset = HealthQuotation.objects.all()
Your url.py should be:
path('get-health-quotation-list', GetHealthQuotationList.as_view()),
NOTE: In case you plan to retrieve data from child table and find its associated parent, then your serializer should be good to go without many=True argument.
I am using this serializer to query and return some json objects.
class MyModelPathSerializer(serializers.ModelSerializer):
class Meta():
model = MyModel
fields = ('setup', 'path')
depth=1
I am interesting in returning a property of of my model rather than a field though.
class MyModel(models.Model):
setup = models.ForeignKey(Setup, on_delete=models.CASCADE)
#property
def path(self):
return '/some/path/'
Is there a way to modify the serializer class so that also properties are returned?
You can use SerializerMethodField to return the property of your model instance. This is a read-only field.
class MyModelPathSerializer(serializers.ModelSerializer):
path = serializers.SerializerMethodField()
class Meta:
model = MyModel
fields = ("setup", "path")
depth = 1
def get_path(self, model_instance):
return model_instance.path
https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
I feel like this is a super basic question but am having trouble finding the answer in the DRF docs.
Let's say I have a models.py set up like so:
#models.py
class Person(models.Model):
name = models.CharField(max_length=20)
address = models.CharField(max_length=20)
class House(models.Model):
name = models.CharField(max_length=20)
owner = models.ForeignKey(Person)
And I have a ModelSerializer set up like so:
#serializers.py
class House(serializers.ModelSerializer):
class Meta:
model = House
fields = '__all__'
What I want to do is to be able to POST new House objects but instead of having to supply the pk of the Person object, I want to be able to supply the name of the Person object.
E.g.
post = {'name': 'Blue House', 'owner': 'Timothy'}
The actual models I'm using have several ForeignKey fields so I want to know the most canonical way of doing this.
One solution may be to use a SlugRelatedField
#serializers.py
class House(serializers.ModelSerializer):
owner = serializers.SlugRelatedField(
slug_field="name", queryset=Person.objects.all(),
)
class Meta:
model = House
fields = '__all__'
This will also change the representation of your serializer though, so it will display the Person's name when you render it. If you need to render the Person's primary key then you could either override the House serializers to_representation() method, or you could implement a small custom serializer field by inheriting SlugRelatedField and overriding to_representation() on that instead.
Change your serializer as below by overriding the create() method
class House(serializers.ModelSerializer):
owner = serializers.CharField()
class Meta:
model = House
fields = '__all__'
def create(self, validated_data):
owner = validated_data['owner']
person_instance = Person.objects.get(owner=owner)
return House.objects.create(owner=person_instance, **validated_data)
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.
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