如何使用带有apollo-upload-client的graphene-file-upload将graphql中的文件上传到Python数据库并在前端做出反应。?

Posted

技术标签:

【中文标题】如何使用带有apollo-upload-client的graphene-file-upload将graphql中的文件上传到Python数据库并在前端做出反应。?【英文标题】:How to Upload files in graphql using graphene-file-upload with apollo-upload-client to Python Database and react front-end.? 【发布时间】:2020-01-08 18:44:00 【问题描述】:

我正在尝试使用 graphene-file-upload 将文件上传到 django 后端,该文件具有将后端链接到反应前端的突变,我正在尝试使用 apollo-upload 客户端将其与 graphql 链接。 在我的 django 模型中,一个空文件正在成功上传,但它没有上传我选择的真实文件,而是上传了一个空文件。 就像它什么都不上传 但实例是在数据库中创建的,其中添加了另一个空的故事。

这是我的一些代码。

我的数据库模型。模型.py

class Story(models.Model):
    file = models.FileField(null=True)
    created_at = models.DateTimeField(auto_now_add=True)

我的 schema.py

from graphene_file_upload.scalars import Upload

class StoryType(DjangoObjectType):
    class Meta:
        model = Story

class UploadFile(graphene.Mutation):
    story = graphene.Field(StoryType)

    class Arguments:
        file = Upload()

    def mutate(self, info, file):

        for line in file:
            print(line)

        story = Story(file=file)

        story.save()
        return UploadFile(story=story)

我的前端 File.js

import React from 'react';
import  Mutation  from 'react-apollo';
import withStyles from '@material-ui/core/styles';
import gql from 'graphql-tag';

const styles = theme => (
    layoutRoot: 
);


const UploadFile = () => (
  <Mutation
    mutation=gql`
      mutation($file: Upload!) 
        uploadFile(file: $file) 
          story 
            file
          
        
      
    `
  >
    mutate => (
      <input
        type="file"
        required
        onChange=(
          target: 
            validity,
            files: [file]
          
        ) => validity.valid && mutate( variables:  file  )
      />
    )
  </Mutation>
)

export default withStyles(styles, withTheme: true)(UploadFile);

【问题讨论】:

【参考方案1】:

现在我已经在 GraphQLView 中覆盖了 parse_body,以便它正确处理 multipart/form-data。

# views.py
from django.http.response import HttpResponseBadRequest
from graphene_django.views import GraphQLView

class MyGraphQLView(GraphQLView):

    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":
            # noinspection PyBroadException
            try:
                body = request.body.decode("utf-8")
            except Exception as e:
                raise HttpError(HttpResponseBadRequest(str(e)))

            try:
                request_json = json.loads(body)
                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 (TypeError, ValueError):
                raise HttpError(HttpResponseBadRequest("POST body sent invalid JSON."))

        # Added for graphql file uploads
        elif content_type == 'multipart/form-data':
            operations = json.loads(request.POST['operations'])
            files_map = json.loads(request.POST['map'])
            return place_files_in_operations(
                operations, files_map, request.FILES)

        elif content_type in [
            "application/x-www-form-urlencoded",
            #"multipart/form-data",
        ]:
            return request.POST

        return 

def place_files_in_operations(operations, files_map, files):
    # operations: dict or list
    # files_map: filename: [path, path, ...]
    # files: filename: FileStorage

    fmap = []
    for key, values in files_map.items():
        for val in values:
            path = val.split('.')
            fmap.append((path, key))

    return _place_files_in_operations(operations, fmap, files)


def _place_files_in_operations(ops, fmap, fobjs):
    for path, fkey in fmap:
        ops = _place_file_in_operations(ops, path, fobjs[fkey])
    return ops

def _place_file_in_operations(ops, path, obj):

    if len(path) == 0:
        return obj

    if isinstance(ops, list):
        key = int(path[0])
        sub = _place_file_in_operations(ops[key], path[1:], obj)
        return _insert_in_list(ops, key, sub)

    if isinstance(ops, dict):
        key = path[0]
        sub = _place_file_in_operations(ops[key], path[1:], obj)
        return _insert_in_dict(ops, key, sub)

    raise TypeError('Expected ops to be list or dict')

def _insert_in_dict(dct, key, val):
    return **dct, key: val


def _insert_in_list(lst, key, val):
    return [*lst[:key], val, *lst[key+1:]]

【讨论】:

【参考方案2】:

要求

from python_graphql_client import GraphqlClient
import asyncio
client = GraphqlClient(endpoint="http://localhost:8000/graphql")

客户端 -

def test_archive_upload(client):
    file_header = "data:application/zip;base64,"
    with open("files/Archive.zip", 'rb') as file:
    query = """
            mutation uploadFile($file: Upload) 
                uploadFile(file:$file) 
                    ok
                
            
        """
    file = file.read()
    file = file_header + base64.b64encode(file).decode("UTF-8")
    variables = "file": file
    data = client.execute(query=query, variables=variables)

运行-

asyncio.get_event_loop().run_until_complete(test_archive_upload(client))

服务器端 -

file = file.split(",")
file[0] = file[0]+","
file_type = guess_extension(guess_type(file[0])[0])
file = base64.b64decode(file[1])
with open("files/test"+ file_type, "wb") as w_file:
    w_file.write(file)

【讨论】:

以上是关于如何使用带有apollo-upload-client的graphene-file-upload将graphql中的文件上传到Python数据库并在前端做出反应。?的主要内容,如果未能解决你的问题,请参考以下文章

如何在本地使用带有 express 的 graphql 和带有 mongoose 的 mongodb 来检索文档?

如何使用带有路径的“开始”和带有空格的命令在 Windows 中创建批处理文件

如何避免在带有 Swift 4 的 iOS 11 中使用带有刷新控件的 @objc?

如何使用带有打字稿的“调试”模块

如何使用 Qt 发送带有图像附件的电子邮件?

如何使用python在sqlplus中插入带有'&'的值