Axios 到 django-backend post 图像失败

Axios to django-backend post image fail

我正在使用 React 前端通过 axios 与 Django 后端进行通信。出于某种原因,我无法 post 形成包含图像的表格。我通过 Postman 测试了我的 django 后端,它运行良好。

后端在终端显示code 200为成功但未保存数据,前端未通过错误

如果我排除了图像,表单可以 post 成功。请检查下面我的代码;

form.js 文件

import React, { useState } from 'react';
import { Helmet } from 'react-helmet';
import axios from 'axios';
import { connect } from 'react-redux';
import { setAlert } from '../actions/alert';
import './ApplicationForm.css';
import { useNavigate } from 'react-router-dom';
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { IconButton } from '@mui/material';

function ApplicationForm({ setAlert }) {
   const [show, setShow] = useState (false);
   const navigate=useNavigate();

 const [formData, setFormData] = useState({
    pasport_number: '', first_name: '', last_name: '', passport_photo: ''
 });

 const { pasport_number, first_name, last_name, passport_photo } = formData;

 const onChange = e => setFormData({ ...formData, [e.target.name]: e.target.value });
 console.log (formData)
 const onSubmit = e => {
     e.preventDefault();

     const config = {
         headers: {
             'Content-Type': 'application/json'
         }
     };

     axios.post(`${process.env.REACT_APP_API_URL}/api/application-form/`, { pasport_number, first_name, last_name, passport_photo }, config)
     .then(_res => {
           navigate('/');
           setAlert('Application Submitted Successfully', 'success');
     })
     .catch(_err => {
         setAlert('Error with Sending Application', 'error');
     })
 };

 return (
       <div className='applicationForm'>
           <div className='wrapper'>
                   <Helmet>
                       <title>Prosperity - Visa Application Form</title>
                       <meta
                           name='description'
                           content='Filling Application Form '
                       />
                   </Helmet>
               <div className='applicationForm__wrapper'>
                   <h1 className='applicationForm__title'>Application Form</h1>
                   <hr className='appform__hr'/>
                   <form className='applicationForm__form' onSubmit={e => onSubmit(e)}>
                       <div className='form__wrap'>
   
                           <hr className='appform__hr'/>

                           <p className='passport__section__title'>Enter or Import Passport Details</p>
                           <div className='input_grp'>
                               <div className='input_wrap input_wrap__bt'>
                                   <p className='expand__p'>Enter Passport details</p>
                                   <IconButton onClick={()=> setShow(true)}>
                                       <ExpandMoreIcon className='Expand__bt'/>
                                   </IconButton>
                                   <IconButton onClick={()=> setShow(false)}>
                                       <ExpandLessIcon className='Expand__less__bt'/>
                                   </IconButton>
                               </div>
                               <div className='input_wrap input_wrap__bt import__passport'>
                                   <label className='applicationForm__form__label' htmlFor='passport_photo'>Import Passport Image</label>
                                   <input 
                                       className='input__for__two import__passport' 
                                       name='passport_photo' 
                                       type='file' 
                                       accept='image/*,.pdf'
                                       placeholder='Import Passport' 
                                       onChange={e => onChange(e)} 
                                       value={passport_photo} 
                                       required 
                                   />
                               </div>
                           </div>

                           { show?
                               <>
                               <div className='input_grp'>
                                   <div className='input_wrap'>
                                       <label className='applicationForm__form__label appForm__subject' htmlFor='first_name'>First Name</label>
                                       <input 
                                           className='applicationForm__form__input input__for__two' 
                                           name='first_name' 
                                           type='text' 
                                           placeholder='First Name *' 
                                           onChange={e => onChange(e)} 
                                           value={first_name} 
                                           required 
                                       />
                                   </div>
                                   <div className='input_wrap'>
                                       <label className='applicationForm__form__label' htmlFor='last_name'>Last Name</label>
                                       <input 
                                           className='applicationForm__form__input input__for__two' 
                                           name='last_name' 
                                           type='text' 
                                           placeholder='Last Name *' 
                                           onChange={e => onChange(e)} 
                                           value={last_name} 
                                           required 
                                       />
                                   </div>
                               </div>

                               <div className='input_grp'>
                                   <div className='form__wrap'>
                                       <label className='applicationForm__form__label' htmlFor='pasport_number'>Passport Number</label>
                                       <input 
                                           className='applicationForm__form__input input__for__two' 
                                           name='pasport_number' 
                                           type='text' 
                                           placeholder='Passport Number' 
                                           onChange={e => onChange(e)} 
                                           value={pasport_number} 
                                           required 
                                       />
                                   </div>
                               </div>
                               </>
                               :null
                           }
                           <hr className='appform__hr'/>
                           <button className='contact__form__button' htmltype='submit'>Send</button>
                       </div>
                   </form>
               </div>
           </div>
       </div>
 )
}

export default connect(null, { setAlert })(ApplicationForm);

console.log(表格数据);

{pasport_number: 'test3', first_name: 'test1', last_name: 'test2', passport_photo: 'C:\fakepath\signature.jpg'}
first_name: "test1"
last_name: "test2"
pasport_number: "test3"
passport_photo: "C:\fakepath\signature.jpg"
[[Prototype]]: Object

更新的形式包括 react-file-base64 ;

import React, { useState } from 'react';
import { Helmet } from 'react-helmet';
import axios from 'axios';
import { connect } from 'react-redux';
import { setAlert } from '../actions/alert';
import './ApplicationForm.css';
import { useNavigate } from 'react-router-dom';
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { IconButton } from '@mui/material';
import FileBase from "react-file-base64";


function ApplicationForm({ setAlert }) {
    const [show, setShow] = useState (false);
    const navigate=useNavigate();

  const [formData, setFormData] = useState({
     pasport_number: '', passport_photo: ''
  });

  const { pasport_number, passport_photo } = formData;

  const onChange = e => setFormData({ ...formData, [e.target.name]: e.target.value });
  console.log (formData);
  const onSubmit = e => {
      e.preventDefault();

      const config = {
          headers: {
        'Content-Type': 'multipart/form-data',
        'Accept': 'application/json'          
        }
      };

      axios.post(`${process.env.REACT_APP_API_URL}/api/application-form/`, { pasport_number, passport_photo }, config)
      .then(_res => {
            setAlert('Application Submitted Successfully', 'success');
      })
      .catch(_err => {
          setAlert('Error with Sending Application', 'error');
      })
  };

  return (
        <div className='applicationForm'>
            <div className='wrapper'>
                    <Helmet>
                        <title>Prosperity - Visa Application Form</title>
                        <meta
                            name='description'
                            content='Filling Application Form '
                        />
                    </Helmet>
                <div className='applicationForm__wrapper'>
                    <h1 className='applicationForm__title'>Application Form</h1>
                    <hr className='appform__hr'/>
                    <form className='applicationForm__form' onSubmit={e => onSubmit(e)} enctype="multipart/form-data">
                        <div className='form__wrap'>
    
                            <hr className='appform__hr'/>

                            <p className='passport__section__title'>Enter or Import Passport Details</p>
                            <div className='input_grp'>
                                <div className='input_wrap input_wrap__bt'>
                                    <p className='expand__p'>Enter Passport details</p>
                                    <IconButton onClick={()=> setShow(true)}>
                                        <ExpandMoreIcon className='Expand__bt'/>
                                    </IconButton>
                                    <IconButton onClick={()=> setShow(false)}>
                                        <ExpandLessIcon className='Expand__less__bt'/>
                                    </IconButton>
                                </div>
                                <FileBase
                                    type="file"
                                    multiple={false}
                                    onDone={({ base64 }) =>
                                    setFormData({ ...formData, passport_photo: base64 })}
                                />
                            </div>

                            { show?
                                <>

                                <div className='input_grp'>
                                    <div className='form__wrap'>
                                        <label className='applicationForm__form__label' htmlFor='pasport_number'>Passport Number</label>
                                        <input 
                                            className='applicationForm__form__input input__for__two' 
                                            name='pasport_number' 
                                            type='text' 
                                            placeholder='Passport Number' 
                                            onChange={e => onChange(e)} 
                                            value={pasport_number} 
                                            required 
                                        />
                                    </div>
                                </div>
                                </>
                                :null
                            }
                            <hr className='appform__hr'/>
                            <button className='contact__form__button' htmltype='submit'>Send</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
  )
}

export default connect(null, { setAlert })(ApplicationForm);

已更新 console.log ;

{pasport_number: 'TEST99', passport_photo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB4sA…LzzTqZ8wa/uf//J//+/8PoXjMydjnS20AAAAASUVORK5CYII='}
pasport_number: "TEST99"
passport_photo: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB4s
[[Prototype]]: Object

django settings.py ;

from datetime import timedelta
import os
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

SECRET_KEY = 'xxx'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'accounts.apps.AccountsConfig',
    'application_form.apps.ApplicationFormConfig',
    'social.apps.SocialConfig',
    'contacts.apps.ContactsConfig',
    'rest_framework',
    'djoser',
    'corsheaders',
    'rest_framework_simplejwt.token_blacklist'
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'pros.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'build')], 
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'prosperity.wsgi.application'


# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'prosdb',
        'USER': 'postgres',
        'PASSWORD': 'xxxxx',
        'HOST': 'localhost'
    }
}

# email addition
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.xxxx.com'
EMAIL_PORT = 587
EMAIL_HOST_USER = 'xxx@xxx.xx'
EMAIL_HOST_PASSWORD = 'xxxxx'
EMAIL_USE_TLS = True
MAIL_FROM_ADDRESS='xxxxxx'


# Password validation
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/4.0/topics/i18n/

LANGUAGE_CODE = 'en-GB'

TIME_ZONE = 'CET'

DATE_INPUT_FORMATS = [
'%d-%m-%Y', '%Y-%m-%d', 
'%m/%d/%Y', '%m/%d/%y',
'%b %d %Y', '%b %d, %Y',  
'%d %b %Y', '%d %b, %Y',  
'%B %d %Y', '%B %d, %Y',  
'%d %B %Y', '%d %B, %Y', 
]

USE_I18N = True

USE_TZ = True

DATA_UPLOAD_MAX_NUMBER_FIELDS = None

STATIC_URL = 'static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'build/static')
]
STATIC_ROOT = os.path.join(BASE_DIR, 'static')

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated'
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.MultiPartParser'
    ],
}

CORS_ORIGIN_ALLOW_ALL = True

FILE_UPLOAD_PERMISSIONS=0o640

# Token settings
SIMPLE_JWT = {
    'AUTH_HEADER_TYPES': ('JWT',),
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
    'AUTH_TOKEN_CLASSES': (
        'rest_framework_simplejwt.tokens.AccessToken',
    )
}

DJOSER = {
    'LOGIN_FIELD':'email',
    'USER_CREATE_PASSWORD_RETYPE': True,
    'USERNAME_CHANGED_EMAIL_CONFIRMATION': True,
    'PASSWORD_CHANGED_EMAIL_CONFIRMATION': True,
    'SEND_CONFIRMATION_EMAIL': True,
    'SET_USERNAME_RETYPE': True,
    'SET_PASSWORD_RETYPE': True,
    'PASSWORD_RESET_CONFIRM_URL': 'password/reset/confirm/{uid}/{token}',
    'USERNAME_RESET_CONFIRM_URL': 'email/reset/confirm/{uid}/{token}',
    'ACTIVATION_URL': 'activate/{uid}/{token}',
    'SEND_ACTIVATION_EMAIL': True,
    'PASSWORD_RESET_CONFIRM_RETYPE': True,
    'SERIALIZERS': {
        'user_create': 'accounts.serializers.UserCreateSerializer',
        'user': 'accounts.serializers.UserCreateSerializer',
        'user_delete': 'djoser.serializers.UserDeleteSerializer',
    }
}

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

AUTH_USER_MODEL = 'accounts.UserAccount'

这是一个解决方案,但必须有更好的方法来避免创建另一个 useState。

我创造了; const [image, setImage] = useState(null);

然后在输入; onChange{(e)=>setImage(e.target.files[0])}

我认为可能有一种方法可以在我的代码中将 (e.target.files[0])) 与 passport_photo 一起使用,那会很棒。