Проверка безопасности в графеновом резольвере

Я использую Django и Graphene для обслуживания конечной точки graphql и столкнулся с небольшой проблемой, которую никак не могу решить.

У меня есть следующий резольвер:

class Query(ObjectType):
    trainingSession = Field(TrainingSessionType, id=graphene.ID())
    trainingSessions =  DjangoFilterConnectionField(TrainingSessionType)

   

    @staticmethod
    def checked_trainingsession(trainingsession,info):
       # returns the trainingsession if a certain logic is fulfilled
       # else None


    def resolve_trainingSessions(root, info,**kwargs):
        ids= kwargs.get('discipline__id')
        all = TrainingSession.objects.all()

        result = []
        for trainingSession in all:
            trainingSession = Query.checked_trainingsession(trainingSession,info)
            if trainingSession != None:
                result.append(trainingSession)
        return result

вместе с типами объектов и фильтрами:

class TrainingSessionFilter(FilterSet):
    discipline__id = GlobalIDMultipleChoiceFilter()
    class Meta:
        model = TrainingSession
        fields = ["discipline__id"]

class TrainingSessionType(DjangoObjectType):
    class Meta:
        model=TrainingSession
        fields="__all__"
        filterset_class = TrainingSessionFilter
        interfaces = (CustomNode,)


class CustomNode(graphene.Node):
    """
        For fetching object id instead of Node id
    """
    class Meta:
        name = 'Node'

    @staticmethod
    def to_global_id(type, id):
        return id

однако когда я пытаюсь выполнить запрос


query Sessions{
    trainingSessions(discipline_Id:[2,3]){
    edges{
      node{
        dateTime,
        discipline{
          id
        }
      }
    }
  }
}

Я получаю ошибку:

Traceback (most recent call last):
  File "D:\Ben\GitHub-Repos\dojo-manager\env\lib\site-packages\promise\promise.py", line 489, in _resolve_from_executor
    executor(resolve, reject)
  File "D:\Ben\GitHub-Repos\dojo-manager\env\lib\site-packages\promise\promise.py", line 756, in executor
    return resolve(f(*args, **kwargs))
  File "D:\Ben\GitHub-Repos\dojo-manager\env\lib\site-packages\graphql\execution\middleware.py", line 75, in make_it_promise
    return next(*args, **kwargs)
  File "D:\Ben\GitHub-Repos\dojo-manager\env\lib\site-packages\graphene_django\fields.py", line 176, in connection_resolver
    iterable = queryset_resolver(connection, iterable, info, args)
  File "D:\Ben\GitHub-Repos\dojo-manager\env\lib\site-packages\graphene_django\filter\fields.py", line 62, in resolve_queryset
    return filterset_class(data=filter_kwargs, queryset=qs, request=info.context).qs
  File "D:\Ben\GitHub-Repos\dojo-manager\env\lib\site-packages\django_filters\filterset.py", line 193, in __init__
    model = queryset.model
graphql.error.located_error.GraphQLLocatedError: 'list' object has no attribute 'model'

Я знаю, что должен возвращать набор запросов из resolve_trainingSessions. Однако я не знаю, как затем применить проверку разрешений к отдельным результатам. Логика не очень сложная, но я не могу обернуть ее в стандартный фильтр модели Django или объект Q.

Спасибо за любую помощь или подсказки.

Ok Мне удалось решить свою проблему, следуя этой идее: https://docs.graphene-python.org/projects/django/en/latest/authorization/#user-based-queryset-filtering

if user.is_anonymous:
    return TrainingSession.objects.none()

if user.is_superuser:
    return TrainingSession.objects.filter(filter)
....

Не супер элегантный, но он делает свою работу и не слишком плох.

Итак, как вы уже догадались, причина, по которой запросы с типами DjangoFilterConnectionField должны возвращать queryset, а не list, заключается в том, чтобы правильно работала пагинация, которая поставляется из коробки. К сожалению, для пользователей, которым важна только фильтрация, но не пагинация, отказаться от возврата queryset не представляется возможным. Поэтому у вас есть три варианта. (PS Я внес некоторые незначительные изменения в ваши фрагменты кода, (например, использование @classmethod)

  1. Вы не используете DjangoFilterConnectionField
class Query(ObjectType):
    trainingSession = Field(TrainingSessionType, id=graphene.ID())
    trainingSessions =  graphene.List(TrainingSessionType, discipline__id=grapene.List(graphene.ID)

   
    @staticmethod
    def checked_trainingsession(trainingsession,info):
       # returns the trainingsession if a certain logic is fulfilled
       # else None

    @classmethod
    def resolve_trainingSessions(cls, info, discipline__id):
        return [ts for ts in TrainingSession.objects.filter(id__in=discipline_id) if cls.checked_trainingsession(ts, info)]

Преимущество этого метода в том, что в случае, если в вашем кверисете есть элементы, которые вам разрешено видеть, то вы можете вернуть их, не возвращая объект кверисета (вы можете выполнять фильтрацию без базы данных).

  1. Вы очень стараетесь написать свой checked_trainigsession как фильтр queryset - это может показаться невозможным, но после достаточного количества пота вы сможете это сделать... я был там.

  2. Как вы сделали здесь, вы жертвуете возможностью возвращать частичные данные, если пользователю разрешено видеть только некоторые элементы, и просто выдаете ошибку, когда этого не происходит. Способ, которым вы возвращаете queryset.objects.none(), хорош, но вы можете так же легко выдать ошибку (что может быть элегантнее?)

from graphql_jwt.decorators import superuser_required

class Query(ObjectType):
    trainingSession = Field(TrainingSessionType, id=graphene.ID())
    trainingSessions =  DjangoFilterConnectionField(TrainingSessionType)

    @superuser_required
    def resolve_trainingSessions(root, info,**filters):
        return TrainingSessionFilter(filters).qs

Вы также можете поменять superuser_required на login_required или staff_member_required. Удачи!

Вернуться на верх