Problems posting images from react to Django Rest API using redux - django

I'm trying to post an image in my Django API from a react form with no luck. I'm also using the image uploader from AntDesign library and redux.
Here's the code I've tried so far:
-Form Code:
class ArticleForm extends React.Component {
this.props.onSendNewArticle(
fieldsValue.upload.fileList[0].originFileObj);
render() {
<Form.Item
name="upload"
label="Image"
onPreview={this.handlePreview}>
<Upload accept=".jpeg, .png" beforeUpload={() => false}>
<Button>
<UploadOutlined /> Choisir une image
</Button>
</Upload>
</Form.Item>
}
const mapStateToProps = (state) => {
return {
loading_add_article: state.add_article.loading_add_article,
error_add_article: state.add_article.error_add_article,
};
};
const mapDispatchToProps = (dispatch) => {
return {
onSendNewArticle: (image) =>
dispatch(actions.articleAdded(image)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(CourseForm);
-Here's my view.py
class ArticleCreateView(APIView):
parser_classes = (MultiPartParser, FormParser)
def post(self, request, *args, **kwargs):
article_serializer = ArticleSerializer(data=request.data)
if article_serializer.is_valid():
article_serializer.save()
return Response(article_serializer.data, status=status.HTTP_201_CREATED)
else:
print('error', article_serializer.errors)
return Response(article_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
-Here's my store/actions/addArticle.js:
export const articleAdded = ( image) => {
return (dispatch) => {
dispatch(articleDispached);
axios
.post("http://localhost:8000/api/create/", {
img: image
})
.then((res) => {
const course = res.data;
dispatch(articleAddSucess(course));
})
.catch((err) => {
dispatch(articleAddFailed(err));
});
};
};
Here's the error that I get:
POST http://localhost:8000/api/create/ 415 (Unsupported Media Type)

export const articleAdded = ( image) => {
return (dispatch) => {
dispatch(articleDispached);
axios
.post("http://localhost:8000/api/create/", {
img: image
headers: { "content-type": "multipart/form-data" },
})
.then((res) => {
const course = res.data;
dispatch(articleAddSucess(course));
})
.catch((err) => {
dispatch(articleAddFailed(err));
});
};
};

Related

Send File via Fetch to Django REST Endpoint?

I'm trying to upload a file via Fetch, to a Django REST endpoint.
Component with Fetch Code:
function myComponent(props) {
const classes = useStyles();
const [{token}] = useContext();
function handleImageUpload(files) {
let formData = new FormData()
formData.append('file', files[0])
fetch(receiveSpreadsheetEndpoint, {
method: 'POST',
body: formData,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
})
.then(response => response.json())
.then(data => {
console.log(data)
})
.catch(error => {
console.error(error)
})
}
function Dropzone(props) {
const [files, setFiles] = useState([]);
function handleChange(event) {
setFiles(event[0]);
}
return (
<>
<DropzoneArea
onChange={event => handleChange(event)}
/>
<Button
variant="contained"
color="primary"
onClick={event => handleImageUpload([files])}
>
Primary
</Button>
</>
)
}
return (
<Container className={classes.root} height="100%">
<Dropzone
acceptedFiles={['.csv', 'text/*', 'text/csv']}
showPreviews={true}
showFileNamesInPreview={true}
/>
</Container>
);
}
Django REST endpoint:
class ReceiveFileData(APIView):
permission_classes = (IsAuthenticated,)
parser_classes = (JSONParser, FormParser, MultiPartParser)
def post(self, request):
my_file = request.stream.read
data = {} <== a breakpoint is set here
return Response(data, status=status.HTTP_200_OK)
After my_file = request.stream.read, my_file contains b''.
What am I leaving out?
UPDATE
Here's the request object when it arrives at the REST endpoint on the server:
This is now working. Here is working code in case it may be helpful to others.
REACT
import React, {useEffect, useRef, useState, Component} from 'react';
import 'typeface-roboto';
import {makeStyles} from '#material-ui/styles';
import {useContext} from "../../context";
import Container from "#material-ui/core/Container";
import {DropzoneArea} from 'material-ui-dropzone'
import Button from "#material-ui/core/Button";
//ENDPOINTS
const receiveSpreadsheetEndpoint = '/spreadsheet_generator/api/receive-spreadsheet/';
function UploadSpreadsheet(props) {
const classes = useStyles();
const [{token}] = useContext();
function handleImageUpload(event, theFile) {
event.stopPropagation();
event.preventDefault();
let formData = new FormData()
formData.append('file', theFile)
fetch(receiveSpreadsheetEndpoint, {
method: 'POST',
body: formData,
headers: {
Authorization: `Bearer ${token}`,
},
})
.then(response => response.json())
.then(data => {
console.log(data)
})
.catch(error => {
console.error(error)
})
}
function Dropzone(props) {
const [file, setFile] = useState(null);
function handleChange(event) {
setFile(event[0]);
}
return (
<>
<DropzoneArea
onChange={event => handleChange(event)}
filesLimit={1}
/>
<Button
variant="contained"
color="primary"
onClick={event => handleImageUpload(event, file)}
>
Primary
</Button>
</>
)
}
return (
<Container className={classes.root} height="100%">
<Dropzone
acceptedFiles={['.csv', 'text/*', 'text/csv']}
showPreviews={true}
showFileNamesInPreview={true}
/>
</Container>
);
}
//https://material-ui.com/components/buttons/
const useStyles = makeStyles(theme => ({
root: {
'& > *': {
margin: theme.spacing(1),
},
},
}))
;
export default UploadSpreadsheet;
DJANGO REST ENDPOINT
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.parsers import FormParser, MultiPartParser, JSONParser, FileUploadParser
import logging
from server_modules.spreadsheet.models import SourceSpreadsheet
logger = logging.getLogger(__name__)
logging.basicConfig(
level=logging.DEBUG,
format='%(name)s %(levelname)s %(message)s',
)
def create_spreadsheet_record(file):
# insert record to SourceSpreadsheet
SourceSpreadsheet.objects.create(file=file)
class ReceiveData(APIView):
permission_classes = (IsAuthenticated,)
parser_classes = (FileUploadParser,)
def post(self, request):
success = True
message = 'OK'
file = request.stream.FILES['file']
# save to db
try:
create__spreadsheet_record(file)
except Exception as e:
print("Error calling ReceiveData: " + str(e))
message = str(e)
success = False
data = {'message': message }
if (success):
return Response(data, status=status.HTTP_200_OK)
else:
return Response(data, status=status.HTTP_400_BAD_REQUEST)
DJANGO MODEL
class SourceSpreadsheet(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
file = models.FileField(upload_to='source_spreadsheets/%Y/%m')
BASE.PY
Add:
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

Vue Test Utils / Jest - How to test if class method was called within a component method

I have an interesting problem with a unit test of mine. My unit test is written to click on a button inside a component. This button calls a component method which contains an instance of a class Service (a wrapper class for axios). The only thing this component method does is call Service.requestPasswordReset(). My unit test needs to verify that Service.requestPasswordReset was called.
I know I'm mocking my Service class correctly, because this passes in my unit test:
await Service.requestPasswordReset()
expect(Service.requestPasswordReset).toHaveBeenCalled()
And I know that I'm calling the method correctly on click because this passes in my unit test:
await wrapper.find('button').trigger('click')
expect(mockMethods.resend).toHaveBeenCalled()
I just can't get my test to register that the Service method gets called. Any ideas?
Component
<template lang="pug">
Layout
section
header( class="text-center py-4 pb-12")
h1( class="text-grey-boulder font-light mb-4") Recovery Email
p( class="text-orange-yellow") A recovery email has been sent to your email address
div( class="text-center")
div( class="mb-6")
button(
type="button"
#click.stop="resend()"
class="bg-orange-coral font-bold text-white py-3 px-8 rounded-full w-48"
) Resend Email
</template>
<script>
import Layout from '#/layouts/MyLayout'
import Service from '#/someDir/Service'
export default {
name: 'RecoveryEmailSent',
page: {
title: 'Recovery Email Sent',
},
components: {
Layout,
},
data() {
return {
errorMessage: null
}
},
computed: {
userEmail() {
const reg = this.$store.getters['registration']
return reg ? reg.email : null
},
},
methods: {
async resend() {
try {
await Service.requestPasswordReset({
email: this.userEmail,
})
} catch (error) {
this.errorMessage = error
}
},
},
}
</script>
Service.js
import client from '#/clientDir/BaseClient'
class Service {
constructor() {
this.client = client(baseUrl)
}
requestPasswordReset(request) {
return this.client.post('/account_management/request_password_reset', request)
}
}
export { Service }
export default new Service()
Service.js in __mock__
export default {
requestPasswordReset: jest.fn(request => {
return new Promise((resolve, reject) =>
resolve({
data: {
statusCode: 'Success',
},
})
)
})
}
Unit Test
jest.mock('#/someDir/Service')
import { shallowMount, mount, createLocalVue } from '#vue/test-utils'
import RecoveryEmailSent from './AccountManagement.RecoveryEmailSent'
import Service from '#/someDir/Service'
const localVue = createLocalVue()
// localVue.use(Service) // <-- Tried this, didn't work
describe('Recovery Email Sent', () => {
it('should resend recovery email', async () => {
const mockMethods = {
resend: jest.fn()
}
const email = 'testemail#test.com'
const wrapper = mount(RecoveryEmailSent, {
localVue,
computed: {
userEmail() {
return email
},
},
methods: mockMethods
})
// await Service.requestPasswordReset()
await wrapper.find('button').trigger('click')
expect(mockMethods.resend).toHaveBeenCalled()
expect(Service.requestPasswordReset).toHaveBeenCalled()
})
})
I figured it out. Apparently, Jest's .toHaveBeenCalled() doesn't return true if the method in question was called with parameters. You MUST use .toHaveBeenCalledWith(). I don't see anything about this caveat in their docs, but it does seem to be the case.
Here is my passing test code
it('should resend email hash', async () => {
const email = 'testemail#test.com'
const wrapper = mount(AccountManagementForgottenPasswordSubmitted, {
localVue,
computed: {
userEmail() {
return email
},
},
})
await wrapper.find('button').trigger('click')
expect(Service.requestPasswordReset).toHaveBeenCalledWith({
email: email
})
})
You can use inject-loader to mock your Service
Basic idea:
const RecoveryEmailSentInjector = require('!!vue-loader?inject!./AccountManagement.RecoveryEmailSent')
import Service from '#/someDir/Service'
const mockedServices = {
'#/someDir/Service': Service
}
describe('Recovery Email Sent', () => {
it('should resend recovery email', async () => {
const RecoveryEmailSentWithMocks = RecoveryEmailSentInjector(mockedServices)
const wrapper = mount(RecoveryEmailSentWithMocks, {
...
})
await wrapper.find('button').trigger('click')
expect(mockMethods.resend).toHaveBeenCalled()
expect(mockedServices.requestPasswordReset).toHaveBeenCalled()
})
})

How to pass variables to an Apollo Mutation for Graphene?

I'm currently trying to switch from an Apollo server in nodejs to a Graphene server, but I'm having an issue while mutating.
Client side handling
const GeneratedForm = Form.create()(IngredientCategoryForm)
const createCategoryMut = gql`
mutation createCategoryMut($name: String) {
createCategory(name:$name) {
category {
name
}
}
}
`
const createCategoryWithApollo = graphql(createCategoryMut)(GeneratedForm)
export { createCategoryWithApollo as CategoryForm }
I'm mutating this way :
handleSubmit = (e) => {
const {
mutate
} = this.props
e.preventDefault()
this.props.form.validateFields((err, values) => {
if (!err) {
mutate({
variables: { name: 'myName' }
})
.then(({ data }) => {
console.log('got data', data);
}).catch((error) => {
console.log('there was an error sending the query', error);
})
}})
}
server side Mutation
class CreateCategory(graphene.Mutation):
class Arguments:
name = graphene.String()
category = graphene.Field(lambda: Category)
def mutate(self, info, name='toto'):
category = Category(name=name)
return CreateCategory(category=category)

vue.js unit test w sinon, how to stub a method?

I would like to stub this method ( this.login(°. ) in my submit method of a component, but I get an error :
✗ calls store action login when the form is submitted
TypeError: Cannot stub non-existent own property login
here is the method :
methods: _.extend({}, mapActions(['login']), {
clearErrorMessage () {
this.hasError = false
},
submit () {
return this.login({user: { email: this.email, password: this.password }})
.then((logged) => {
if (logged) {
this.$router.push('shoppinglists')
} else {
this.hasError = true
}
})
}
}),
I tried the following
sandbox.stub(LoginPage, 'login').withArgs(payload).returns(Promise.resolve(response))
in my spec
describe('LoginPage.vue', () => {
let actions
let getters
let store
var sandbox, payload, response
beforeEach(() => {
sandbox = sinon.sandbox.create()
...
})
afterEach(() => {
sandbox.restore()
})
it('calls store action login when the form is submitted', () => {
payload = {user: {email: 'john.doe#domain.com', password: 'john123'}}
response = true
sandbox.stub(LoginPage, 'login').withArgs(payload).returns(Promise.resolve(response))
const wrapper = mount(LoginPage, { store })
const form = wrapper.find('form')[0]
form.trigger('submit')
expect(actions.login.calledOnce).to.equal(true)
})
just stubbing the action with a Promise resolving the status ( true / false
import LoginPage from '#/pages/LoginPage'
import Vue from 'vue'
import Vuex from 'vuex'
import sinon from 'sinon'
import { mount } from 'avoriaz'
Vue.use(Vuex)
describe('LoginPage.vue', () => {
let actions
let store
let sandbox
beforeEach(() => {
sandbox = sinon.sandbox.create()
})
afterEach(() => {
sandbox.restore()
})
it('calls store action login when the form is submitted, w good credentials', (done) => {
actions = {
login: sandbox.stub().returns(Promise.resolve(true))
}
store = new Vuex.Store({
actions
})
const wrapper = mount(LoginPage, { store })
const form = wrapper.find('form')[0]
form.trigger('submit')
wrapper.vm.$nextTick(() => {
expect(actions.login.calledOnce).to.equal(true)
expect(wrapper.data().hasError).to.equal(false)
done()
})
})
it('calls store action login when the form is submitted, w wrong credentials', (done) => {
actions = {
login: sandbox.stub().returns(Promise.resolve(false))
}
store = new Vuex.Store({
actions
})
const wrapper = mount(LoginPage, { store })
const form = wrapper.find('form')[0]
form.trigger('submit')
wrapper.vm.$nextTick(() => {
expect(actions.login.calledOnce).to.equal(true)
expect(wrapper.data().hasError).to.equal(true)
done()
})
})
})

how to catch the unauthenticated response 401 from django rest api in react native

Here in views.py
class StockList(APIView):
#authentication_classes = (TokenAuthentication,)
permission_classes = [IsAuthenticated]
def get(self,request):
stocks = Stock.objects.all()
serializer = StockSerializer(stocks,many=True)
return Response({'user': serializer.data,'post': serializer.data})
def post(self):
pass
Here if token is not valid then it doesnt send any data..
so in react native I have to patch that error and show their invalid credentials.
So here is my react.js file
componentDidMount() {
return fetch('http://10.42.0.1:8000/stocks/',
{
method: "GET",
headers: {
'Authorization': `JWT ${"eyJhbGciOiIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InNhbSIsInVzZXJfaWQiOjYxLCJlbWFpbCI6IiIsImV4cCI6MTUwNDAxNTg4M30.o_NjgPNsxnLU6erGj5Tonap3FQFxN3Hh_SsU6IvLO9k"}`
}
})
.then((response) => response.json())
.then((responseData) => {
console.log(responseData.detail);
let ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.setState({
isLoading: false,
sa: ds.cloneWithRows(responseData.post),
}, function() {
});
})
.catch((error) => {
console.error(error);
});
}
here if json data is not provided then show some error..
how could I accopmlished that.
thank you.