Add expandable field to Serializer on Self Related Model - python

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

Related

How to Serialize an additional field using ManyToMany relationship with intermediary model in Django Rest Framework?

I am trying to add field amount into Ingredient model, by creating an intermediary model IngredientAmount. But I am struggling with serialization.
The response should look like this:
"results": [
......
"ingredients": [
{
"id": 0,
"name": "Potato",
"measurement_unit": "kg",
"amount": 1
}
],
....
]
I am using IngredientAmountSerializer(serializers.ModelSerializer), but Django says that I am in 'Ingredient' object and it has no attribute 'amount'.
what am I doing wrong?
models
class Ingredient(models.Model):
name = models.CharField(max_length=200)
measurement_unit = models.CharField(max_length=200)
class Meta:
ordering = ['id']
def __str__(self):
return self.name
class IngredientAmount(models.Model):
ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE,
related_name='ingredient_amount')
recipe = models.ForeignKey('Recipe', on_delete=models.CASCADE,
related_name='ingredient_amount')
amount = models.SmallIntegerField(
validators=(
validators.MinValueValidator(
1, 'add amount'),))
class Recipe(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
ingredients = models.ManyToManyField(
Ingredient, through=IngredientAmount,
through_fields=('recipe', 'ingredient', ),
)
tags = models.ManyToManyField(Tag, blank=True, related_name='recipe')
image = models.ImageField()
name = models.CharField(max_length=200)
text = models.TextField()
cooking_time = models.PositiveSmallIntegerField(
validators=(
validators.MinValueValidator(1, message='choose time'),
),
)
serializers
class IngredientAmountSerializer(serializers.ModelSerializer):
name = serializers.CharField(source='ingredient.name', read_only=True)
measurement_unit = serializers.IntegerField(source='ingredient.measurement_unit', read_only=True)
class Meta:
model = IngredientAmount
fields = ('id', 'name', 'measurement_unit', 'amount' )
class RecipeSerializer(serializers.ModelSerializer):
author = AuthorSerializer()
tags = TagSerializer(read_only=True, many=True)
image = Base64ImageField(required=True)
ingredients = IngredientAmountSerializer( read_only=True, many=True)
class Meta:
model = Recipe
fields = ('id', 'tags', 'author', 'name', 'image', 'text',
'cooking_time', 'ingredients')
I gues the problem is, you are trying to serialize Ingredient model with IngredientAmountSerializer when in it Meta model is IngredientAmount, and not Ingredient.
Update: If you want serialize IngredientAmount, try to use source
class RecipeSerializer(serializers.ModelSerializer):
...
ingredients = IngredientAmountSerializer(source='ingredientamount_set', read_only=True, many=True)

adding one serializer fields to another -django rest framework

my serializer.py file is as
...
class RelativeSerializerSLC(serializers.ModelSerializer):
full_name = serializers.CharField(source="user.full_name")
rtl_full_name = serializers.CharField(source="user.rtl_full_name")
gender = serializers.CharField(source="user.gender")
phone = serializers.CharField(source="user.phone")
email = serializers.CharField(source="user.email")
avatar = serializers.CharField(source="user.avatar")
date_of_birth = serializers.CharField(source="user.date_of_birth")
class Meta:
model = Relative
fields = ("full_name", "rtl_full_name", "gender", "phone", "email", "avatar", "date_of_birth", "blood_group", "rel")
read_only_fields = ["patient"]
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "id", "full_name", "rtl_full_name", "gender", "phone", "email", "date_of_birth", "avatar"
there i'm creating other serializer fields(userSerializer) and added to my RelativeSerializer.
that seems uglyyy to me, i have no idea on. is there any better option like using one serializer fields for other.
Thanks, new to DRF :)
Maybe try this:
class RelativeSerializerSLC(serializers.ModelSerializer):
users = UserSerializer(read_only=True)
class Meta:
model = Relative
fields = ("full_name", "rtl_full_name", "gender", "phone", "email", "avatar",
"date_of_birth", "blood_group", "rel")
read_only_fields = ["patient"]
And put UserSerializer class on top of the relativeSerializer. Let me know if it works. I don't see your model fields so it might not be.
Check out for nested serializers here https://www.django-rest-framework.org/api-guide/relations/#nested-relationships

Django foreign key rest framework

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}),
}

Serialize ManyToManyFields with a Through Model in Django REST Framework

I have this M2M relation with through model as
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
Please note that, I have extra fields date_joined and invite_reason in the through model.
Now, I want to serialize the Group queryset using DRF and thus I choose the below serializer setup.
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = "__all__"
class GroupSerializer(serializers.ModelSerializer):
members = PersonSerializer(read_only=True, many=True)
class Meta:
model = Group
fields = "__all__"
and it is returning the following response,
[
{
"id": 1,
"members": [
{
"id": 1,
"name": "Jerin"
}
],
"name": "Developer"
},
{
"id": 2,
"members": [
{
"id": 1,
"name": "Jerin"
}
],
"name": "Team Lead"
}
]
Here, the members field returning the Person information, which is perfect.
But,
How can I add the date_joined and invite_reason field/info into the members field of the JSON response?
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = "__all__"
def serialize_membership(self, person_instance):
# simple method to serialize the through model fields
membership_instance = person_instance \
.membership_set \
.filter(group=self.context["group_instance"]) \
.first()
if membership_instance:
return MembershipSerializer(membership_instance).data
return {}
def to_representation(self, instance):
rep = super().to_representation(instance)
return {**rep, **self.serialize_membership(instance)}
class MembershipSerializer(serializers.ModelSerializer): # create new serializer to serialize the through model fields
class Meta:
model = Membership
fields = ("date_joined", "invite_reason")
class GroupSerializer(serializers.ModelSerializer):
members = serializers.SerializerMethodField() # use `SerializerMethodField`, can be used to pass context data
def get_members(self, group):
return PersonSerializer(
group.members.all(),
many=True,
context={"group_instance": group} # should pass this `group` instance as context variable for filtering
).data
class Meta:
model = Group
fields = "__all__"
Notes:
Override the to_representation(...) method of PersonSerializer to inject extra data into the members field of the JSON
We need person instance/pk and group instance/pk to identify the Membership instance to be serialized. For that, we have used the serializer context to pass essential data

Use A Serializer Class Inside Other One In Django

I want to have a serializers that use two model at once (If it possible)
models.py
class Club(models.Model):
id = models.AutoField(primary_key=True)
clubname = models.CharField(max_length=50, blank=True, null=True)
location = models.CharField(max_length=50, blank=True, null=True)
scores = models.IntegerField(blank=True, null=True)
serializers.py
class ShowAllClubSerializer(serializers.ModelSerializer):
class Meta:
model = Club
fields
class ShowClubPictures(serializers.ModelSerializer):
class Meta:
model = Clubpictures
fields = ['picture']
views.py
#api_view(["GET", ])
#permission_classes((IsAuthenticated, ))
def show_all_clubs_view(request):
if request.method == "GET":
clubs = Club.objects.all()
if clubs:
for club in clubs:
pictures = Clubpictures.objects.filter(clubid=club.id)
serializer1 = ShowAllClubSerializer(club)
serializer2 = ShowClubPictures(pictures[0])
return Response(serializer1.data, status=status.HTTP_200_OK)
# return Response(serializer2.data, status=status.HTTP_200_OK)
else:
return Response(status=status.HTTP_400_BAD_REQUEST)
Now I Have These In serializers1 and serializers2 Separately:
{
"clubname": "Club Name",
"location": "Somewhere",
"scores": 5,
}
{
"picture": "/media/images/Screenshot.png"
}
How can I take something like this in result:
{
"clubname": "Club Name",
"location": "Somewhere",
"scores": 5,
"picture": "/media/images/Screenshot.png"
}
You can use nested serializers to achieve that.
Your ClubSerializer would look something like this:
class ClubSerializer(serializers.ModelSerializer):
pictures = ClubPictureSerializer(many=True)
class Meta:
model = Club
fields = ('clubname', 'location', 'scores', 'pictures')
class ClubPictures(serializers.ModelSerializer):
class Meta:
model = Clubpictures
fields = ['picture']
assuming you have a ForeignKey from the ClubPicture to your Club with a related_name of pictures.
Also, on your view, you don't need to loop through Club.objects.all() and serialize each object individually - ModelSerializer/Serializer accept a many=True parameters that handles multiple objects already.

Categories