This question already has answers here:
DRF: Simple foreign key assignment with nested serializers?
(12 answers)
Closed 6 months ago.
I have a model:
class Schedule(LogSaveDeleteMixin, models.Model):
name = models.CharField(max_length=40)
start_date = models.DateTimeField(null=True, blank=True)
class DeliveryChannel(LogSaveDeleteMixin, models.Model):
name = models.CharField(unique=True, max_length=40)
state = models.CharField(choices=DeliveryChannelState.choices, default='draft', max_length=15)
schedule = models.ForeignKey(Schedule, null=True, on_delete=models.SET_NULL)
And standard ModelSerializer:
class DeliveryChannelsSerializer(serializers.ModelSerializer):
class Meta:
model = DeliveryChannel
fields = '__all__'
class ScheduleSerializer(serializers.ModelSerializer):
class Meta:
model = Schedule
fields = '__all__'
For read requests (GET) I want to receive schedule field as a nested serializer:
{
"id": 0,
"name": "string",
"state": "archived",
"schedule": {
"id": 0,
"name": "string",
"start_date": "2022-08-12T02:41:32.187Z",
}
}
But for writing (POST, PUT) I want to get only schedule id:
{
"id": 0,
"name": "string",
"state": "archived",
"schedule": 0
}
I would like to know the best practices for do this
You can just set some fields in the DeliveryChannelsSerializer.
class DeliveryChannelsSerializer(serializers.ModelSerializer):
schedule = ScheduleSerializer(read_only = True)
schedule_id = serializers.IntegerField(write_only = True)
class Meta:
model = DeliveryChannel
fields = '__all__'
extra_fields = ['schedule_id']
The POST payload should be like the following:
{
"name": "string",
"state": "archived",
"schedule_id": 1
}
You can make two different serializers for DeliveryChannels.
class ScheduleSerializer(serializers.ModelSerializer):
class Meta:
model = Schedule
fields = ['id', 'name', 'start_date']
class DeliveryChannelSerializer(serializers.ModelSerializer):
# Serializer for POST,PUT
class Meta:
model = DeliveryChannel
fields = ['id', 'name', 'state', 'schedule']
class DeliveryChannelDetailSerializer(serializers.ModelSerializer):
# Serializer for GET
schedule = ScheduleSerializer()
class Meta:
model = DeliveryChannel
fields = ['id', 'name', 'state', 'schedule']
if you use ModelViewSet you need to override get_serializer_clas
def get_serializer_class(self):
if self.action == "list":
return DeliveryChannelDetailSerializer
return DeliveryChannelSerializer
Related
Is there any way to display the name of the user in the "likedBy" section of the view, instead of the user id? Using django rest framework
From view I get , ignore comments:
[
{
"id": 3,
"title": "ddsa",
"content": "dsadsa",
"created": "2021-02-10T08:07:42.758400Z",
"updated": "2021-02-10T08:07:42.758400Z",
"author": 1,
"category": [
{
"pk": 1,
"name": "Life"
}
],
"likedBy": [
1
],
"comments": [
{
"id": 2,
"content": "ghfa",
"created": "2021-02-10T08:08:02.407950Z",
"post": 3,
"author": 1,
"likedBy": [
1
]
}
]
}
]
Views.py:
class PostViewSet(FlexFieldsMixin, generics.ListAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
permit_list_expands = ['category', 'comments']
Models.py
class Post(models.Model):
title = models.CharField(max_length=255)
content = models.TextField()
category = models.ManyToManyField(Category, related_name='posts')
author = models.ForeignKey(User, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
likedBy = models.ManyToManyField(User, related_name='posts', blank=True)
class Meta:
ordering = ['-created']
Serializers.py:
class PostSerializer(FlexFieldsModelSerializer):
class Meta:
model = Post
fields = '__all__'
expandable_fields = {
'category': ('blogApi.CategorySerializer', {'many': True}),
'comments': ('blogApi.CommentSerializer', {'many': True}),
}
How serialize ManyToMany field to display text values
Given that you are not serializing a relation, but rather an attribute of your model which is related to your user, I believe you have to use a serializer.SerializerMethodField(). This allows you to do the following:
class PostSerializer(FlexFieldsModelSerializer):
liked_by = serializers.SerializerMethodField(method_name="get_user_likes")
class Meta:
model = Post
fields = (
"title",
"content",
"category",
"author",
"created",
"update",
"liked_by"
)
expandable_fields = {
'category': ('blogApi.CategorySerializer', {'many': True}),
'comments': ('blogApi.CommentSerializer', {'many': True}),
}
#classmethod
def get_user_likes(obj):
# do whatever you like here, but I tend to call a
# method on my model to keep my serializer file
# nice and tidy
# you'll need to define a UserSerializer
return UserSerializer(obj.get_user_likes(), many=True, read_only=True)
And in your Post model:
class Post(models.Model):
title = models.CharField(max_length=255)
content = models.TextField()
category = models.ManyToManyField(Category, related_name='posts')
author = models.ForeignKey(User, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
likedBy = models.ManyToManyField(User, related_name='posts', blank=True)
class Meta:
ordering = ['-created']
def get_user_likes(self):
return self.likedBy.all()
You can of course define the full method in your serializer, but as I said I like to keep the serializer as clean as possible and put all methods associated with models in my models.py.
#classmethod
def get_user_likes(obj):
# you'll need to define a UserSerializer
return UserSerializer(obj.likedBy.all(), many=True, read_only=True)
So you can set
likedBy = serializers.ReadOnlyField(source='get_likedBy')
in your PostSerializer class
and define function in your Post model class like below:
#property
def get_likedBy(self):
liked_by = []
for user in self.users_liked_post.all():
liked_by.append(user.name)
return liked_by
just use correct related_name instead of users_liked_post
If you add likedBy to your expandable fields, and then add ?expand=likedBy to your url, it should give you all the information that you outline in the UserSerializer, or write a new serializer named LikedBySerializer. Also as a general rule, try not to use '__all__' it's a good way to leak data. Happy coding!
class LikedBySerializer(FlexFieldsModelSerializer):
class Meta:
model = User
fields = '__all__'
class PostSerializer(FlexFieldsModelSerializer):
class Meta:
model = Post
fields = '__all__'
expandable_fields = {
'category': ('blogApi.CategorySerializer', {'many': True}),
'comments': ('blogApi.CommentSerializer', {'many': True}),
'likedBy': ('blogApi.LikedBySerializer', {'many': True}),
}
I have got a model of User Details which is Self Related to create the relationship between two users.
Model
class AdvDetails(models.Model):
user_id = models.UUIDField(primary_key=True,default=uuid.uuid4,editable=False)
title = models.CharField(max_length=5, choices=[('Mr', 'Mr'), ('Ms', 'Ms'), ('Mrs', 'Mrs'), ('Dr', 'Dr'), ('NA', '')], default='NA')
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
email_id = models.EmailField(null=True, blank=True, default=None)
parent_profile = models.ForeignKey("self", null=True, blank=True)
View Set
class AdvDetailsViewSet(viewsets.ModelViewSet):
serializer_class = AdvDetailsSerializer
filter_backends = (filters.SearchFilter,DjangoFilterBackend, filters.OrderingFilter)
filter_fields = ('email_id,'parent_profile')
search_fields = ( '^first_name',)
def get_queryset(self):
return AdvDetails.objects.all()
Serializer
class AdvDetailsSerializer(serializers.ModelSerializer):
class Meta:
model = AdvDetails
fields = '__all__'
This structure facilitates me to help get the data in the below format
{
"user_id": "055cbde6-10ea-4558-86fc-1b42624ce760",
"title": "Mr",
"first_name": "foo",
"last_name": "bar",
"email_id" : "foo#bar.com"
"parent_profile":"6c429d4c-9fb4-42e5-9d7d-9fc782f81fb0"
}
I would like to modify the serializer in such a way that I would get the data of the parent profile's email_id as below
{
"user_id": "055cbde6-10ea-4558-86fc-1b42624ce760",
"title": "Mr",
"first_name": "foo",
"last_name": "bar",
"email_id" : "foo#bar.com",
"parent_profile":"6c429d4c-9fb4-42e5-9d7d-9fc782f81fb0",
"parent_email_id" : "parent#email.com"
}
Any help is highly appreciated. I've tried PrimaryKeyRelatedField but it was not solving my problem.
I made a package that allows expanding fields dynamically, on-demand per request:
https://github.com/rsinger86/drf-flex-fields
For your case:
class AdvDetailsSerializer(FlexFieldsModelSerializer):
class Meta:
model = AdvDetails
fields = ('user_id', 'title', 'first_name', 'last_name', 'email_id', 'parent_profile', 'parent_email_id' )
expandable_fields = {'parent_profile': '<app_name>.AdvDetailsSerializer'}
Replace <app_name> with the name of the Django app that defines the serializer, so it can be loaded lazily.
If instead you would like to statically expand a field, you can define that field as a nested serializer:
class AdvDetailsSerializer(serializers.ModelSerializer):
parent_profile = ProfileProfileSerializer()
class Meta:
model = AdvDetails
fields = (
'user_id',
'title',
'first_name',
'last_name',
'email_id',
'parent_profile',
'parent_email_id'
)
I'm building an API with the django rest framework. I have these models:
class Organism(models.Model):
name = models.CharField(max_length=255)
address = models.ForeignKey(Address, on_delete=models.CASCADE)
type = models.ForeignKey(Type, on_delete=models.CASCADE)
class Address(models.Model):
street = models.CharField(max_length=255, blank=True)
class Type(models.Model):
name = models.CharField(max_length=255, blank=True)
This is the view for my mode Organism :
class OrganismViewSet(viewsets.ModelViewSet):
queryset = Organism.objects.all()
serializer_class = OrganismSerializer
pagination_class = StandardResultsSetPagination
filter_backends = (filters.SearchFilter, DjangoFilterBackend)
filter_class = OrganismFilter
search_fields = ('name')
And my serializer:
class OrganismSerializer(serializers.ModelSerializer):
addresse = AddressSerializer()
type = TypeSerializer()
class Meta:
model = Organism
fields = '__all__'
def update(self, instance, validated_data):
// What I should write to do something "elegant"
Let's imagine when I get my Organism, I have:
{
"address": {
"id": 1
"street": "test"
},
type: {
"id": 1,
"name": "type Organism"
},
"name":"TestTest",
}
So I'm trying to update an Organism (I want to change the name of the street but not create a new object AND change the Type which exists in my database) by sending this:
{
"address": {
"id": 1
"street": "new name"
},
type: {
"id": 2,
"name": "new type"
}
"name":"TestTest",
}
And the fact is I don't have the ID of my object in the parameter "validated_data" of the method update.
If you guys know how to proceed... Thank you in advance.
I'm using Django and Django-rest-framework, and I have the next Model and Serializer:
class Category(models.Model):
id_category = models.UUIDField(primary_key=True, default=uuid.uuid1, editable=False)
name = models.TextField(null=False)
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id_category', 'name', 'parent')
Now, I want to make a query like Category.objects.filter(parent=None) that returns something like this:
[
{
"id_category": "UUID",
"name": "Father",
"childrens": [
{
"id_category": "UUID",
"name": "Son",
"childrens": [
{
"id_category": "UUID",
"name": "Grandson"
}
]
}
]
},
{
"id_category": "UUID",
"name": "Other"
}
]
As you see, a Category could have one father and many children. Need help to make this query, because I don't know how do that.
You can write additional serializer for child category:
class ChildrenSerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id_category', 'name', 'parent')
class CategorySerializer(serializers.ModelSerializer):
children = ChildrenSerializer(many=True)
class Meta:
model = Category
fields = ('id_category', 'name', 'parent', 'children')
You could also show nested models with depth option:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
depth = 1
fields = ('id_category', 'name', 'parent', 'children')
I am a newbie in django world and I'm trying to create a simple rest service with those models:
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
class Album(models.Model):
artist = models.ForeignKey(Musician,on_delete=models.CASCADE)
name = models.CharField(max_length=100)
release_date = models.DateField()
num_stars = models.IntegerField()
And those serializers:
class MusicianSerializer(serializers.ModelSerializer):
class Meta:
model = Musician
fields = ('id','first_name', 'last_name', 'instrument')
class AlbumSerializer(serializers.ModelSerializer):
class Meta:
model = Album
fields = ('id','artist', 'name', 'release_date', 'num_stars')
I want to be able to post a JSON like this one:
{
"first_name": "MusicNom",
"last_name": "MusicCognom",
"instrument": "Flauta",
"albums":
[
{
"name": "Album1",
"release_date": "2015-02-12",
"num_stars": 5
},
{
"id": 2,
"artist": 1,
"name": "AlbumNuevo",
"release_date": "2013-01-08",
"num_stars": 5
}
]
}
This JSON should create the musician and his albums. I've found some examples in the documentation that are useful using "GET" but I would like to do it with "POST".