У меня вопрос об использовании apollo-upload-client
и graphene-django
. Здесь я обнаружил apollo-upload-client
добавление operations
в formData. Но пытается только noreferrer чтобы получить параметр query
. И вопрос, где и как это исправить?
Apollo-upload-client и графен-джанго
Ответы (2)
Если вы имеете в виду данные с заголовком, например (при просмотре HTTP из инструментов Chrome):
Content-Disposition: form-data; name="operations"
и данные вроде
{"operationName":"MyMutation","variables":{"myData"....}, "query":"mutation MyMutation"...}
,
библиотека graphene-python интерпретирует это и собирает для вас запрос, вставляя переменные и удаляя данные файла из запроса. Если вы используете Django, вы можете найти все загруженные файлы в info.context.FILES
при написании мутации.
Вот мое решение для поддержки последней версии apollo-upload-client (8.1). Недавно мне пришлось пересмотреть свой код Django, когда я обновился с apollo-upload-client 5.x до 8.x. Надеюсь это поможет.
Извините, я использую старую версию graphene-django, но, надеюсь, вы сможете обновить синтаксис мутации до последней версии.
Загрузите скалярный тип (в основном сквозной):
class Upload(Scalar):
'''A file upload'''
@staticmethod
def serialize(value):
raise Exception('File upload cannot be serialized')
@staticmethod
def parse_literal(node):
raise Exception('No such thing as a file upload literal')
@staticmethod
def parse_value(value):
return value
Моя мутация загрузки:
class UploadImage(relay.ClientIDMutation):
class Input:
image = graphene.Field(Upload, required=True)
success = graphene.Field(graphene.Boolean)
@classmethod
def mutate_and_get_payload(cls, input, context, info):
with NamedTemporaryFile(delete=False) as tmp:
for chunk in input['image'].chunks():
tmp.write(chunk)
image_file = tmp.name
# do something with image_file
return UploadImage(success=True)
Тяжелая работа происходит в настраиваемом представлении GraphQL. Обычно он вставляет файловый объект в соответствующие места на карте переменных.
def maybe_int(s):
try:
return int(s)
except ValueError:
return s
class CustomGraphqlView(GraphQLView):
def parse_request_json(self, json_string):
try:
request_json = json.loads(json_string)
if self.batch:
assert isinstance(request_json,
list), ('Batch requests should receive a list, but received {}.').format(
repr(request_json))
assert len(request_json) > 0, ('Received an empty list in the batch request.')
else:
assert isinstance(request_json, dict), ('The received data is not a valid JSON query.')
return request_json
except AssertionError as e:
raise HttpError(HttpResponseBadRequest(str(e)))
except BaseException:
logger.exception('Invalid JSON')
raise HttpError(HttpResponseBadRequest('POST body sent invalid JSON.'))
def parse_body(self, request):
content_type = self.get_content_type(request)
if content_type == 'application/graphql':
return {'query': request.body.decode()}
elif content_type == 'application/json':
return self.parse_request_json(request.body.decode('utf-8'))
elif content_type in ['application/x-www-form-urlencoded', 'multipart/form-data']:
operations_json = request.POST.get('operations')
map_json = request.POST.get('map')
if operations_json and map_json:
operations = self.parse_request_json(operations_json)
map = self.parse_request_json(map_json)
for file_id, f in request.FILES.items():
for name in map[file_id]:
segments = [maybe_int(s) for s in name.split('.')]
cur = operations
while len(segments) > 1:
cur = cur[segments.pop(0)]
cur[segments.pop(0)] = f
logger.info('parse_body %s', operations)
return operations
else:
return request.POST
return {}