user11879807
user11879807

Reputation: 301

Django Rest Framework Relationships Serialization

I am trying to get serialized data for the below model with foreign keys but in the output i am only getting the ids instead of the fields of the model associated with hyperlink.

I have tried getting all the fields of the Skills model using the commented lines in the profile serializer but no luck

models.py

class Profile(models.Model):
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    first_name = models.CharField(max_length=20, null=True)
    last_name = models.CharField(max_length=20, null=True)

    def __str__(self):
        return 'Profile for user {}'.format(self.user.username)


class Skill(models.Model):
    skill = models.CharField(max_length=20, null=True)
    level = models.CharField(max_length=20, null=True)
    user = models.ForeignKey(Profile, null=True,
                             on_delete=models.PROTECT, related_name='skills')

    def __str__(self):
        return '%s: %s: %s' % (self.user, self.skill, self.level)

serializer.py

class SkillSerializer(serializers.ModelSerializer):

    class Meta:
        model = Skill
        fields = ('user', 'skill', 'level')

class ProfileSerializer(serializers.ModelSerializer):

    class Meta:
        # skills = SkillSerializer(many=True, read_only=True)  # serializes child model's data inside this model's data
        # skills = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='skill-detail')
        # skills = serializers.StringRelatedField(many=True)
        # skills = serializers.HyperlinkedIdentityField( view_name = "skill-list", lookup_field = "user")
        url = HyperlinkedIdentityField( view_name="profile-detail", lookup_field = "id")
        # user = serializers.ReadOnlyField(source='user.username')
        # pdb.set_trace()

        model = Profile
        fields = ['id', 'user', 'url', 'skills']

views.py

class ProfileList(generics.ListCreateAPIView):
    queryset = Profile.objects.all()
    serializer_class = ProfileSerializer

class ProfileDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Profile.objects.all()
    serializer_class = ProfileSerializer

class SkillList(generics.ListCreateAPIView):
    queryset = Skill.objects.all()
    serializer_class = SkillSerializer

class SkillDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Skill.objects.all()
    serializer_class = SkillSerializer

urls.py

path('profiles/', views.ProfileList.as_view(), name='profile-list'),
path('profiles/<int:pk>/', views.ProfileDetail.as_view(), name='profile-detail'),   
path('skills/', views.SkillList.as_view(), name='profile-list'),
path('skills/<int:pk>/', views.SkillDetail.as_view(), name='skill-list'),

Output: http://127.0.0.1:8000/authenticator/profiles/

   [  "id": 6,
        "user": 4,
        "url": "http://127.0.0.1:8000/authenticator/profiles/6/",
        "skills": [
            57,
            55
        ],
   ]

Expected output:

   [  "id": 6,
        "user": 4,
        "url": "http://127.0.0.1:8000/authenticator/profiles/6/",
        "skills": [
              {
                "user": 6,
                "skill": "ABC",
                "level": "Beginner"
              },
              {
                "user": 6,
                "skill": "DEF",
                "level": "Intermediate"
              },
         ]
    ]

Upvotes: 1

Views: 472

Answers (2)

dipesh
dipesh

Reputation: 834

You can use the concept of nested serializer for the solution.
Create two serializer ProfileReadSerializer and ProfileWriteSerializer
serializers.py

class SkillSerializer(serializers.ModelSerializer):
    class Meta:
        model = Skill
        fields = ('user', 'skill', 'level')


class ProfileReadSerializer(serializers.ModelSerializer):
    skills = SkillSerializer(many=True, read_only=True)
    url = HyperlinkedIdentityField( view_name="profile-detail", lookup_field = "id")

    class Meta:
        model = Profile
        fields = ('id', 'user', 'url', 'skills')


class ProfileWriteSerializer(serializers.ModelSerializer):
    skills = SkillSerializer(many=True)

    class Meta:
        model = Profile
        fields = ('id', 'user', 'url', 'skills')

In views you simply can use ModelViewSet to make things easy and use get_serializer_class
views.py

from rest_framework.viewsets import ModelViewSet

class ProfileVewSet(ModelViewSet):
    queryset = Profile.objects.all()

    def get_serializer_class(self):
        if self.request.method == 'POST' or self.request.method == 'PUT' or self.request.method == 'PATCH':
            return ProfileWriteSerializer
        else:
            return ProfileReadSerializer

And at last if you are using ModelViewSet you need to change the urls.py
urls.py

from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('profile', views.ProfileViewSet, base_name='profile')

urlpatterns = [
    path('', include(router.urls))
]

I think this will solve your problem. Please have a look.

Upvotes: 0

Sander Vanden Hautte
Sander Vanden Hautte

Reputation: 2543

This should normally work with the following in serializer.py:

class ProfileSerializer(serializers.ModelSerializer):
    skills = SkillSerializer(many=True, read_only=True)  # serializes child model's data inside this model's data
    url = HyperlinkedIdentityField( view_name="profile-detail", lookup_field = "id")

    class Meta:
        model = Profile
        fields = ['id', 'user', 'url', 'skills']

So basically uncomment the line in which the ProfileSerializer is told to serialize the skills with your SkillsSerializers, such that the entire skill objects are included in the result instead of its IDs.

Upvotes: 1

Related Questions