使用嵌套结构的参数使用 django-graphene 过滤父级

Posted

技术标签:

【中文标题】使用嵌套结构的参数使用 django-graphene 过滤父级【英文标题】:Using arguments to nested structures for filtering parents using django-graphene 【发布时间】:2021-10-13 01:10:53 【问题描述】:

我目前正在使用带有 Graphene 的 Python 和 Django 来为我的 Graphql 后端建模。我的问题类似于 Graphene 的 repo https://github.com/graphql-python/graphene/issues/431 上的这个问题。我也在使用 graphene_django_optimizer 库来提高性能。

但是,我很难理解如何在当前情况下将@syrusakbary 提出的解决方案应用于我的问题。任何帮助将不胜感激

这是我要执行的查询

    getUser(userId: $userId)
    
        id
        username
        trainings
            id
            name
            sessions
                id    
                createdAt
                completedAt
                category
            
        
    

培训是正确的,只有属于该特定用户 ID 的培训。然而,每次培训的所有课程都是为所有用户带来的。我希望会话也特定于该单个用户。在我的 types.py 上是相关类型

class SessionType(DjangoObjectType):

    class Meta:
        model = Session
        fields = "__all__"
        convert_choices_to_enum = False

    @classmethod
    def get_queryset(cls, queryset, info, **kwargs):

        if info.context.user.is_anonymous:
            return queryset.order_by('-id')
        return queryset
class TrainingType(gql_optimizer.OptimizedDjangoObjectType):

    class Meta:
        model = Training
        fields = "__all__"
        convert_choices_to_enum = False
class UserType(DjangoObjectType):
    class Meta:
        model = get_user_model()
        fields = "__all__"

这是我的相关模型:

class Training(models.Model):
    name = models.CharField(max_length=200, help_text='Training\'s name')
    details = models.TextField(default="", help_text='Descriptive details about the training')
    course = models.ForeignKey("Course", related_name="trainings", on_delete=models.CASCADE)
    user = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="trainings")
    metric_cards = models.TextField(default="", help_text="An object describing the metrics to be used. (Optional)")

    def __str__(self):
        return str(self.id) + ' - ' + self.name


class Session(models.Model):
    name = models.CharField(max_length=200, help_text='Session\'s name')
    category = models.CharField(max_length=240, choices=SESSION_CATEGORIES, default="practice",
                                help_text='Session type. Can be of \'assessment\''
                                          'or \'practice\'')
    total_steps = models.IntegerField(default=1, help_text='Amount of steps for this session')
    created_at = models.DateTimeField(editable=False, default=timezone.now, help_text='Time the session was created'
                                                                                      '(Optional - default=now)')
    completed_at = models.DateTimeField(editable=False, null=True, blank=True, help_text='Time the session was finished'
                                                                                         '(Optional - default=null)')
    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="training_sessions", on_delete=models.DO_NOTHING)
    training = models.ForeignKey("Training", related_name="sessions", on_delete=models.CASCADE)

    def __str__(self):
        return self.name

根据每种类型,我的解析器位于单独的文件中,例如 类 SessionQuery(graphene.ObjectType) 包含与会话相关的所有解析器。像这样的:

class SessionQuery(graphene.ObjectType):
    debug = graphene.Field(DjangoDebug, name='_debug')

    # Session Queries
    session = graphene.Field(SessionType, id=graphene.Int(), user_id=graphene.Int(required=True))
    last_created_session = graphene.Field(SessionType)
    all_sessions = graphene.List(SessionType,
                                 first=graphene.Int(),
                                 skip=graphene.Int(),)
    latest_activities = graphene.List(LatestActivity, limit=graphene.Int())
    activity_in_range = graphene.List(UserRangeActivity, start_date=graphene.Date(), end_date=graphene.Date())
    unique_users_activity_in_range = graphene.Int(start_date=graphene.Date(), end_date=graphene.Date())

 def resolve_last_created_session(root, info):
        return Session.objects.latest('id')

    def resolve_all_sessions(root, info,first=None,skip=None):
        if skip:
            return gql_optimizer.query(Session.objects.all().order_by('-id')[skip:], info)
        elif first:
            return gql_optimizer.query(Session.objects.all().order_by('-id')[:first], info)
        else:
            return gql_optimizer.query(Session.objects.all().order_by('-id'), info)

(只是代码的一部分,因为太多了,其余的无关紧要)

现在,据我所知,要实现我想要的,我需要在 TrainingQuery 类下有一个类似 resolve_sessions 的东西,在那里我可以有一个参数 user_id,我只是从嵌套链中传递下来的。但我没有该领域的解析器。 Sessions 是一个 Session 的列表,它是 Training 模型中的外键,当我在查询中有这样的东西时会自动带上这个列表:


training
  
    id
    name
    sessions 
      id
      name
    
  

我想我想要实现的查询是这样的:

query getUser($userId: Int!) 
    getUser(userId: $userId)
    
        id
        username
        trainings
            id
            name
            sessions(userId: Int)
                id    
                createdAt
                completedAt
                category
            
        
    

但是我可以在哪个地方/解析器实现这样的事情?它在我的 SessionType 的 get_queryset 方法中吗?如果有,怎么做?

我在正确的道路上吗?

【问题讨论】:

【参考方案1】:

我找到了解决方案。是的,我走在正确的轨道上。问题实际上是关于石墨烯的糟糕文档。我不得不打开 ResolveInfo 对象的源代码。可以看here

基本上,在查询的父级传递的参数在 info.variable_values 下可用。所以我需要做的就是修改 get_queryset 方法并像这样:

class SessionType(DjangoObjectType):
    class Meta:
        model = Session
        fields = "__all__"
        convert_choices_to_enum = False

    @classmethod
    def get_queryset(cls, queryset, info, **kwargs):
        if info.variable_values.get('userId') and info.variable_values.get('userId') is not None:
            return queryset.filter(Q(user_id=info.variable_values.get('userId')))
        return queryset

这是一件非常重要的事情,我们通常希望过滤器以这种方式工作。我希望他们将这个“技巧”添加到他们的文档中。希望这个答案能帮助遇到同样问题的其他人

【讨论】:

【参考方案2】:

听起来您想修改TrainingType 上的sessions 字段以添加新参数,并向类添加resolve_sessions 方法。

根据我在您的问题中看到的情况,应该是这样的:

class TrainingType(graphene.ObjectType):
    # other fields omitted
    sessions = graphene.List(
        SessionType,
        user_id=graphene.ID(),
        first=graphene.Int(),
        skip=graphene.Int(),
    )

    def resolve_sessions(self, info, user_id=None, first=None, skip=None):
        # Change this to do something other than return an empty list
        return []
   

【讨论】:

不,不幸的是,这不起作用。因为会话是由 fk 关系自动生成的,所以添加这个解析不会覆盖任何会话应该带来的。但好消息是我找到了方法。我会在这里发布答案:)。无论如何感谢您的帮助

以上是关于使用嵌套结构的参数使用 django-graphene 过滤父级的主要内容,如果未能解决你的问题,请参考以下文章

Rails 嵌套强参数,如何使用?

结构体(结构体嵌套结构体指针结构体参数传递)

从嵌套结构(带有其他结构的数组)创建字典 Swift

将嵌套参数传递到 Azure 数据工厂

C语言函数归纳

Objective-C++ 和 Swift - 桥接头中的嵌套结构