HyperlinkedModelSerializer 自定义 lookup_field 到 related_model

HyperlinkedModelSerializer custom lookup_field to related_model

我有以下配置,我想将 UserProfileView 中的 url 字段映射到相关用户的用户名,而不是默认的 pk 字段

目前 url 如下所示,感谢您的帮助

{
        "user": 23,
        "bio": "My bio",
        "created_on": "2020-06-12T21:24:52.746329Z",
        "url": "http://localhost:8000/bookshop/bio/8/?format=api"
    },

我要找的是

{
        "user": 23,   <--- this is the user <pk> 
        "bio": "My bio",
        "created_on": "2020-06-12T21:24:52.746329Z",
        "url": "http://localhost:8000/bookshop/bio/username/?format=api"
    },
models.py

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
    bio = models.CharField(max_length=255)
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.user.username
views.py

class UserProfileViewSets(viewsets.ModelViewSet):

    authentication_classes = [TokenAuthentication, ]
    permission_classes = [rest_permissions.IsAuthenticated, permissions.UserProfileOwnerUpdate, ]
    queryset = models.UserProfile.objects.all()
    serializer_class = serializers.UserProfileSerializer
    renderer_classes = [renderers.AdminRenderer, renderers.JSONRenderer, renderers.BrowsableAPIRenderer, ]
    # lookup_field = 'user.username'

    def perform_create(self, serializer):
        serializer.save(user=self.request.user)
serializer.py

class UserProfileSerializer(serializers.ModelSerializer):

    class Meta:
        model = models.UserProfile
        fields = ['user', 'bio', 'created_on', 'url']
        extra_kwargs = {
            'last_updated': {
                'read_only': True
            },
            'user': {
                'read_only': True
            },
        }

经过努力和阅读许多文章后,我做到了,如果有人正在寻找相同的用例,我会发布解决方案。

  • 字段通过一对一关系相互关联
models.py

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
    bio = models.CharField(max_length=255)
    created_on = models.DateTimeField(auto_now_add=True)
    last_updated = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.user.username

class User(AbstractBaseUser, PermissionsMixin):
    """"
    Customizes the default user account
    """
    email = models.EmailField(unique=True, help_text='username is the email address')
    first_name = models.CharField(max_length=40, blank=False)
    last_name = models.CharField(max_length=40, blank=False)
    date_joined = models.DateTimeField(auto_now_add=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    username = models.CharField(max_length=15, unique=True, null=True, blank=False,
                                validators=(validators.UnicodeUsernameValidator, ))
    is_borrower = models.BooleanField(default=False)

  • serializer 是一个 HyperlinkedModelSerializer,如下所示 user SerializerField 是 PrimaryKeyRelatedField 并且正在与另一个 column/field 在 User 模型中 user.username - 我将其设为默认值 PrimaryKeyRelatedFieldpk 我不想在 API[= 上公开它37=]

  • url 键被自定义为 HyperlinkedRelatedField 以指向上面的字段 - user 和视图名称 user-related

serializer.py

class UserProfileSerializer(serializers.HyperlinkedModelSerializer):

    user = serializers.PrimaryKeyRelatedField(source='user.username', read_only=True)
    url = serializers.HyperlinkedRelatedField(read_only=True, view_name='user-detail', )

    class Meta:
        model = models.UserProfile
        fields = ['user', 'bio', 'created_on', 'url']
        extra_kwargs = {
            'last_updated': {
                'read_only': True
            },
            'user': {
                'read_only': True
            },
        }
  • 在视图上,我将 lookup_field 定义为 user 并覆盖 get_object 方法,因为现在查询集应由 username
views.py

class UserProfileViewSets(viewsets.ModelViewSet):

    authentication_classes = [TokenAuthentication, ]
    permission_classes = [rest_permissions.IsAuthenticated, permissions.UserProfileOwnerUpdate, ]
    queryset = models.UserProfile.objects.all()
    serializer_class = serializers.UserProfileSerializer
    renderer_classes = [renderers.AdminRenderer, renderers.JSONRenderer, renderers.BrowsableAPIRenderer, ]
    lookup_field = 'user'

    def perform_create(self, serializer):
        serializer.save(user=self.request.user)

    def get_object(self):
        queryset = self.filter_queryset(models.UserProfile.objects.get(user__username=self.kwargs.get('user')))
        return queryset

编辑:

我用另一种方法完成了要求,认为这种方法更简洁,所以下面进行修改。

  • 您需要在 kwargs 上方创建一个新的自定义 HyperLinkedIdentityField,检查下面的 kwargs,该值映射到 OneToOneForgienKey 定义的相关模型

    class AuthorHyperLinkedIdentityField(serializers.HyperlinkedIdentityField):
        def get_url(self, obj, view_name, request, format):
            if hasattr(obj, 'pk') and obj.pk is None:
                return None
            return self.reverse(view_name, kwargs={
                'obj_username': obj.author.username
            }, format=format, request=request)

  • 在视图中,您使用 CustomizedField
  • 中定义的 kwargs 覆盖 lookup_field

    class AuthorViewSet(viewsets.ModelViewSet):
        serializer_class = serializers.AuthorSerializer
        queryset = models.Author.objects.all()
        renderer_classes = [renderers.JSONRenderer, renderers.BrowsableAPIRenderer, renderers.AdminRenderer]
        # the below is not used but i keep it for reference
        # lookup_field = 'author__username'
        # the below should match the kwargs in the customized HyperLinkedIdentityField
        lookup_field = 'obj_username'

  • 最终的序列化器看起来像
class AuthorSerializer(serializers.HyperlinkedModelSerializer):
    """
    Serializers Author Model
    """

    # first_name = serializers.SlugRelatedField(source='author', slug_field='first_name',
    #                                           read_only=True)
    # last_name = serializers.SlugRelatedField(source='author', slug_field='last_name',
    #                                          read_only=True)
    author = serializers.PrimaryKeyRelatedField(queryset=models.User.objects.filter(groups__name='Authors'),
                                                write_only=True)
    name = serializers.SerializerMethodField()
    username = serializers.PrimaryKeyRelatedField(source='author.username', read_only=True)
    # the below commented line is building the URL field based on the lookup_field = username
    # which takes its value from the username PrimaryKeyRelatedField above
    # url = serializers.HyperlinkedRelatedField(view_name='user-detail', read_only=True)
    url = AuthorHyperLinkedIdentityField(view_name='author-detail', read_only=True)

    class Meta:
        model = models.Author
        fields = ['author', 'name', 'username', 'url', ]

    def get_name(self, author):
        return '%s %s' % (author.author.first_name, author.author.last_name)
  • 下面作者模型供大家参考
class Author(models.Model):
    """
    A Model to store the Authors info
    """
    author = models.OneToOneField(User, on_delete=models.CASCADE, related_name='authors')
    is_author = models.BooleanField(default=True, editable=True, )

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['author'], name='check_unique_author')
        ]

    def __str__(self):
        return '%s %s' % (self.author.first_name, self.author.last_name)

    def author_full_name(self):
        return '%s %s' % (self.author.first_name, self.author.last_name)