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')
Related
I am trying to use Nuxt 3 as a server to pass through API requests. I would like to make FormData requests to a Django DRF API through my Nuxt server to POST data and images.
basically, the usecase is filling a simple form with data and images and send the formdata to a django backend. Here are more details.
In pages.vue:
const taskData = reactive({
title: '',
city: '',
description: '',
category: route.params.cat_slug,
subCat: '',
image_1: [],
image_2: [],
image_3: []
})
const submitTaskForm = async () => {
let formData = new FormData()
formData.append('title', taskData.title)
formData.append('city', taskData.city)
formData.append('description', taskData.description)
formData.append('category', taskData.category)
formData.append('subCat', taskData.subCat)
taskData.image_1.forEach((fileItem) => {
formData.append('image_1', fileItem.file)
})
taskData.image_2.forEach((fileItem) => {
formData.append('image_2', fileItem.file)
})
taskData.image_3.forEach((fileItem) => {
formData.append('image_3', fileItem.file)
})
const data = await useFetch('/api/tasks/add/', {
method: 'POST',
body: formData
})
return data
}
in /server/api/add.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event)
const access = getCookie(event, 'access') || null
try {
const data = await $fetch(process.env.API_BASE_URL + "/api/tasks/add/", {
method: "POST",
body: body,
headers: {
Authorization: "Bearer " + access,
},
})
return data
} catch (error) {
console.log(error);
return error
}
})
now the backend part with the view handling the form:
class TasksCreate(APIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = TasksSerializer
parser_classes = [MultiPartParser, FormParser]
def post(self, request):
data = self.request.data
title = data['title']
city = data['city']
description = data['description']
category_slug = data['category']
subCat = data['subCat']
image_1 = request.FILES.get('image_1', "")
image_2 = request.FILES.get('image_2', "")
image_3 = request.FILES.get('image_3', "")
try:
if not Subcategories.objects.filter(slug=subCat, category__slug=category_slug).exists():
return Response(
{'error': 'The subcategory does not correspond to the category.', },
status=status.HTTP_400_BAD_REQUEST
)
task = Task.objects.create(
title=title,
city=city,
description=description,
slug='%s-%s' % (slugify(title), randint(1000, 10000)),
category=get_object_or_404(Category, slug=category_slug),
sub_category = get_object_or_404(Subcategories, slug=subCat),
created_by=request.user,
image_1=image_1,
image_2=image_2,
image_3=image_3,
)
task.save()
return Response(
{'task': 'Task created successfully', },
status=status.HTTP_201_CREATED
)
except Exception as e:
print(e)
return Response(
{'error': 'Something went wrong when creating the task'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
the error: Unsupported Media Type: /api/tasks/add/
I have tried with readBody readRawBody readMultipartFormData and also adding "Content-Type": "multipart/form-data" to headers.
Also, hitting directly the view with Postman work perfectly so my guess it's nuxt 3 not sending correctly the form.
DISCLAIMER: This is gonna be a pretty long description of a problem, to which I can't seem to find sources for that could help me. So any piece of information is appreciated!
I might add that I am fairly new to Angular and text script.
I have set up my backend with Django and set up the Rest Framework. These are my models:
models.py
class Person(models.Model):
name = models.CharField(max_length = 250, blank = True)
job = models.ForeignKey('Job', on_delete = models.CASCADE, null = True)
def __str__(self):
return self.name
class Job(models.Model):
job_name = models.CharField(max_length = 200, blank = False)
rank = models.PositiveIntegerField(default = 0, unique = True)
def __str__(self):
return self.job_name
In the backend I have set up different job names (Doctor, Teacher, etc...). So when I create a new Person, I get to choose from one of the different jobs, which have been created prior to this. (This is exactly how I woukd want my frontend to work)
Here are my serializer.py, views.py and urls.py files:
serializer.py
class PersonSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Person
fields = ('id', 'name', 'job')
class JobSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Job
fields = ('id', 'job_name')
views.py
class PersonViewSet(viewsets.ModelViewSet):
queryset = Person.objects.all()
serializer_class = PersonSerializer
def list(self, request, *args, **kwargs):
person = Person.objects.all()
serializer = PersonSerializer(person, many = True, context = {'request': request})
return Response(serializer.data)
class JobViewSet(viewsets.ModelViewSet):
queryset = Job.objects.all()
serializer_class = JobSerializer
def list(self, request, *args, **kwargs):
job = Job.objects.all()
serializer = JobSerializer(job, many = True, context = {'request': request})
return Response(serializer.data)
urls.py
router = routers.DefaultRouter()
router.register('person', views.PersonViewSet)
router.register('job', views.JobViewSet)
urlpatterns = [
path('', include(router.urls)),
]
The Rest Framework has successfully been integrated and I can now create, read, update and delete (CRUD) my models.
In Angular I have created a person component with a service, which contains very simple functions, to use CRUD on my frontend.
person.service.ts
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class PersonService {
backendURL = 'http://127.0.0.1:8000'
httpHeaders = new HttpHeaders({'Content-Type': 'application/json'})
constructor(private http:HttpClient) { }
getPerson():Observable<any> {
return this.http.get(this.backendURL + '/person/',
{headers: this.httpHeaders});
}
getOnePerson(id):Observable<any> {
return this.http.get(this.backendURL + '/person/' + id + '/',
{headers: this.httpHeaders});
}
updatePerson(person): Observable<any> {
const body = { name: person.name, job: person.job, };
return this.http.put(this.backendURL + '/person/' + person.id + '/', body,
{headers: this.httpHeaders});
}
createPerson(person): Observable<any> {
const body = { name: person.name, job: person.job, };
return this.http.post(this.backendURL + '/person/', body,
{headers: this.httpHeaders});
}
deletePerson(id): Observable<any> {
return this.http.delete(this.backendURL + '/person/' + id + '/',
{headers: this.httpHeaders});
}
}
And my component ts file:
person.component.ts
import { Component, OnInit } from '#angular/core';
import { PersonService } from '../../services/person.service';
#Component({
selector: 'app-person',
templateUrl: './person.component.html',
styleUrls: ['./person.component.css'],
providers: [PersonService]
})
export class PersonComponent implements OnInit {
person_all = [{ name: ''}, ];
selectedPerson;
constructor(private api: PersonService) {
this.getPerson();
this.selectedPerson = { id:-1, name: '', job: '', }
}
getPerson = () => {
this.api.getPerson().subscribe(
data => {
this.person_all = data;
},
error => {
console.log(error);
}
)
}
personClicked = (person) => {
this.api.getOnePerson(person.id).subscribe(
data => {
this.selectedPerson = data;
},
error => {
console.log(error);
}
)
}
updatePerson = () => {
this.api.updatePerson(this.selectedPerson).subscribe(
data => {
this.getPerson();
},
error => {
console.log(error);
}
);
}
createPerson = () => {
this.api.createPerson(this.selectedPerson).subscribe(
data => {
this.person_all.push(data);
},
error => {
console.log(error);
}
);
}
deletePerson = () => {
this.api.deletePerson(this.selectedPerson.id).subscribe(
data => {
this.getPerson();
},
error => {
console.log(error);
}
);
}
ngOnInit(): void {
}
}
In HTML I created a very simple view, in where I can see every created Person and some buttons to create, update or delete some.
person.component.html
<h2>Person List</h2>
<ul>
<li *ngFor="let person of person_all">
<h2 (click)="personClicked(person)">{{person.nachname}}</h2>
</li>
</ul>
Name <input type="text" [(ngModel)]="selectedPerson.name"/><br>
Job <input type="text" [(ngModel)]="selectedPerson.job"/><br>
<button *ngIf="selectedPerson.id != -1" (click)="updatePerson()"> PUT </button>
<button *ngIf="selectedPerson.id == -1" (click)="createPerson()"> CREATE </button>
<button *ngIf="selectedPerson.id != -1" (click)="deletePerson()"> DELETE </button>
This is my problem: When I try to create a new Person, I don't see the different job names, which I have set up in the backend. I want to know, how do I get a "list" of jobs to select from, similar to how it works in Django with a Foreign Key?
Thanks for the read and thanks in advance!
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));
});
};
};
I'm using Angular 6 and Django REST Framework
The view of DRF is
class AmountGivenViewSet(viewsets.ModelViewSet):
serializer_class = AmountGivenSerializer
permission_classes = (IsAuthenticated, AdminAuthenticationPermission,)
def get_queryset(self):
return AmountGiven.objects.filter(
contact__user=self.request.user
)
def perform_create(self, serializer):
save_data = {}
print(self.request.POST)
# validate user and to save_data dictionary
contact_pk = self.request.POST.get('contact', None)
print(contact_pk)
if not contact_pk:
raise ValidationError({'contact': ['Contact is required']})
contact = Contact.objects.filter(
user=self.request.user,
pk=contact_pk
).first()
if not contact:
raise ValidationError({'contact': ['Contact does not exists']})
# add contact to save_data dictionary
save_data['contact'] = contact
# process mode_of_payment is in request
mode_of_payment_pk = self.request.POST.get('mode_of_payment', None)
if mode_of_payment_pk:
mode_of_payment = ModeOfPayment.objects.get(pk=mode_of_payment_pk)
if not mode_of_payment:
raise ValidationError({'mode_of_payment': ['Not a valid mode of payment']})
# add mode_of_payment to save_data dictionary
save_data['mode_of_payment'] = mode_of_payment
# pass save_data dictionary to save()
serializer.save(**save_data)
AmountGivenSerializer in serializers.py
class AmountGivenSerializer(serializers.ModelSerializer):
class Meta:
model = AmountGiven
depth = 1
fields = (
'id', 'contact', 'amount', 'interest_rate', 'duration', 'given_date', 'promised_return_date',
'mode_of_payment', 'transaction_number', 'interest_to_pay', 'total_payable', 'amount_due',
'total_returned', 'comment', 'modified', 'created'
)
and in Angular component
import { Component, OnInit } from '#angular/core';
import {FormBuilder, FormControl, FormGroup, Validators} from '#angular/forms';
import {AmountGiven} from '../amount-given.model';
import {AmountGivenService} from '../amount-given.service';
import {ActivatedRoute} from '#angular/router';
#Component({
selector: 'app-amount-given-add',
templateUrl: './amount-given-add.component.html',
styleUrls: ['./amount-given-add.component.css']
})
export class AmountGivenAddComponent implements OnInit {
addMoneyForm: FormGroup;
submitted = false;
contact_id: string;
amountGiven: AmountGiven;
constructor(
private formBuilder: FormBuilder,
private amountGivenService: AmountGivenService,
private route: ActivatedRoute
) { }
ngOnInit(): void {
this.route.params.subscribe(
param => {
this.contact_id = param['contact_id'];
}
);
this.addMoneyForm = this.formBuilder.group({
amount: new FormControl('', [
Validators.required
]),
interest_rate: new FormControl(),
duration: new FormControl(),
given_date: new FormControl(),
promised_return_date: new FormControl(),
transaction_number: new FormControl(),
mode_of_payment: new FormControl(),
comment: new FormControl()
});
}
get f() {
return this.addMoneyForm.controls;
}
onSubmit() {
this.submitted = true;
// stop here if form is invalid
if (this.addMoneyForm.invalid) {
console.log('invalid');
return;
}
const data = this.addMoneyForm.value;
data.contact = this.contact_id;
this.amountGivenService.add(data).subscribe(res => {
console.log('req completed', res);
});
}
}
But when form is submited it returns validation error from server for contact field.
{"contact":["Contact is required"]}
The request header has the contact parameter
sending the same request from Postman is working fine. The Javascript XHR code of Postman request is
var data = new FormData();
data.append("amount", "5000");
data.append("contact", "65827a1f-003e-4bb3-8a90-6c4321c533e6");
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
console.log(this.responseText);
}
});
xhr.open("POST", "https://koober-production.herokuapp.com/api/amount-given/");
xhr.setRequestHeader("Authorization", "Bearer ATjIuQ6hLzc55wHaXIzHmcKafEzk1B");
xhr.setRequestHeader("Cache-Control", "no-cache");
xhr.setRequestHeader("Postman-Token", "28d9d33a-f0a6-431c-8936-da4f6565ece4");
xhr.send(data);
could not understand this issue is related to Angular or Django because Postman request is working fine with Djano and Angular is sending the request in the parameter.
Edit 2: AmountGivenService
import { Injectable } from '#angular/core';
import {ResourceProviderService} from '../../resource-provider.service';
import {AuthService} from '../../auth/auth.service';
import {Observable, of} from 'rxjs';
import {AmountGiven} from './amount-given.model';
import {AppHttpClient} from '../../app-http-client';
#Injectable({
providedIn: 'root'
})
export class AmountGivenService {
private url = 'amount-given/';
constructor(
private http: AppHttpClient
) { }
add(data): Observable<AmountGiven> {
return this.http.Post<AmountGiven>(this.url, data);
}
}
which is furhter using AppHttpClient
import { Injectable } from '#angular/core';
import {HttpClient, HttpHeaders, HttpParams} from '#angular/common/http';
import {Observable, of} from 'rxjs';
import {catchError} from 'rxjs/operators';
import {ResourceProviderService} from './resource-provider.service';
export interface IRequestOptions {
headers?: HttpHeaders;
observe?: 'body';
params?: HttpParams;
reportProgress?: boolean;
responseType?: 'json';
withCredentials?: boolean;
body?: any;
}
export function appHttpClientCreator(http: HttpClient, resource: ResourceProviderService) {
return new AppHttpClient(http, resource);
}
#Injectable({
providedIn: 'root'
})
export class AppHttpClient {
private api_url = this.resource.url + '/api/';
constructor(
public http: HttpClient,
private resource: ResourceProviderService
) {
}
public Post<T>(endPoint: string, params: Object, options?: IRequestOptions): Observable<T> {
return this.http.post<T>(this.api_url + endPoint, params, options)
.pipe(
catchError(this.handleError('post', endPoint, null))
);
}
private handleError<T> (operation = 'operation', endpoint = '', result?: T) {
return (error: any): Observable<T> => {
console.error(error);
console.log(error.message);
console.log(operation);
console.log(endpoint);
return of(result as T);
};
}
}
The problem is that you're trying to access:
self.request.POST.get('contact', None)
in DRF you need to do:
self.request.data.get('contact', None)
It works from postman because you're constructing a FormData object which is fundamentally different from the JSON body angular will send and that is expected by a RESTful API like DRF.
from a more general standpoint, in DRF, serializers should perform the request validation using the built in validators, rather than manually validating in the view.
I'm hesitant to add this as the answer just yet, but it feels like you need to add something along the lines of:
<form method="POST" action="url/to/your-DRF-endpoint" enctype="application/x-www-form-urlencoded">
....
</form>
Either within your template or within your Angular POST request code you need to make sure the data is posted as application/x-www-form-urlencoded.
Essentially, Django expects application/x-www-form-urlencoded, and without it your self.request.POST object will be empty and hence self.request.POST.get('contact', None) will be empty and default to None.
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.