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