I'm trying to serialize foreikey to another name.
For example:
class User(models.Model):
user_name = models.CharField(..)
class Blog(models.Model):
user = models.ForeignKey(User, ...)
title = models.CharField(...)
class BlogSerializer(serializers.ModelSerializer):
class Meta:
model = Blog
fields = '__all__'
when use BlogSerializer(blog_instance).data can get the data:
{
"id": 1,
"user": 1,
"user_info": {"id": 1, "user_name": "admin"},
"title": "hello world"
}
Just create another serializer for User and add it as any field you want in the BlogSerializer. For example:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username')
class BlogSerializer(serializers.ModelSerializer):
user_info = UserSerializer(source='user')
class Meta:
model = Blog
fields = ('id', 'user', 'user_info', 'title')
Though I don't see much need of user with just the id since user_info already includes the id and other info. But if that is what you want then this is how to implement it
I think you just want a different representation for the user, you can make this trick to change that:
class BlogSerializer(serializers.ModelSerializer):
class Meta:
model = Blog
fields = '__all__'
def to_representation(self, instance):
ret = super().to_representation(instance)
# here you override the 'user' field, you can either use a dictionary
ret['user'] = {"id": instance.user.id, "user_name": instance.user.user_name}
# or better yet create an actual UserSerializer and get that representation, eg.
# ret['user'] = UserSerializer(context=self.context).to_representation(instance.user)
return ret
Related
I have to pass a request body as shown below.
{
"user_id": 1,
"skill": [
{
"id":1
},
{
"id":2
},
{
"id":3
}
]
}
Models looks like this:
class UserSkill(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
skill = models.ForeignKey(Skills, on_delete=models.CASCADE)
class Meta:
db_table = "user_skill"
Serializer looks like :
class UserSkillSerializer(serializers.ModelSerializer):
class Meta:
model = UserSkill
fields = ['user', 'skill']
Viewset is done like:
class UserSkillViewSet(viewsets.ModelViewSet):
queryset = UserSkill.objects.all()
serializer_class = UserSkillSerializer
I have to pass the above mentioned request body to this api and for each element in "skill", I have to create an object for the model "UserSkill".
Some one please help me with the changes I have to make to get this api working.
Thanks
class UserSkillSerializer(serializers.ModelSerializer):
class Meta:
model = UserSkill
fields = ['user', 'skill']
def create(self, validated_data):
user_id = validated_data.get('user_id')
skill_ids = validated_data.get('skill')
skill = [item.get("id") for item in skill_ids]
super().create({"user": user_id, "skill": skill})
Try it.
change your skill field in UserSkill model to ManyToManyField. like:
class UserSkill(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
skill = models.ManyToManyField(Skills)
class Meta:
db_table = "user_skill"
and you need to pass a request body like this.
{
"user": 1,
"skill": [1, 2, 3]
}
without any change in serializer and view classes your requirements applicated.
if you want to use ForeignKey you need to some implementation in your view and you must overwrite create function in ModelViewSet.
I have two serializers, one for Country and one for my model Foo, I want to save the object by using the foreign key for this model but it errors out whenever I try to validate.
I have this
class Actor(TLPWrappedModel, CommentableModel):
label = models.CharField(max_length=56, unique=True)
country_of_origin = models.ForeignKey(Country, on_delete=models.CASCADE)
class FooSerializer(serializers.ModelSerializer):
country_of_origin = CountrySerializer()
class Meta:
model = Actor
fields = [
'id',
'country_of_origin',
'label',
]
class Country(models.Model):
label = models.CharField(max_length=56, unique=True)
iso_code = models.CharField(max_length=3, unique=True)
class CountrySerializer(serializers.ModelSerializer):
class Meta:
model = Country
fields = [
'iso_code',
'label',
]
And this is what I'm trying to do
serializers = FooSerializer(data={'label': 'Foobar',
'country_of_origin': self.country.id})
serializers.is_valid()
print(serializers.errors)
print(serializers.validated_data)
serializers.save()
But I get this error {'country_of_origin': {'non_field_errors': [ErrorDetail(string='Invalid data. Expected a dictionary, but got int.', code='invalid')]}}
is it possible to use the ID of a foreign key to validate and create the object using the serializer?
We can update the to_represent of the FooSerializer to get the desired output
Try
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Actor
fields = [
'id',
'country_of_origin',
'label',
]
def to_representation(self, instance):
data = super().to_representation(instance)
data['country_of_origin'] = CountrySerializer(instance.country_of_origin)
return data
serializers = FooSerializer(data={'label': 'Foobar', 'country_of_origin': self.country})
serializers.is_valid(raise_expection=True)
serializers.save()
In this I have updated the code to assign the self.country as country_of_origin. Also, I am using the raise_expection in the is_valid method. This method will return the errors as 400 response.
Try
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Actor
fields = [
'id',
'country_of_origin',
'label',
]
You can safely drop defining the 'country of origin` in the FooSerializer
contry_of_origin would be an object, and you are passing an id for it.
Do you need a nested serializer? : country_of_origin = CountrySerializer()
For the example that you have given, I would suggest you to change it to PrimaryKeyRelatedField()
Your serializer would look like:
class FooSerializer(serializers.ModelSerializer):
country_of_origin = serializers.PrimaryKeyRelatedField()
class Meta:
model = Actor
fields = [
'id',
'country_of_origin',
'label',
]
i created a model named 'Post'.
here is the code:
class Post(models.Model):
body = models.TextField(max_length=10000)
date = models.DateTimeField(default=datetime.now, blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
ordering = ['-date']
i want to get all objects of Post model with users firstname and lastname.
in views.py:
#api_view(['GET'])
#permission_classes((IsAuthenticated,))
def allPost(request):
allpost = Post.objects.all()
serializer = PostSerializers(allpost, many=True)
return Response(serializer.data)
in serialisers.py:
class UserSerializers(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
class PostSerializers(serializers.ModelSerializer):
user = serializers.RelatedField(many=True)
class Meta:
model = Post
fields = ('body','date','user')
You can make a serialzier with firstname and lastname:
class SimpleUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('first_name', 'last_name')
and then use that serializer as subserialiser:
class PostSerializer(serializers.ModelSerializer):
user = SimpleUserSerializer()
class Meta:
model = Post
fields = ('body','date','user')
This generates a JSON blob like:
{
"body": "Sample body text",
"date": "2020-12-11T12:34:56.789Z",
"user": {
"first_name": "MyFirst",
"last_name": "MyLast"
}
}
or you use make use of two CharFields:
class PostSerializer(serializers.ModelSerializer):
first_name = serializers.CharField(source='user.first_name', read_only=True)
last_name = serializers.CharField(source='user.last_name', read_only=True)
class Meta:
model = Post
fields = ('body','date')
this generates as JSON blob:
{
"body": "Sample body text",
"date": "2020-12-11T12:34:56.789Z",
"first_name": "MyFirst",
"last_name": "MyLast"
}
Note: The name of a serializer class is normally singular, so PostSerializer instead of
PostSerializers.
There is no need to use related field in this case. many=True should be used if you're passing multiple objects (like you did with posts).
What you should do is user your UserSerializer in PostSerializer
class PostSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Post
fields = ('body','date','user')
Read docs for more info
https://www.django-rest-framework.org/api-guide/relations/#nested-relationships
My models:
class Category(models.Model):
name = models.Charfield()..
code = models.Charfield()...
class Movie(models.Model):
name = models.Charfield()..
categories = models.ManyToManyField(Category)
My serializer
class MovieSerializer(serializers.ModelSerialier):
class Meta:
model= Movie
fields = ['name',
'categories',
]
When i get a Movie by id it returns:
[
{"name": "My Movie",
"categories": [1, 2, 3],
}
]
I want my response to be :
....
"categories": ["Fun", "Adventure", ...]
What I've tried
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['name']
def to_representation(self, instance):
return instance.name
It gives me the expected result, but i dont think it is a clean way to do it
Option 1
You can use your models __str__ method for this. Just use a StringRelatedField:
class MovieSerializer(serializers.ModelSerialier):
categories = serializers.StringRelatedField(many=True)
class Meta:
model= Movie
fields = ['name', 'categories']
and add the __str__ method to your category model:
class Category(models.Model):
name = models.Charfield()
code = models.Charfield()
def __str__(self):
return self.name
Option 2
Use a custom RelatedField, like described in the docs:
class CategoryListingField(serializers.RelatedField):
def to_representation(self, value):
return value.name
class MovieSerializer(serializers.ModelSerialier):
categories = CategoryListingField(many=True)
class Meta:
model= Movie
fields = ['name', 'categories']
This is close to what you did, just a little bit cleaner, as a RelatedField is designed to accomplish exactly that. Using a custom serializer is not the way to go here, since you dont want to serialize the object itself, but only a specific field from it. Per default, this is the pk (or id), but by specifying your own related field, you can change it to whatever you want.
Add depth = 1 to your serializer.
This will give you something like:
[
{
"name": "My Movie",
"categories": [
{ "id": 1, "name": "Fun", "code": "category_code" }
]
}
]
then you can extract whatever you want from this object.
I have DB structure like this:
common_org:
id,
code,
title
special_org:
id,
code,
address
Okay. For this I've created models:
class CommonOrg(models.Model):
code = models.CharField()
title = models.CharField()
class SpecialOrg(models.Model):
code = models.CharField(null=True)
address= models.CharField()
Now I want to output SpecialOrg as usual, but if I have CommonOrg.code == SpecialOrg.code, then attach CommonOrg to SpecialOrg like this:
{
"id": 1,
"code": "XYZ",
"address": "ADDRESS",
"common_org": {
"id": 2,
"code": "XYZ",
"title": "TITLE"
}
}
Now I have solution with serializers.RelatedField:
class CommonOrgField(serializers.RelatedField):
def to_representation(self, value):
class _CommonOrgSerializer(serializers.ModelSerializer):
class Meta:
model = CommonOrg
fields = '__all__'
representation = None
try:
common_org = CommonOrg.objects.get(code=value)
representation = _CommonOrgSerializer(common_org).data
except CommonOrg.DoesNotExist:
pass
return representation
class SpecialOrgSerializer(serializers.ModelSerializer):
class Meta:
model = SpecialOrg
fields = '__all__'
common_org = CommonOrgField(read_only=True, source='code')
But it looks ugly for me.
So the question is: what is the right approach to implement it in DRF? Database is not mine and I cannot to alter it.
In most cases where I am to add a read only field to a serializer, when the field is not related to the current model at database level, I'd use a serializer method field. You could use serializer method field like this in your case:
class SpecialOrgSerializer(serializers.ModelSerializer):
common_org = serializers.SerializerMethodField()
class Meta:
model = SpecialOrg
fields = '__all__'
def get_common_org(self, obj):
try:
common_org = CommonOrg.objects.get(code=value)
except CommonOrg.DoesNotExist:
return None
return _CommonOrgSerializer(common_org).data