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

Using arguments to nested structures for filtering parents using django-graphene

我目前正在使用 Python & Django 和 Graphene 来为我的 Graphql 后端建模。我的问题类似于 Graphene 的回购 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

我的解析器在不同的文件中,根据每种类型,例如 class 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 class 下有一个类似 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
            }
        }
    }
}

但是place/resolver我可以在哪个地方实现这样的事情呢?它是在我的 SessionType 上的 get_queryset 方法中吗?如果是,怎么做?

我走的路对吗?

听起来您想修改 TrainingType 上的 sessions 字段以添加新参数,并向 class 添加 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 []
   

我找到了解决方案。是的,我走在正确的轨道上。问题实际上是关于石墨烯的糟糕文档。我必须打开 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

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