如何隐藏/排除graphene_django中请求实体的某些外键字段?

Posted

技术标签:

【中文标题】如何隐藏/排除graphene_django中请求实体的某些外键字段?【英文标题】:How to hide/exclude certain fields of foreign key in graphene_django wrt the requested entity? 【发布时间】:2020-12-09 05:44:06 【问题描述】:

我已经阅读了如何在这些链接中排除(隐藏)django 和 graphene_django 中的某些字段:

graphql-python repository Why does my excluded field still appear in this Django form? 等

假设我们有以下Post 模型,它具有User 模型的外键。

apps/posts/models.py

from django.db import models
from apps.users.models import User

class Post(models.Model):
    author = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        null=False
    )

    created_at = models.DateTimeField(
        auto_now_add=True,
    )

    title = models.TextField(
        null=False,
        max_length=100,
    )

    content = models.TextField(
        null=False,
    )

    def __str__(self):
        return self.title

apps/users/models.py

from django.db import models
from django.contrib.auth.models import AbstractUser

class User(AbstractUser, models.Model):
    phone_no = models.CharField(
        blank=True,
        null=True,
        default="",
        max_length=10,
        verbose_name="Phone Number",
    )

    avatar = models.ImageField(
        null=True,
        upload_to='static',
    )

    USERNAME_FIELD = "username"
    EMAIL_FIELD = "email"

    def __str__(self):
        return self.username

我尝试了以下方法,但没有按预期工作:

apps/posts/schema.py

import graphene
from graphene import Mutation, InputObjectType, ObjectType
from graphene_django.types import DjangoObjectType

from .models import Post


class PostType(DjangoObjectType):
    class Meta:
        model = Post
        exclude_fields = [
            'created_at', #it worked
            'author.password', #the way I tried to hide the foreign key field
            
        ]

class Query(ObjectType):
    posts = graphene.List(
        PostType
    )

    def resolve_posts(self, info):
        #TODO: pagination
        return Post.objects.all()

截图:

如何在 graphql 模型类型中隐藏它的某些字段(如上例中的作者密码)?

【问题讨论】:

如何创建一个排除字段passwordAuthorType(DjangoObjectType) 并在PostTypeauthor=graphene.Field(AuthorType) 中使用此类型 @SagarAdhikari 你的意思是把它添加到 PostType 元类中? 否,在元类之上,(类似于类变量)。 @SagarAdhikari 你能给我举个例子吗?想象一下您根据不同的权限执行此操作的情况。例如,超级用户可以看到电子邮件字段,但普通用户不能。 GraphQL API 需要事先指定您可以从服务器获取/发布到服务器的数据类型。因此,据我所知,它违反了 graphql 标准。但是您显然可以检查权限并向普通用户发送消息“此字段不可访问”,而不是发送数据。 【参考方案1】:

从 cmets 了解到,您有一个 UserType

您可以在元数据中使用 exclude 选项,

class UserType(DjangoObjectType):
    class Meta:
        model = User
        exclude = ('password',)

更新

您还可以使用自定义解析器来检查请求的实体

class UserType(DjangoObjectType):
    password = graphene.String()

    def resolve_password(self, info):
        requested_user = info.context.user
        if requested_user.email in ['admin@test.com', 'ceo@test.com']:
            return self.password
        return None

    class Meta:
        model = User
        fields = '__all__'

【讨论】:

我不想为所有用户排除它。正如我在帖子中解释的那样,我想对具有不同权限的特定用户隐藏它。 哦..我明白了。 AFAIK,没有开箱即用的方法来做这种权限检查 所以你的意思是它有严重的安全问题?如果您能给我投票,我将不胜感激 我认为这不是安全问题“问题”。我认为这是 “此功能尚未在石墨烯中实现” 那么 Facebook、Instagram 和其他人是如何使用它的呢?你的意思是他们为此尝试了不同的API?喜欢restful api?【参考方案2】:

这只是在解析查询字段之前检查权限的答案。 (所以,不是原始问题的答案)

这样的事情会起作用。


def permission_check_my_field(func):
    @wraps(func)
    def wrapper(self,info,**kwargs):
        user=info.context.user
        if (......) # permit condition here
           return func(self, info,**kwargs)
        else:
            return None
    return wrapper

class  Query(graphene.ObjectType):
    my_field = graphene.Field(...) # or graphene.List or .....
    
    @permission_check_my_field
    def resolve_my_field(......)
      # do your normal work

更新

如果用户数据足以检查该字段是否可访问(如other answer),则上述代码有效。但是,如果您需要检查用户是否已被授予访问该字段的某些权限,那么您需要这样做:


def permission_check_in_query(perm):
    def wrapped_decorator(func):
        @wraps(func)
        def wrapper(self,info,**kwargs):
            user=info.context.user
            if user.has_perm(perm) # check if the user has privilege
                return func(self, info,**kwargs)
            else:
                return None 
                # All fields are not nullable, so  `return None' might throw error. You can pass `what you need to return` in decorator argument and use it here , to avoid this.   
        return wrapper
    return wrapped_decorator

class  Query(graphene.ObjectType):
    my_field = graphene.Field(...) # or graphene.List or .....
    
    @permission_check_in_query('model.access_my_field') # your permission code 
    # learn django permissions if you are not sure what it is
    # doesn't have to be django_permission, can be any argument like 'is_it_a_superuser' that you will use to check user privilege. Modify decorator code accordingly
    def resolve_my_field(......)
      # do your normal work

这样做,您可以重复使用任何字段和任何权限。只需将装饰器 @permission_check_in_query(your arguments) 添加到需要在解析之前进行权限检查的任何字段上方即可。

TLDR:关于 API 接受和返回的数据类型,此答案类似于 other answer。它只是提供可重用性和权限检查。

【讨论】:

我已经为这个问题添加了赏金,如果你能指导我该怎么做并更新你的答案,我将不胜感激。 我给出的这个答案与另一个答案相似,除了它返回“PermissionDenied”并且我刚刚添加了可重用的装饰器。您可以创建一个接受参数的装饰器,即您的权限代码(这里我使用了无参数装饰器),这将使其可用于任何字段。 现在,我更新了带有参数的 docorator,您可以在其中通过权限和检查。 现在一切正常:)

以上是关于如何隐藏/排除graphene_django中请求实体的某些外键字段?的主要内容,如果未能解决你的问题,请参考以下文章

在 graphene/graphene_django 中扩展查询参数

在引导表中如何隐藏/排除导出列

如何使 tar 排除隐藏目录

graphene_django 源码走读

如何隐藏所有排除文件类型

无法在 graphene_django 中获取 OneToOne 关系查询的值