Search code examples
django-rest-frameworkdjango-views

Django Rest Framework Nested Serialziers


iam using DRF so i make a small blog that have follow/unfollow system that's my models :

class User(AbstractUser):
    email = models.EmailField(unique=True)
    objects = CustomUserManager()
    following = models.ManyToManyField(
        'self', through='Follow', symmetrical=False, related_name='followers'
    )
    def follow(self, target_user):
        if self == target_user:
            raise ValueError("You cannot follow yourself.")

        if not self.is_following(target_user):
            Follow.objects.create(follower=self, following=target_user)
            return True
        return False

    def unfollow(self, target_user):
        if self.is_following(target_user):
            Follow.objects.filter(follower=self, following=target_user).delete()
            return True
        return False

    def is_following(self, target_user):
        return Follow.objects.filter(follower=self, following=target_user).exists()

class UserProfile(models.Model):
    user = models.OneToOneField(User,on_delete = models.CASCADE, related_name='profile')
    bio = models.TextField(blank=True, null=True)
    profile_picture = models.ImageField(upload_to='profile_pictures/', blank=True, null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    followers_count = models.IntegerField(default=0)
    following_count = models.IntegerField(default=0)
    # is_private = models.BooleanField(default=False)
    
    def __str__(self):
        return self.user.username



class Follow(models.Model):
    follower = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_following')
    following = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_followers')
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['follower','following'],  name="unique_followers"),
            models.CheckConstraint(check=~models.Q(follower=models.F("following")), name="no_self_follow")
        ]
        ordering = ["-created_at"]
        
    def __str__(self) -> str:
        return f"{self.follower} follows {self.following}"

and this my view :

class FollowersFollowingViewSet(ViewSet):
    
    @action(detail=True, methods=['GET'])
    def followers(self, request, pk=None):
        target_user = User.objects.get(pk=pk)
        followers = Follow.objects.filter(following=target_user).select_related('follower__profile','follower')
        serializer = FollowersSerializer(followers, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)
    
    @action(detail=True, methods=['GET'])
    def following(self, request, pk=None):
        target_user = User.objects.get(pk=pk)
        following = target_user.following.all()
        serializer = FollowingSerializer(following, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

and those my serializers:

class FollowersSerializer(serializers.ModelSerializer):
    follower = SimpleUserProfileSerializer()  

    class Meta:
        model = Follow
        fields = ['follower']

class FollowingSerializer(serializers.ModelSerializer):
    following = SimpleUserProfileSerializer()

    class Meta:
        model = Follow
        fields = ['following']
class SimpleUserProfileSerializer(serializers.ModelSerializer):
    user_id = serializers.ReadOnlyField(source='user.id')  # جلب user.id من العلاقة
    username = serializers.ReadOnlyField(source='user.username')  # جلب user.username
    first_name = serializers.ReadOnlyField(source='user.first_name')  # جلب user.first_name
    last_name = serializers.ReadOnlyField(source='user.last_name')  # جلب user.last_name

    class Meta:
        model = UserProfile  # الموديل الأساسي هو UserProfile
        fields = ['user_id', 'username', 'first_name', 'last_name', 'profile_picture', 'bio']

and this my output :

[
    {
        "follower": {
            "profile_picture": null,
            "bio": null
        }
    }
]

i just do not understand why he is still returning the just fields that's in profile model
i think it's because of it's a model Serializer and the model set to UserProfile ?
is there any solution or i should make a new serializer in this case ?
i just want him to get all info about all followers


Solution

  • following and follower appear to be User objects, not UserProfile objects, so the serializer should go the other way around:

    class SimpleUserProfileSerializer(serializers.ModelSerializer):
        user_id = serializers.ReadOnlyField(source='id')  # جلب user.id من العلاقة
        username = serializers.ReadOnlyField(source='username')  # جلب user.username
        first_name = serializers.ReadOnlyField(
            source='first_name'
        )  # جلب user.first_name
        last_name = serializers.ReadOnlyField(
            source='last_name'
        )  # جلب user.last_name
        bio = serializers.ReadOnlyField(source='profile.bio')
        profile_picture = serializers.ReadOnlyField(source='profile.profile_picture')
    
        class Meta:
            model = User
            fields = [
                'user_id',
                'username',
                'first_name',
                'last_name',
                'profile_picture',
                'bio',
            ]

    Note: Please don't store aggregates in the model: determine aggregates when needed: storing aggregates in the model makes updating and keeping data in sync harder. You can use .annotate(…) [Django-doc] to generate counts, sums, etc. per object when needed.