Failing to make a POST request from ReactJS to Django - django

Hello so I am working on a Django and React project I am fairly new to the domain I can't understand why this is not working, so I would love to make a POST request to my API and save the contents to the database and the after then the function that is currently working to retrieve contents in my DB will do its work to update the website.
So after I made the POST request this is the response I get when I console logged:
Response { type: "cors", url: "http://127.0.0.1:8000/api/upload-lecture/", redirected: false, status: 200, ok: true, statusText: "OK", headers: Headers, body: ReadableStream, bodyUsed: false }
I personally thought after getting a status code of 200 everything is fine but when I go check the database the is nothing new that was added.
I even checked the with Django logs that were coming and this is what I got too:
"POST /api/upload-lecture/ HTTP/1.1" 200 108
So I do not understand why the contents are not in the database.
Code to my Api: Upload method:
#api_view(['POST'])
def videoUpload(request):
serializer = LectureVideosSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
Code to React:This is where I was trying to make the POST request
import React, { useState } from 'react';
const VideoUploadForm = () =>{
const [lecturer, setLecturer] = useState('');
const [module, setModule] = useState('');
const [video, setVideo] = useState();
const [date, setDate] = useState('');
const newVideo = () =>{
const uploadData = new FormData();
uploadData.append('lecturer', lecturer);
uploadData.append('module', module);
uploadData.append('video', video);
uploadData.append('date', date);
fetch('http://127.0.0.1:8000/api/upload-lecture/', {
method:'POST',
body:uploadData
}).then(response => console.log(response)).catch(error => console.log(error))
}
const handleLecturer = (e) =>{
setLecturer({
lecturer: e.target.value
})
}
const handleModule = (e) =>{
setModule({
module: e.target.value
})
}
const handleVideo = (e) =>{
setVideo({
video: e.target.files[0]
})
}
const handleDate = (e) =>{
setDate({
date: e.target.value
})
}
return(
<div className="form__container">
<label>
Lecturer:
<input type="text" onChange={handleLecturer} placeholder="Lecturer uploading"/>
</label>
<label>
Module:
<input type="text" onChange={handleModule} placeholder="Module of Video Uploaded"/>
</label>
<label>
Video:
<input type="file" onChange={handleVideo}/>
</label>
<label>
Date:
<input type="text" onChange={handleDate} placeholder="YY-mm-dd"/>
</label>
<button onClick={() => newVideo()}>Upload Video</button>
</div>
)
}
export default VideoUploadForm;
This is the error that I am getting when I print out the serializers errors
[*] Error:{'video': [ErrorDetail(string='The submitted data was not a file. Check the encoding type on the form.', code='invalid')], 'date': [ErrorDetail(string='Date has wrong format. Use one of these formats instead: YYYY-MM-DD.', code='invalid')]}
How can I resolve this

For the date make sure you have the format mentioned in the error,for file upload,I usually use MultiPartParser,you can set that up using a parser_classes decorator.

Related

How to maintain authentication data

I am doing a Vue 3 practice together with Django Rest Framework, what I am trying to do is a token authentication validation, a user logs in, a token is going to be generated, but I run into a problem and it is that when at moment of logging in is done correctly and I am able to obtain the generated token, the problem is that when reloading the page the token is no longer in the vue application, a possible solution that I decided is to make the token save in local storage, but i think it is not the correct solution.
This is my Login.vue:
<template>
<h2>login</h2>
<form method="POST" #submit.prevent="sendData" autocomplete="off">
<input
type="text"
placeholder="Nombre de Usuario"
v-model.trim="username"
/>
<input
type="password"
placeholder="ContraseƱa de Usuario"
v-model.trim="password"
/>
<button type="submit">enviar</button>
</form>
</template>
<script>
import { ref } from '#vue/reactivity';
import { watchEffect } from '#vue/runtime-core';
export default {
setup() {
const username = ref('');
const password = ref('');
const token = ref('');
const sendData = () => {
fetch(`http://localhost:8000/auth-token/`, {
method: 'POST',
body: JSON.stringify({
username: username.value,
password: password.value,
}),
headers: {
'Content-Type': 'application/json',
},
})
.then((res) => res.json())
.catch((error) => console.error('Error:', error))
.then((response) => {
token.value = response.token;
});
};
watchEffect(() => localStorage.setItem('Token', token.value));
return {
username,
password,
sendData,
};
},
};
</script>
Here is one of my latest answer on the question: https://stackoverflow.com/a/66872372/8816585
TLDR: you only have a few possibilities to persist the data on the frontend but using localStorage/cookies/IndexedDB is totally fine for this purpose.
Also, making a call to the backend at the start of your app is a good idea too, especially if the payload is a bit heavy (you send some JWT, and get a load of personal infos).
Here is a list of packages that may help you persist data on the frontend: https://github.com/vuejs/awesome-vue#persistence

Safari doesn't set cookie on subdomain

I've the following setup:
local domain entries in /etc/hosts:
127.0.0.1 app.spike.local
127.0.0.1 api.spike.local
I've created an express server in TypeScript:
const app = express()
app.use(cookieparser())
app.use(
cors({
origin: 'https://app.spike.local',
credentials: true,
exposedHeaders: ['Set-Cookie'],
allowedHeaders: ['Set-Cookie']
})
)
app.get('/connect/token', (req, res) => {
const jwt = JWT.sign({ sub: 'user' }, secret)
return res
.status(200)
.cookie('auth', jwt, {
domain: '.spike.local',
maxAge: 20 * 1000,
httpOnly: true,
sameSite: 'none',
secure: true
})
.send()
})
type JWTToken = { sub: string }
app.get('/userinfo', (req, res) => {
const auth = req.cookies.auth
try {
const token = JWT.verify(auth, secret) as JWTToken
console.log(req.cookies.auth)
return res.status(200).send(token.sub)
} catch (err) {
return res.status(401).json(err)
}
})
export { app }
I've created a simple frontend:
<button
id="gettoken"
class="m-2 p-1 rounded-sm bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-600 focus:ring-opacity-50 text-white"
>
Get Token
</button>
<button
id="callapi"
class="m-2 p-1 rounded-sm bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-600 focus:ring-opacity-50 text-white"
>
Call API
</button>
<div class="m-2">
Token Response Status Code:
<span id="tokenresponse" class="bg-green-100"></span>
</div>
<div class="m-2">
API Response: <span id="apifailure" class="bg-red-100"></span
><span id="apiresponse" class="bg-green-100"></span>
</div>
<script type="text/javascript">
const tokenresponse = document.getElementById('tokenresponse')
const apiresponse = document.getElementById('apiresponse')
const apifailure = document.getElementById('apifailure')
document.getElementById('gettoken').addEventListener('click', async () => {
const response = await fetch('https://api.spike.local/connect/token', {
credentials: 'include',
cache: 'no-store'
})
tokenresponse.innerHTML = response.status
})
document.getElementById('callapi').addEventListener('click', async () => {
const userInfoResponse = await fetch('https://api.spike.local/userinfo', {
credentials: 'include',
cache: 'no-store'
})
if (userInfoResponse.status === 200) {
const userInfo = await userInfoResponse.text()
apifailure.innerHTML = ''
apiresponse.innerHTML = userInfo + ' #' + new Date().toISOString()
} else {
const failure = (await userInfoResponse.json()).message
console.log(failure)
apiresponse.innerHTML = ''
apifailure.innerHTML = failure
}
})
</script>
When running the UI on https://app.spike.local and the API on https://api.spike.local both using self certificates and browsing the UI, I can successfully request a token in a cookie and subsequently use this token via cookie being sent automatically for the API call in Chrome and Firefox.
However, on Safari on macOS (and iOS) the Cookie isn't being sent in the subsequent API call.
As can be seen,
Cookie settings are SameSite=None, HttpOnly, Secure, Domain=.spike.local.
CORS has no wildcards for headers and origins and exposes and allows the Set-Cookie header as well as Access-Control-Allow-Credentials.
on client side, fetch options include credentials: 'include'
As said, both API and UI are served over SSL with valid self signed certificates.
When disabling Preferences/Privacy/Prevent cross-site tracking in Safari, everything works fine. But this not an option for this scenario in production.
What am I doing wrong here?
Solved it by changing the TLD to .com instead of .local.
The hint has been in this comment.

how to set cookies during vuejs post

I am trying to send post data to a django Restful API using vuejs. here is the code I have so far:
<script>
import axios from 'axios'
import VueCookies from 'vue-cookies'
//3RD ATTEMPT
VueCookies.set("csrftoken","00000000000000000000000000000000");
// # is an alias to /src
export default {
name: "Signup",
components: {},
data: () => {
},
methods: {
sendData(){
// 2ND ATTEMPT
// $cookies.set("csrftoken", "00000000000000000000000000000000");
axios({
method: 'post', //you can set what request you want to be
url: 'https://localhost:8000/indy/signup/',
data: {
csrfmiddlewaretoken: "00000000000000000000000000000000",
first_name: "wade",
last_name: "king",
email: "wade%40mail.com",
password1: "05470a5bfe",
password2: "05470a5bfe"
},
// 1ST ATTEMPT
// headers: {
// Cookie: "csrftoken= 00000000000000000000000000000000"
// },
withCredentials: true
})
}
}
</script>
I have a button which executes the sendData() method on a click. The code uses the axios library to send a post request to the django API running on http://localhost:800/indy/signup/
The problem with just sending a post request to the API is that it will get blocked in order to prevent Cross Site Response Forgery (CSRF), I dont quite understand CSRF but I know if the csrftoken is set as a cookie and has the same value as the csrfmiddlewaretoken then the post should go through to the API.
You can see my attempts to set the cookie in the code I provided
1ST ATTEMPT)
headers: {
Cookie: "csrftoken= 00000000000000000000000000000000"
},
Here I'm trying to set the cookie directly in the header. When I click send I get an error in my browser console saying refused to set unsafe header "Cookie"
2ND ATTEMPT)
$cookies.set("csrftoken", "00000000000000000000000000000000");
Here I'm trying to set the cookie using the vue-cookies module. When i click send I get the following error, net::ERR_SSL_PROTOCOL_ERROR
3RD ATTEMPT)
VueCookies.set("csrftoken","00000000000000000000000000000000");
Here I'm trying to set a global cookie using the vue-cookies module. When I click send I get the same error as attempt 2
IMPORTANT:
However when I send post data to the API from my terminal using the following curl command, it works perfectly
curl -s -D - -o /dev/null \
-H 'Cookie: csrftoken= 00000000000000000000000000000000' \
--data 'csrfmiddlewaretoken=00000000000000000000000000000000&first_name=wade&last_name=king&email=wade%40mail.com&password1=05470a5bfe&password2=05470a5bfe' \
http://localhost:8000/indy/signup/
my main question is How can I replicate this curl request using vuejs? I've looked all over on line and none of the tutorials deal with setting cookies.
I posted this question some time ago, I have managed to work around it by running the vue frontend on the same network as the django backend. Follow this tutorial for instructions: integrating vuejs and django
Once I had the application set up I was able to set the cookies much more cleanly using :
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
Here is my login page for example
<template>
<div class = "container">
<h2>Sign In</h2>
<b-form v-on:submit.prevent="submit()">
<b-form-group id="signin" label="">
<!-- dynamic error message -->
<p class="loginErr" v-if="logErr">Incorrect Username or Password</p>
<b-form-input
id="signin-email"
v-model="username"
placeholder="Email"
required
></b-form-input>
<b-form-input
id="signin-password"
v-model="password"
placeholder="Password"
required
type="password"
></b-form-input>
</b-form-group>
<b-button v-if="!loading" type="submit" variant="primary">Submit</b-button>
<b-spinner v-if="loading"></b-spinner>
</b-form>
</div>
</template>
<script>
import axios from 'axios'
import Vue from 'vue'
export default {
data: ()=>{
return{
loading: false,
logErr: false,
username:'',
password:'',
next: '%2Findy%2Fprofile%2F'
}
},
created: function(){
},
methods: {
submit(){
var vm = this;
vm.loading = true;
var dataStr = 'username='+vm.username+'&password='+vm.password
//set the csrf tokens so django doesn't get fussy when we post
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
axios.post('http://localhost:8000/api/signin/', dataStr)
.then(function (response) {
vm.loading = false;
//determine if indy accepts the login request
var res = response.data
console.log(response.data)
if(!res.login){
vm.logErr = true;
}else{
vm.redirect('home');
}
})
.catch(function (error) {
//currentObj.output = error;
});
},
redirect(path) {
this.$router.push('/' + path);
}
}
}
</script>
<style>
.loginErr{
color: orange;
}
</style>

Axios unable to get JSON from Django view

I want to implement a front-end and back-end data interaction with axios and django view. Now I have succeed in posting data to django view with code below.
axios.post("{% url 'main:getCommodityInfo'%}",
param,
{headers:{'X-CSRFToken': this.getCookie('csrftoken')}})
.then(response=>{
console.log(response);
alert("response has been caught");
})
.catch(error=>{
console.log(error);
alert("connection has error")
})
But when I want to return json from view to axios:
def getCommodityInfo(request):
if request.method=="POST":
# get POST parameters
searchKey = request.POST.get('searchKey')
category = request.POST.get('category')
print("Enter the POST view! ", searchKey, category)
# unique ID for each record for DB
uniqueId = str(uuid4())
# spider setting
settings = {
'unique_id': uniqueId,
'USER_AGENT': 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'
}
# taskId to indentify each task
task = scrapyd.schedule('JDSpider', 'getCommodityInfo',
settings=settings, searchKey=searchKey, category=category)
print("It seems everything is running well? ")
return JsonResponse({'taskId': task, 'uniqueId': uniqueId, 'status': 'started'},safe=False)
the browser has no change! Firstly, I tried to figure out why it occurred independently. The potential clue maybe lie in urls.py.
urlpatterns = [
# eg:127.0.0.1:8000/main/
path('', views.index, name = 'index'),
path('getCommodityInfo/',views.getCommodityInfo, name = 'getCommodityInfo'),
path('getCommodityCommentDetail/',views.getCommodityCommentDetail, name="getCommodityCommentDetail"),
path('commodityInfo/<str:category>/<str:searchKey>/',views.commodityInfoPage, name = 'commodityInfoPage'),
path('commentsInfo/<str:commodityId>/',views.commodityCommentPage,name = 'commodityCommentPage'),
# path('?searchkey=<str:searchKey>&categroy=<str:category>/',views.getCommodityInfo, name = 'getCommodityInfo'),
]
Because I found that the initial url http://127.0.0.1:8000/main/ in my browser after clicking on the button to post data to getCommodityInfo view became http://127.0.0.1:8000/main/?searchKey=switch&category=Electronics . This url seems not to match any url patterns in urls.py. So I tried to append an additional urlpattern path('?searchkey=<str:searchKey>&categroy=<str:category>/',views.getCommodityInfo, name = 'getCommodityInfo'). Unfortunately, it doesn't work.
After that, I am searching for a long time on net. But no use. Please tell me whether my idea to solve right. Or try to give some ideas how to achieve this.Thanks in advance.
Edit 1 My console logs were asked.
This is my console log after click the button to post data.
And when I click on the alert, the browser go to http://127.0.0.1:8000/main/?searchKey=switch&category=Electronics and the chrome network loading shows like:
And there are no log output in console.
Edit 2 There are some doubts on whether axios send the request by POST or GET, and I try to indentify it in my django view
My python console output this, meaning getCommodityInfo did indentify the request as POST(You could review my code)
Edit 3 #dirkgroten pointed out likely I have sended both POST and GET. So I give the whole code related in my template
And here is my form. And whole js related.
<form action="" id="searchForm">
<label for="searchKey">KeyWords</label>
<input v-model="searchKey" palceholder="Input Search Key" type="string" class="form-control" id="searchKey" name="searchKey">
<label for="category">Commodity Category</label>
<select v-model="selected" id="category" name="category">
<option v-for="option in options" v-bind:value="option.value">
${option.text}
</option>
</select>
<button v-on:click="startSpider" class="btn btn-default" >Submit</button>
<p>KeyWords : ${ searchKey }</p>
<p>Category : ${ selected }</p>
</form>
<script type="text/javascript">
var searchApp = new Vue({
delimiters:['${','}'],
el: "#searchForm",
data:{
searchKey:'',
selected:'',
options: [
{text: 'Baby', value:'Baby'},
{text: 'Book', value:'Book'},
{text: 'Electronics', value:'Electronics'},
{text: 'Fashion', value:'Fashion'},
{text: 'Food', value:'Food'},
{text: 'Health&Beauty', value:'Health&Beauty'},
{text: 'Home', value:'Home'},
{text: 'Industrial&Scientific', value:'Industrial&Scientific'},
{text: 'Motor', value:'Motor'},
{text: 'Pet', value:'Pet'},
{text: 'Sports', value:'Sports'},
{text: 'Other', value:'Other'},
]
},
created:function(){
this.selected = "";
},
methods:{
startSpider:function(event){
console.log(this.searchKey);
console.log(this.selected);
alert("spider is ready to run!");
var param = new URLSearchParams();
param.append('searchKey',this.searchKey);
param.append('category',this.selected);
axios.post("{% url 'main:getCommodityInfo'%}",
param,
{headers:{'X-CSRFToken': this.getCookie('csrftoken')}})
.then(response=>{
this.searchKey = "!!!";
this.category = "Baby";
console.log(response.data)
alert("response has been caught");
console.log(response.data)
})
.catch(error=>{
console.log(error);
alert("connection has error")
})
},
getCookie:function(name) {
var value = '; ' + document.cookie
var parts = value.split('; ' + name + '=')
if (parts.length === 2) return parts.pop().split(';').shift()
},
}
});
</script>
Acutally, I have found the mistake. It's all about the <form>... The solution is here

Angular2 How to POST data using a service class

I have a simple application form which I am trying to post to the server. I am fairly new to Angular2
How can I pass the data from the component to the service and onto the server for a POST request.
The POST is working fine when I try it directly from FireFox plugin 'httpRequester'
This is the TaskComponent.ts
#Component({
selector: 'tasks',
template: `<div mdl class="mdl-grid demo-content">
<div class="demo-graphs mdl-shadow--2dp mdl-color--white mdl-cell mdl-cell--8-col">
<h3>Create Task Page</h3>
<form action="#" (ngSubmit)="onSubmit()">
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input class="mdl-textfield__input" type="text" pattern="[A-Z,a-z]*" id="taskname" [(ngModel)]="data.taskname"/>
<label class="mdl-textfield__label" for="taskname">Task Name</label>
<span class="mdl-textfield__error">Only alphabet and no spaces, please!</span>
</div>
<button class="mdl-button mdl-js-button mdl-button--raised mdl-button--colored" type="submit">Create Task</button>
</form>
`,
directives: [ROUTER_DIRECTIVES, MDL]
})
export class CreateTaskComponent {
data: any
constructor() {
this.data = {
//taskname: 'Example Task'
};
}
onSubmit(form) {
console.log(this.data.taskname); <--Data is passed upon submit onto the console. Works fine.
//Need to call the postApartment method of ApartmentService
}
}
ApartmentService.ts
import {Http, Response} from 'angular2/http'
import {Injectable} from 'angular2/core'
import 'rxjs/add/operator/map';
#Injectable()
export class ApartmentService {
http: Http;
constructor(http: Http) {
this.http = http;
}
getEntries() {
return this.http.get('./api/apartments').map((res: Response) => res.json());
}
getProfile(userEmail :string){
return this.http.get(`./api/apartments/getprofile/${userEmail}`).map((res: Response) => res.json());
}
postApartment(){
// Not familiar with the syntax here
}
}
Server.ts
router.route('/api/apartments')
.post(function(req, res) {
var apartment = new Apartment();
apartment.name = req.body.name;
apartment.save(function(err) {
if (err)
res.send(err);
res.json({ message: 'Apartment created!' });
});
})
You can inject service via dependency injection and use it in the component
export class CreateTaskComponent {
constructor(){private _apartmentService: ApartmentService}{}
}
And you can access this in any of the component function via
onSubmit(form) {
console.log(this.data.taskname); <--Data is passed upon submit onto the console. Works fine.
//Need to call the postApartment method of ApartmentService
this._apartmentService.postApartment()
}
And when bootstraping the component you have to add it as dependency via
bootstrap(AppComponent, [ApartmentService]);
Another option for doing the last step is by added providers in the Component decorator like
#Component{
providers: [ApartmentService]
}
Inject the apartmentService in the component, No need of providers as I have bootstrapped it. (If you bootstartp the service, Do not include it in providers. It breaks the system)
export class CreateTaskComponent {
data: any
constructor(private apartmentService: ApartmentService) {
this.data = {};
}
onSubmit(form) {
this.apartmentService.postApartment(this.data);
}
}
The critical piece is the postApartment() method in the service
postApartment(data :any){
return this.http.post('/api/apartments',
JSON.stringify(data),{headers : new Headers({'Content-Type':'application/json'})
})
.map((res: Response) => res.json()).subscribe();
}
Also make sure on the server.js code, the mongoose fields match the http body parameters being passed.
I had to fix it to make it work.