Как я могу сохранить файл blob с отправкой формы с помощью React. js + Django Rest Framework - PullRequest
0 голосов
/ 30 мая 2020

Я пытаюсь отправить обрезанное изображение, сгенерированное в приложении реакции с помощью response-image-crop, и сохранить его в Django Rest Api с помощью Ax ios.

Приложение использует React , Redux и Ax ios на внешнем интерфейсе и Django Rest Framework на внутреннем интерфейсе.

Форма отправлялась без файла и сохранялась в django без кода для добавленного файла.

Теперь, когда файл добавлен в отправку формы, сервер возвращает ошибку 400 .

Я подозреваю, что я не отправляю большой двоичный объект в правильном формате на сервер django, но я не уверен, что делать дальше.

Обновление: я использовал ax ios ниже, чтобы преобразовать URL-адрес большого двоичного объекта в большой двоичный объект, и теперь я пытаюсь создать файл, который я могу отправить в django rest api. Форма отправляется в API django rest без файла, но когда файл добавляется в отправку формы, я получаю ошибку 400. Я обновил код, чтобы отразить мои последние интеграции. Я включил код, в котором я установил заголовки как multipart / form-data. Ошибка, по-видимому, связана с процессом преобразования файла в методе onSubmit () ниже.

Вот мой соответствующий код: Импорт библиотеки response-image-crop.

// Cropper
import 'react-image-crop/dist/ReactCrop.css';
import ReactCrop from 'react-image-crop';

Функция внутри хука реакции:

const AdCreator = ({ addFBFeedAd }) => {
  const [title, setTitle] = useState('');
  const [headline, setHeadline] = useState('');
  const [ad_text, setAdText] = useState('');
  const cropper = useRef();



  // Cropper
  const [upImg, setUpImg] = useState();
  const imgRef = useRef(null);
  const [crop, setCrop] = useState({ unit: '%', width: 30, aspect: 1.91 / 1 });
  const [previewUrl, setPreviewUrl] = useState();

  const onSelectFile = e => {
    if (e.target.files && e.target.files.length > 0) {
      const reader = new FileReader();
      reader.addEventListener('load', () => setUpImg(reader.result));
      reader.readAsDataURL(e.target.files[0]);
    }
  };

  const onLoad = useCallback(img => {
    imgRef.current = img;
  }, []);

  const makeClientCrop = async crop => {
    if (imgRef.current && crop.width && crop.height) {
      createCropPreview(imgRef.current, crop, 'newFile.jpeg');
    }
  };
  const makePostCrop = async crop => {
    if (imgRef.current && crop.width && crop.height) {
      createCropPreview(imgRef.current, crop, 'newFile.jpeg');
    }
  };

  const createCropPreview = async (image, crop, fileName) => {
    const canvas = document.createElement('canvas');
    const scaleX = image.naturalWidth / image.width;
    const scaleY = image.naturalHeight / image.height;
    canvas.width = crop.width;
    canvas.height = crop.height;
    const ctx = canvas.getContext('2d');

    ctx.drawImage(
      image,
      crop.x * scaleX,
      crop.y * scaleY,
      crop.width * scaleX,
      crop.height * scaleY,
      0,
      0,
      crop.width,
      crop.height
    );

    return new Promise((resolve, reject) => {
      canvas.toBlob(blob => {
        if (!blob) {
          reject(new Error('Canvas is empty'));
          return;
        }
        blob.name = fileName;
        window.URL.revokeObjectURL(previewUrl);
        setPreviewUrl(window.URL.createObjectURL(blob));
      }, 'image/jpeg');
    });
 };

  const onSubmit = (e) => {
    e.preventDefault();
    const config = { responseType: 'blob' };
    let file = axios.get(previewUrl, config).then(response => {
        new File([response.data], title, {type:"image/jpg", lastModified:new Date()});       
    }); 
    let formData = new FormData();
    formData.append('title', title);
    formData.append('headline', headline);
    formData.append('ad_text', ad_text);
    formData.append('file', file);
    addFBFeedAd(formData);


  };
  return (

Часть формы:

<form method="post" id='uploadForm'>                  
          <div className="input-field">
            <label for="id_file">Upload Your Image</label>
            <br/>
            {/* {{form.file}} */}
          </div>
          <div>
            <div>
              <input type="file" accept="image/*" onChange={onSelectFile} />
            </div>
            <ReactCrop
              src={upImg}
              onImageLoaded={onLoad}
              crop={crop}
              onChange={c => setCrop(c)}
              onComplete={makeClientCrop}
              ref={cropper}
            />
            {previewUrl && <img alt="Crop preview" src={previewUrl} />}
          </div>

            <button className="btn darken-2 white-text btn-large teal btn-extend" id='savePhoto' onClick={onSubmit} value="Save Ad">Save Ad</button>

        </form>

Вот Ax ios Call:

 export const addFBFeedAd = (fbFeedAd) => (dispatch, getState) => {
  setLoading();
  axios
    .post(`http://localhost:8000/api/fb-feed-ads/`, fbFeedAd, tokenMultiPartConfig(getState))
    .then((res) => {
      dispatch(createMessage({ addFBFeedAd: 'Ad Added' }));
      dispatch({
        type: SAVE_AD,
        payload: res,
      });
    })
    .catch((err) => dispatch(returnErrors(err)));
}

Здесь я установил заголовки для составных данных формы

export const tokenMultiPartConfig = (getState) => {
 // Get token from state
  const token = getState().auth.token;

  // Headers
  const config = {
    headers: {
      "Content-type": "multipart/form-data",
    },
  };

  // If token, add to headers config
  if (token) {
    config.headers['Authorization'] = `Token ${token}`;
  }

  return config;
};

Модель:

class FB_Feed_Ad(models.Model):
    title = models.CharField(max_length=100, blank=True)
    headline = models.CharField(max_length=25, blank=True)
    ad_text = models.CharField(max_length=125, blank=True)
    file = models.ImageField(upload_to='photos/%Y/%m/%d/', blank=True)

Большой двоичный объект предварительного просмотра кадрирования:

blob:http://localhost:3000/27bb58e5-4d90-481d-86ab-7baa717cc023

Обрезанное изображение в консоли. после вызова ax ios.

File:  
Promise {<pending>}
__proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: undefined
AdCreator.js:169 formData: 
FormData {}
__proto__: FormData

Как видите, я пытаюсь отправить файл изображения blob, созданный с помощью response-image-cropper, как часть данных формы при отправке формы . Я хочу сохранить обрезанное изображение в Django Rest API.
Есть предложения?

1 Ответ

1 голос
/ 30 мая 2020

вы должны отправить его как «Content-Type»: «multipart / form-data» в django imageField. Поэтому вам следует соответствующим образом преобразовать файл blob:

let cropImg = this.$refs.cropper.getCroppedCanvas().toDataURL();
let arr = this.cropImg.split(","),
    mime = arr[0].match(/:(.*?);/)[1],
    bstr = atob(arr[1]),
    n = bstr.length,
    u8arr = new Uint8Array(n);

while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
}

let imageCrop = new File([u8arr], 'imagename', { type: mime });

const fd = new FormData();
fd.append("avatar", imageCrop);

// send fd to axios post method. 
// You should pass in post request "Content-Type": "multipart/form-data" inside headers.
...