Custom serializer output for nested relationship field in DRF serializer class - python

I am in little trouble while developing a web api's using Django-Rest-Framework(DRF).
Problem Statement
I have two models User and Review.
models.py
# Consider User model as `django.contrib.auth.models.User`
from django.contrib.auth.models import User
from django.db import models
# Review model
class Review(models.Model):
head = models.CharField()
content = models.CharField()
user = models.ForeignKey(User)
is_deleted = models.BooleanField(default=False)
and, i have two endpoints like this:
/users/ - list of all users
/users/<pk> - detail of user
/review/ - list of all reviews
/review/<pk> - detail of review
I want my output like this:
# /users/
[
{
"url": "http://localhost:8000/users/1",
"fisrt_name": "Adolf",
"last_name": "Hitler",
"email": "adolfhilter#xyz.com",
"is_staff": false
........ # other fields
},
.........
.........
]
# /reviews/
[
{
"url": "http://localhost:8000/reviews/1",
"head": "Head of Review",
"content": "Content of Review",
"user": {
"url": "http://localhost:8000/users/1",
"first_name": "Adolf",
"last_name": "Hitler"
},
"is_deleted": false
},
.........
.........
]
My Solution
To achieve this form of output i created three serializers class, one is UserSerializer class , second one is ReviewSerializer, and thir one id ReviewUserSerializer.These classes are as follows:
serializers.py
from rest_framework import serializers
from .models import Review
from django.contrib.auth.models import User
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'first_name', 'last_name', 'email', 'is_staff', .....)
class ReviewUserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'first_name', 'last_name')
class ReviewSerializer(serializers.HyperlinkedModelSerializer):
user = ReviewUserSerializer()
class Meta:
model = Review
fields = ('url', 'head', 'content', 'user')
So, now i want to know that ,
Is there any other way which can avoid to create an extra separate serializer class(here is ReviewUserSerializer) for these type of situations?
If yes, then suggest me a solution with proper code snippets.

Have you tried using a SerializerMethodField?
from rest_framework import serializers
from .models import Review
from django.contrib.auth.models import User
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'first_name', 'last_name', 'email', 'is_staff', .....)
class ReviewSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Review
fields = ('url', 'head', 'content', 'user')
def get_user(self, obj) :
request = self.context['request']
return {
'url': reverse('user-detail',
kwargs={'pk': obj.user.id}, request=request),
'first_name': obj.user.first_name,
'last_name': obj.user.last_name,
}

Related

Returning a user with a list of the groups he belongs to in Django Rest API

I'm writing an API and I want to return a list of users along with the groups each user belongs to. I'm fairly new to Django and I'm stuck. I've tried several ways but the closest I came to finding a solution is when Django returned auth.Group.none while the user is in a Group.
authentication/models.py
class CustomUser(AbstractUser):
role = models.CharField(blank=True, max_length=120)
authentication/views.py
class CustomUserView(APIView):
permission_classes = [IsAuthenticated, IsAdmin, ]
serializer_class = CustomUserSerializer
def get(self, request, format='json'):
queryset = CustomUser.objects.all()
serializer = CustomUserSerializer(queryset, many=True)
filterset_fields = ['id', 'name', 'email', 'groups']
return Response(serializer.data, status=status.HTTP_200_OK)
authentication/serializers.py
class CustomUserSerializer(serializers.ModelSerializer):
email = serializers.CharField(
required=True
)
username = serializers.CharField(required=True)
password = serializers.CharField(min_length=8, write_only=True)
first_name = serializers.CharField()
last_name = serializers.CharField()
groups = serializers.CharField()
role = serializers.CharField()
class Meta:
model = CustomUser
fields = ('id', 'email', 'username', 'first_name', 'last_name', 'password', 'groups', 'role')
extra_kwargs = {'password': {'write_only': True}}
JSON Output
{
"id": 4,
"email": "",
"username": "testuser",
"first_name": "",
"last_name": "",
"groups": "auth.Group.None"
}
Any input would be appreciated!
Thanks in advance.
groups = serializers.CharField()
, in your serializer is incorrect , change serializer to this:
from django.contrib.auth.models import Group
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model= Group
fields = ('id','name')
class CustomUserSerializer(serializers.ModelSerializer):
email = serializers.CharField(required=True)
username = serializers.CharField(required=True)
password = serializers.CharField(min_length=8, write_only=True)
first_name = serializers.CharField()
last_name = serializers.CharField()
groups = GroupSerializer(many=True)
role = serializers.CharField()
class Meta:
model = CustomUser
fields = ('id', 'email', 'username', 'first_name', 'last_name', 'password','groups', 'role')
extra_kwargs = {'password': {'write_only': True}}
Zahra Ebrahimi's answer is correct, Its just an extension of the answer.
if you want to use drf's browsable api form to create the instance, then add a queryset of groups, for the conveniences.
class GroupSerializer(PrimaryKeyRelatedField,serializers.ModelSerializer):
class Meta:
model= Group
fields = ('id','name')
class CustomUserSerializer(serializers.ModelSerializer):
......
......
groups = GroupSerializer(many=True, queryset=Groups.objects.all())
......
class Meta:
model = CustomUser
fields = ('id', 'email', 'username', 'first_name', 'last_name', 'password','groups', 'role')
extra_kwargs = {'password': {'write_only': True}}

how to get a foreignkey object to display full object in django rest framework

I have a Django back-end built with django_rest_framework. I currently have an object that is a foreign key. When I make a API request to grab an object it displays the foreignkey id and only the id. I want it to display the entire object rather than just the id of the foriegnkey. Not sure how to do it because it did not really show how to do it in the documentation.
Here is the code:
Views page:
from users.models import Profile
from ..serializers import ProfileSerializer
from rest_framework import viewsets
class ProfileViewSet(viewsets.ModelViewSet):
queryset = Profile.objects.all()
lookup_field = 'user__username'
serializer_class = ProfileSerializer
There is a user foreign key referring to the user.
Urls:
from users.api.views.profileViews import ProfileViewSet
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'', ProfileViewSet, base_name='profile')
urlpatterns = router.urls
The serializer:
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = (
'id',
'user',
'synapse',
'bio',
'profile_pic',
'facebook',
'twitter'
)
This is what it looks like:
HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
[
{
"id": 1,
"user": 3,
"bio": "software engineer",
"profile_pic": "http://127.0.0.1:8000/api/user/profile/profile_pics/allsum-logo-1.png",
"facebook": "http://www.facebook.com/",
"twitter": "http://www.twitter.com/"
}
]
Use depth=1 in your Meta class of serializer,
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = (
'id',
'user',
'synapse',
'bio',
'profile_pic',
'facebook',
'twitter'
)
depth = 1
You can create a UserSerializer and use it in ProfileSerializer like this(using as nested serializer):
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'username',
'first_name',
# and so on..
)
class ProfileSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = Profile
fields = (
'id',
'user',
'synapse',
'bio',
'profile_pic',
'facebook',
'twitter'
)

Assigning current 'User' as foreign key to nested serializers

I am trying to assign current 'User' to two models using nested serializers.
class UserAddressSerializer(serializers.ModelSerializer):
class Meta:
model = UserAddress
fields = ('user', 'address_1', 'address_2', 'country',
'state_province', 'city', 'zip_code')
class UserProfileSerializer(serializers.ModelSerializer):
user_address = UserAddressSerializer()
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = UserProfile
fields = ('user', 'first_name', 'middle_name', 'last_name',
'title', 'display_name', 'time_zone', 'user_address', 'default_office')
def create(self, validated_data):
user = validated_data.pop('user')
user_address_data = validated_data.pop('user_address')
user_address_object = UserAddress.objects.create(
user=user, **user_address_data)
user_profile_object = UserProfile.objects.create(
user=user, **validated_data)
return user
What I am getting is this output in Postman.
{
"user_address": {
"user": [
"This field is required."
]
}
}
I want to know a way to pass 'User' to both of these model creation.
You need to remove user from fields of UserAddressSerializer:
class UserAddressSerializer(serializers.ModelSerializer):
class Meta:
model = UserAddress
fields = ('address_1', 'address_2', 'country', # <-- Here
'state_province', 'city', 'zip_code')

Join related models in django rest framework

Trying to create an API method for getting user profile. The problem is that there are two tables related to user: built in django User and SocialAccount from allauth framework. I guess the joining part should be in serializers so after a research I came up with this:
from rest_framework import serializers
from django.contrib.auth.models import User
from allauth.socialaccount.models import SocialAccount
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('pk', 'first_name', 'last_name')
class SocialSerializer(serializers.ModelSerializer):
user = UserSerializer(many=False, read_only=True)
class Meta:
model = SocialAccount
fields = ('uid', 'provider', 'user')
It works but it outputs it as nested objects:
{
"uid": "",
"provider": "",
"user": {
"pk": 5,
"first_name": "",
"last_name": ""
}
}
I would like it to be as one object:
{
"uid": "",
"provider": "",
"pk": 5,
"first_name": "",
"last_name": ""
}
alternatively, try
class SocialSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = SocialAccount
fields = ('uid', 'provider', 'user')
def to_representation(self, instance):
data = super(SocialSerializer, self).to_representation(instance)
user = data.pop('user')
for key, val in user.items():
data.update({key: val})
return data
You can either try to flatten the JSON (see this link) or redefine your serializer as below:
class SocialSerializer(serializers.ModelSerializer):
pk = serializers.SerializerMethodField()
first_name = serializers.SerializerMethodField()
last_name = serializers.SerializerMethodField()
class Meta:
model = SocialAccount
fields = ('uid', 'provider', 'pk', 'first_name', 'last_name')
def get_pk(self, obj):
return obj.user.pk
def get_first_name(self, obj):
return obj.user.first_name
def get_last_name(self, obj):
return obj.user.last_name
These are serializers.SerializermethodField() fields which will look at the get_<field_name> method and call them and use the returned value.
http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield

How to pull other serialized data from different Serializer classes?

I'm trying to pull the permissions data into my UserSerializer. I have a Many To Many relationship with User to Group with a through table called GroupUser. I'm able to pull the group_id and the group_name, but I can't seem to populate the data for permissions into my User Serializer.
So far I've tried using permission = serializers.ReadOnlyField(source='group.permissions') but that doesn't work.
Any suggestions?
Here's my code:
class GroupSerializer(serializers.ModelSerializer):
users = GroupUserSerializer(source='groupuser_set', many=True)
permissions = PermissionSerializer(source='permission_set', many=True)
class Meta:
model = Group
fields = ('id', 'name', 'users', 'permissions', 'role', )
class GroupUserSerializer(serializers.ModelSerializer):
id = serializers.ReadOnlyField(source='group.id')
name = serializers.ReadOnlyField(source='group.name')
class Meta:
model = GroupUser
fields = ( 'id', 'name', )
class UserSerializer(serializers.ModelSerializer):
tests = TestSerializer(source='test_set', many=True)
groups = GroupUserSerializer(source='groupuser_set', many=True)
class Meta:
model = User
fields = ( 'id', 'username', 'groups', 'tests', )
I want my data to look like this:
{
"id": 1,
"username": "user",
"groups": [
{
"id": 2,
"name": "employee"
"permission": "Access-Denied"
}
],
"tests": []
},
but I have it without "permissions"
class UserSerializer(serializers.ModelSerializer):
tests = TestSerializer(source='test_set', many=True)
groups = GroupSerializer(read_only=True, many=True)
class Meta:
model = User
fields = ( 'id', 'username', 'groups', 'tests', )
Edited explanation:
I was able to find the answer with a little tinkering.
I thought, from reading docs and stackoverflow questions, you had to include the 'Through' table when referencing a many to many relationship, but that's not true, the serializer already does it for you.
So I added the groups, as shown, with that information and all the data that is associated in the GroupSerializer with a relationship with data was populated just like the way I want it to be.
I recommend you to write a new Serializer for your UserSerializer, this will not affect other Serializers:
class GroupForUserSerializer(serializers.ModelSerializer):
users = GroupUserSerializer(source='groupuser_set', many=True)
permissions = PermissionSerializer(read_only=True , many=True)
class Meta:
model = Group
fields = ('id', 'name', 'permissions' )
Then you can in your UserSerializer use GroupForUserSerializer now:
class UserSerializer(serializers.ModelSerializer):
tests = TestSerializer(source='test_set', many=True)
groups = GroupForUserSerializer(source='groupuser_set', many=True)
class Meta:
model = User
fields = ( 'id', 'username', 'groups', 'tests' )

Categories