I am currently setting up a file upload with React for the frontend and Django for the backend. More specifically, I want to pass a file + some data to my database via an API.
But while uploading the file I get the error: "The submitted data was not a file. Check the encoding type on the form."
models.py (Django)
class Story (models.Model):
title = models.CharField(max_length=100,blank=False)
content = models.TextField(blank=False)
author = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
audio = models.FileField(default='SOME STRING', upload_to='audio_stories/',null=True, validators=[validate_file_extension_audio])
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('story-detail', kwargs={'pk': self.pk})
serializers.py (Django)
class StorySerializer(serializers.ModelSerializer):
class Meta:
model = Story
fields = '__all__'
A MultiPartParser is used to pass file + data.
api.py (Django)
class StoryViewSet(viewsets.ModelViewSet) (Django):
serializer_class = StorySerializer
parser_classes = (MultipartJsonParser, parsers.JSONParser)
queryset = Story.objects.all()
permission_classes = [
permissions.IsAuthenticated
]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
utils.py (Django)
class MultipartJsonParser(parsers.MultiPartParser):
def parse(self, stream, media_type=None, parser_context=None):
result = super().parse(
stream,
media_type=media_type,
parser_context=parser_context
)
data = {}
# find the data field and parse it
data = json.loads(result.data["data"])
qdict = QueryDict('', mutable=True)
qdict.update(data)
return parsers.DataAndFiles(qdict, result.files)
In actions story.js (React)
import axios from "axios";
import { tokenConfig } from './auth';
import { createMessage, returnErrors } from "./messages";
import {ADD_STORY} from "./types";
const apiBase = "http://localhost:8000/api";
export const addStory = story => (dispatch, getState) => {
axios
.post(apiBase +"/story/", story, tokenConfig(getState))
.then(res => {
dispatch(createMessage({ addStory: "Story Added" }));
dispatch({
type: ADD_STORY,
payload: res.data
});
})
.catch(err =>
dispatch(returnErrors(err.response.data, err.response.status))
);
};
In components create_story.js (React)
import React, { Component } from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { addStory } from "../../actions/story";
export class CreateStory extends Component {
state = {
title: "",
content:""
};
static propTypes = {
addStory: PropTypes.func.isRequired
};
onChange = e => this.setState({ [e.target.name]: e.target.value });
onSubmit = e => {
e.preventDefault();
const { title,content, audio} = this.state;
const story = { title, content, audio};
this.props.addStory(story);
this.setState({
title: "",
content:"",
audio:""
});
};
render() {
const {title, content, audio} = this.state;
return (
<div className="card card-body mt-4 mb-4">
<h2>Add Story</h2>
<form onSubmit={this.onSubmit}>
<div className="form-group">
<label>Title</label>
<input
className="form-control"
type="text"
name="title"
onChange={this.onChange}
value={title}
/>
</div>
<div className="form-group">
<label>Content</label>
<input
className="form-control"
type="text"
name="content"
onChange={this.onChange}
value={content}
/>
</div>
<div className="form-group">
<label>Audio</label>
<input
className="form-control"
type="file"
name="audio"
onChange={this.onChange}
value={audio}
/>
</div>
<div className="form-group">
<button type="submit" className="btn btn-primary">
Submit
</button>
</div>
</form>
</div>
);
}
}
export default connect(
null,
{ addStory }
)(CreateStory);
I'm having trouble tracing the error in my code. I assume that the react form does not provide the correct data format, i.e. formdata. But i dont know how and wehre to change this. Without the file upload I was able to successfully pass data to my database.
Many thanks.
Your onChange function in create_story.js is not gathering the file. You may need to reformat that function to something like:
onChange = (e) => {
if(e.target.name === 'audio') {
this.setState({
[e.target.name]: e.target.files[0]
}, () => console.log(this.state.audio))
} else {
this.setState({
[e.target.name]: e.target.value
}, () => console.log(this.state))
}
}
Related
I am using a modelForm to create post objects via ajax. The images field are part of the form but not passed to the fields of the Meta class because that will allow to save the post first and add the images uploaded after that. My issue is if I do use a regular view(without ajax) the request.FILES are being submitted correctly but when I use via ajax those files are not part of the request.files which renders an empty <MultiValueDict: {}> I don't really know why.
Here is my code.
def post(self, request, *args, **kwargs):
form = PostForm(request.POST or None, request.FILES or None)
result = {}
files = request.FILES
print(files)
if is_ajax(request=request) and form.is_valid():
print("the request is ajax and the form is valid")
title = form.cleaned_data.get("content", "")
print("Title ", title)
post_instance = form.save(commit=False)
post_instance.author = request.user
result['success'] = True
return JsonResponse(result)
$.ajax({
url: $("#CreatePostModal").attr("data-url"),
data:$("#CreatePostModal #createPostForm").serialize(),
method: "post",
dataType: "json",
success: (data) => {
if (data.success) {
setTimeout(() => {
$(e.target).next().fadeOut();
ResetForm('createPostForm', 'PreviewImagesContainer')
$("#CreatePostModal").modal('hide')
$(e.target.nextElementSibling).fadeOut()
alertUser("Post", "has been created successfully!")// alerting the user
}, 1000)
console.log(data.title)
} else {
$("#createPostForm").replaceWith(data.formErrors);
$("#PreviewImagesContainer").html("");
$("#CreatePostModal").find("form").attr("id", "createPostForm");
$(e.target.nextElementSibling).fadeOut()
};
$(e.target).prop("disabled", false);
},
error: (error) => {
console.log(error)
}
})
});
Here is the form file
class PostForm(forms.ModelForm):
images = forms.ImageField(
required=False,
widget=forms.ClearableFileInput(attrs={
'multiple': True,
})
)
class Meta:
model = Post
fields = ("content",)
widgets = {
"content": forms.Textarea(attrs={"placeholder": "Tell us something today....", "rows": 5, "label": ""})
}
again the imagefield has a manytomany relationship with the post model.
What I am doing wrong?
Here is the modal where I am rendering the form itself with crispy form
<!-- create post modal -->
<div class="modal fade" id="CreatePostModal" data-url="{% url 'post-list-view' %}" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Creating Post</h5>
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
×
</button>
</div>
<div class="modal-body">
<div class="container-fluid">
{% crispy form %}
<div id="PreviewImagesContainer">
</div>
</div>
</div>
<div class="modal-footer float-right">
<button type="button" class="btn btn-sm btn-dark" data-bs-dismiss="modal">Cancel</button>
<button type="submit" form="createPostForm" id="createPostBtn" class="btn btn-sm btn-primary">Post</button>
<span class="loading-icon"><i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</div>
</div>
</div>
</div>
<!-- end of create post modal -->
I finally solved it using the FormData of javascript by looping through of the files that I am getting from the input then append it to the data of the FormData.
let imageFiles = []
$("#CreatePostModal").on('change', (e) => {
$(postImagesPreviewContainer).html("")
if ($(e.target).attr("id") !== "id_images") return;
var filenames = "";
for (let i = 0; i < e.target.files.length; i++) {
filenames += (i > 0 ? ", " : "") + e.target.files[i].name;
}
e.target.parentNode.querySelector('.custom-file-label').textContent = filenames;
//why is the this element returning the document and not the target itself
// check the length of the files to know what template to make
const files = e.target.files
const numberOfImages = files.length
let gridColumnSize;
if (numberOfImages > 5 | numberOfImages === 0) return;
var row = document.createElement("div")
row.setAttribute("class", "post-images")
for (file of files) {
const postImageChild = document.createElement("div");
postImageChild.setAttribute("class", "post-images__child_down")
const reader = new FileReader();
reader.onload = () => {
img = document.createElement("img")
img.setAttribute("src", reader.result)
img.onload = (e) => {
// here i will process on resizing the image
const canvas = document.createElement("canvas")
const max_width = 680
const scaleSize = max_width / e.target.width
canvas.width = max_width
canvas.height = e.target.height * scaleSize
var ctx = canvas.getContext("2d") // setting the context of the canvas
ctx.drawImage(e.target, 0, 0, canvas.width, canvas.height)
const encodedSource = ctx.canvas.toDataURL(e.target, 'image/png', 1)
const processedImg = document.createElement("img") // create a processed image and return it.
processedImg.src = encodedSource
$(postImageChild).append(processedImg)
imageFiles.push(processedImg)
}
}
$(row).prepend(postImageChild)
$(postImagesPreviewContainer).append(row);
reader.readAsDataURL(file)
}
After getting and resizing all the images I made the ajax call like that:
$("#CreatePostModal").on("click", (e) => {
if ($(e.target).attr("id") !== "createPostBtn") return;
e.preventDefault();
e.target.setAttribute("disabled", true);
$(e.target.nextElementSibling).fadeIn()
var form = $("#createPostForm")[0]
var data = new FormData(form); // getting the form data
console.log("this is the data", data)
for (var i = 0; i < imageFiles.length; i++) { // appending images to data
data.append('images', imageFiles[i]);
};
$.ajax({
url: $("#CreatePostModal").attr("data-url"),
data: data, //$("#CreatePostModal #createPostForm").serialize(),
method: "post",
processData: false,
cache: false,
contentType: false,
dataType: "json",
success: (data) => {
if (data.success) {
setTimeout(() => {
$(e.target).next().fadeOut();
ResetForm('createPostForm', 'PreviewImagesContainer')
$("#CreatePostModal").modal('hide')
$(e.target.nextElementSibling).fadeOut()
alertUser("Post", "has been created successfully!")// alerting the user
}, 1000)
} else {
$("#createPostForm").replaceWith(data.formErrors);
$("#PreviewImagesContainer").html("");
$("#CreatePostModal").find("form").attr("id", "createPostForm");
$(e.target.nextElementSibling).fadeOut()
};
$(e.target).prop("disabled", false);
},
error: (error) => {
console.log(error)
}
})
});
and finally getting the images from request.FILES in the views.py file.
def post(self, request, *args, **kwargs):
form = PostForm(request.POST or None, request.FILES or None)
result = {}
files = request.FILES.getlist("images")
if is_ajax(request=request) and form.is_valid():
post_obj = form.save(commit=False)
post_obj.author = request.user
print(post_obj)
post_obj.save()
for file in files:
new_file = Files(image=file)
new_file.save()
post_obj.images.add(new_file)
post_obj.save()
result['success'] = True
return JsonResponse(result)
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 have an user profile where he can crop his image, and so after cropping, and printing data in view nothing is passed to view
Form
<form method="post" action="change_photo/" id="avatar_changing_form">
{% csrf_token %}
<label for="upload_image">
{% if item.image%}
<img src="{{item.image.url}}" alt="" style="max-height:300px">
{%else%}
<img src="{%static 'avatar_sample.png' %}" id="uploaded_image"
class="img-responsive img-circle" />
{%endif%}
<div class="overlay">
<div class="text">Click to Change Profile Image</div>
</div>
<!-- <input type="file" name="image" class="image" id="upload_image" style="display:none" /> -->
{{imageForm.image}}
</label>
</form>
JS & Ajax
$(document).ready(function(){
const imageForm = document.getElementById('avatar_changing_form')
const confirmBtn = document.getElementById('crop')
const input = document.getElementById('upload_image')
const csrf = document.getElementsByName('csrfmiddlewaretoken')
var $modal = $('#modal');
var image = document.getElementById('sample_image');
var cropper;
$('#upload_image').change(function (event) {
var files = event.target.files;
var done = function (url) {
image.src = url;
$modal.modal('show');
};
if (files && files.length > 0) {
reader = new FileReader();
reader.onload = function (event) {
done(reader.result);
};
reader.readAsDataURL(files[0]);
}
});
$modal.on('shown.bs.modal', function () {
cropper = new Cropper(image, {
aspectRatio: 1,
viewMode: 3,
preview: '.preview'
});
}).on('hidden.bs.modal', function () {
cropper.destroy();
cropper = null;
});
$('#crop').click(function () {
cropper.getCroppedCanvas().toBlob((blob) => {
console.log('confirmed')
const fd = new FormData();
fd.append('csrfmiddlewaretoken', csrf[0].value)
fd.append('file', blob, 'my-image.png');
$.ajax({
type: 'POST',
url: imageForm.action,
enctype: 'multipart/form-data',
data: fd,
success: function (response) {
console.log('success', response)
$modal.modal('hide');
$('#uploaded_image').attr('src', fd);
},
error: function (error) {
console.log('error', error)
},
cache: false,
contentType: false,
processData: false,
})
});
});
})
View
def change_photo(request):
if request.user.is_authenticated and Guide.objects.filter(user = request.user).exists():
item = Guide.objects.get(user=request.user)
if request.method == "POST":
Photoform = ChangeImageForm(request.POST or None, request.FILES or None, instance = item)
if Photoform.is_valid():
print(Photoform.cleaned_data['image'])
Photoform.save()
return HttpResponseRedirect('/profile/')
form
class ChangeImageForm(ModelForm):
class Meta:
model = Guide
fields = ['image']
def __init__(self, *args, **kwargs):
super(ChangeImageForm, self).__init__(*args, **kwargs)
self.fields['image'].widget = FileInput(attrs={
'name':'image',
'class':'image',
'id':'upload_image',
'style':'display:none'
})
When i print image field from cleaned data in terminal displays "none", and when i load image through admin everything working good, can pls someone tell me where the problem is?
I want to select an image and POST that to the Django REST API that I have built but I am getting an error in doing so
""product_image": ["The submitted data was not a file. Check the encoding type on the form."]"
I can create and update strings and integers but when I try add the image field I get this error. I can POST images through Postman so I don't think its my API that's at fault.
Can anyone help me out here as I have looked around but can't seem to find anything.
add-product.component.ts
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl, Validators } from '#angular/forms';
import { ActivatedRoute, Router } from '#angular/router';
import { ApiService } from 'src/app/api.service';
import { Product } from 'src/app/models/Product';
import { Location } from '#angular/common';
import { Shop } from '../../../models/Shop';
#Component({
selector: 'app-add-product',
templateUrl: './add-product.component.html',
styleUrls: ['./add-product.component.css']
})
export class AddProductComponent implements OnInit {
product!: Product;
selectedFile!: File;
colours = [
'Red',
'Blue',
'Orange',
'Yellow',
'White',
'Black',
'Navy',
'Brown',
'Purple',
'Pink'
]
categories = [
'Food & Beverages',
'Clothing & Accessories',
'Home & Garden',
'Health & Beauty',
'Sports & Leisure',
'Electronic & Computing'
]
stock = [
'In Stock',
'Out Of Stock',
]
productForm = new FormGroup({
name: new FormControl(''),
price: new FormControl(0.00),
product_image: new FormControl(''),
product_description: new FormControl(''),
quantity: new FormControl(0),
colour: new FormControl(''),
stock: new FormControl(''),
shipping_fee: new FormControl(0.00),
weight: new FormControl(0.00),
shop: new FormControl(''),
category: new FormControl(''),
});
constructor(
private route: ActivatedRoute,
private apiService: ApiService,
private location: Location,
private router: Router,
private postService: ApiService
) { }
ngOnInit(): void {
}
goBack(): void {
this.location.back();
}
onFileSelected(event: any) {
this.selectedFile = <File>event.target.files[0]
console.log(event)
}
addProduct(): void {
console.log(this.productForm.value)
this.apiService.addProduct(
this.productForm.value
).subscribe(() => this.goBack(),
result => console.log(result),
);
}
}
add-product.component.html
<form [formGroup]="productForm">
<label>Product Name</label><br>
<input type="text" formControlName="name"><br>
<label>Price</label><br>
<input type="text" formControlName="price"/><br>
<input type="file" formControlName="product_image" (change)="onFileSelected($event)"><br>
<label>Description</label><br>
<textarea type="text" formControlName="product_description"></textarea><br>
<label>Quantity of Products</label><br>
<input type="text" formControlName="quantity"/><br>
<label>Colour of Product</label><br>
<select formControlName="colour">
<option value="" disabled>Choose a Colour</option>
<option *ngFor="let colour of colours" [ngValue]="colour">
{{ colour }}
</option>
</select><br>
<label>Stock Availability</label><br>
<select formControlName="stock">
<option value="" disabled>Stock Availability</option>
<option *ngFor="let stock of stock" [ngValue]="stock">
{{ stock }}
</option>
</select><br>
<label>Shipping Fee Price</label><br>
<input type="text" formControlName="shipping_fee"/><br>
<label>Weight (kg)</label><br>
<input type="text" formControlName="weight"/><br>
<label>Shop</label><br>
<input type="text" formControlName="shop"/><br>
<label>Category</label><br>
<select formControlName="category">
<option value="" disabled>Choose a Category</option>
<option *ngFor="let category of categories" [ngValue]="category">
{{ category }}
</option>
</select><br>
</form>
<button type="submit" (click)="addProduct()">Add</button>
<button (click)="goBack()">go back</button>
api.service.ts
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http'
import { Product } from './models/Product';
import { CookieService } from 'ngx-cookie-service';
import { Category } from './models/Category';
import { Shop } from './models/Shop';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class ApiService {
baseUrl = 'http://127.0.0.1:8000/';
baseProductUrl = `${this.baseUrl}app/products/`
baseCategoryUrl = `${this.baseUrl}app/categories/`
baseShopUrl = `${this.baseUrl}app/shops/`
headers = new HttpHeaders({
'Content-Type': 'application/json',
});
constructor(
private httpClient: HttpClient,
private cookieService: CookieService,
) { }
/* Product CRUD */
/* POST: add a product to the server */
addProduct(product: Product) {
const productUrl = `${this.baseProductUrl}`
return this.httpClient.post<Product>(productUrl, product, {headers: this.getAuthHeaders()})
}
// DELETE: delete the product from the server
deleteProduct(product_code: number): Observable<Product> {
const productUrl = `${this.baseProductUrl}${product_code}/`
return this.httpClient.delete<Product>(productUrl, {headers: this.getAuthHeaders()})
}
// PUT: update the product on the server
updateProduct(product_code: number, product: Product): Observable<Product> {
const productUrl = `${this.baseProductUrl}${product_code}/`
return this.httpClient.put<Product>(productUrl, product, {headers: this.getAuthHeaders()})
}
// GET: get all products from the server
getProducts() {
return this.httpClient.get<Product[]>(this.baseProductUrl, {headers: this.getAuthHeaders()});
}
// GET: get one product from the server
getProduct(product_code: number): Observable<Product> {
const productUrl = `${this.baseProductUrl}${product_code}/`
return this.httpClient.get<Product>(productUrl, {headers: this.getAuthHeaders()});
}
// GET: get all categories from the server
getCategories() {
return this.httpClient.get<Category[]>(this.baseCategoryUrl, {headers: this.getAuthHeaders()});
}
// GET: get one category from the server
getCategory(id: number): Observable<Category> {
const url = `${this.baseCategoryUrl}${id}/`;
return this.httpClient.get<Category>(url, {headers: this.getAuthHeaders()});
}
// GET: get all shops from the server
getShops() {
return this.httpClient.get<Shop[]>(this.baseShopUrl, {headers: this.getAuthHeaders()});
}
// GET: get one shop from the server
getShop(business_reg: string): Observable<Shop> {
const url = `${this.baseShopUrl}${business_reg}/`;
return this.httpClient.get<Shop>(url, {headers: this.getAuthHeaders()});
}
loginUser(authData: any) {
const body = JSON.stringify(authData);
return this.httpClient.post(`${this.baseUrl}auth/`, body, {headers: this.headers});
}
registerUser(authData: any) {
const body = JSON.stringify(authData);
return this.httpClient.post(`${this.baseUrl}user/users/`, body, {headers: this.headers});
}
getAuthHeaders() {
const token = this.cookieService.get('ur-token')
return new HttpHeaders({
'Content-Type': 'application/json',
Authorization: `Token ${token}`
});
}
}
The content-type must be multipart/form-data instead of application-json
that way you can send files to an API
I am using django with VueJS. The data is updating properly in my database.
I need to accomplish two things:
Post the correct content to the field image_file.
Get the downloaded image file pasted onto the servers folder which is media/shop/images
My attempted code is as below:
models.py
...
image_file = models.ImageField(upload_to='shop/images/', blank=True, null=True)
urls.py
...
urlpatterns += [
url(r'^Post-Items-Axios$', myviews.Post_Items_Axios, name='Post-Items-Axios'),
]
views.py
#api_view(['GET', 'POST', 'PUT', 'DELETE'])
def Post_Items_Axios(request):
if request.method == 'POST':
data_itemfullhd = request.data['Item Name']
data_image_file = request.data['Item Image File']
td_items, created = Md_Items.objects.get_or_create(
itemfullhd = data_itemfullhd)
td_items.imagefl = data_imagefl
td_items.image_file = data_image_file
td_items.save()
data = { 'data_itemfullhd': data_itemfullhd }
return Response(data)
bm_home.html
<template id="product-edit">
<div>
<h2>Product details</h2>
<form method="post" enctype="multipart/form-data">{% csrf_token %}
<div class="form-group">
<label for="edit-name">Item Name</label>
<input class="form-control" id="edit-name" v-model="product.itemfullhd" required/>
</div>
<!-- Upload single Image files -->
<div class="form-group">
<label for="edit-imagefile">Image</label>
<input type="file" id="edit-imagefile" #change="onFileChanged" required/>
</div>
<button type="submit" class="btn btn-primary" #click.prevent="updateProduct">Save</button>
<a class="btn btn-dark"><router-link to="/product-list">Cancel</router-link></a>
</form>
</div>
</template>
Vue template
var ProductEdit = Vue.extend({
template: '#product-edit',
data: function () {
return {
product: findProduct(this.$route.params.product_id),
selectedImage: null,
};
},
methods: {
onFileChanged (event) {
this.selectedImage = event.target.files[0]
this.product.image_file = this.selectedImage.name
},
updateProduct: function () {
let product = this.product;
products[findProductKey(product.id)] = {
id: product.id,
itemfullhd: product.itemfullhd,
image_file: product.image_file,
};
const data = {
"Item Name": product.itemfullhd,
"Item Image File": product.image_file,
}
// const formData = new FormData()
// formData.append('image_file', this.selectedImage, this.selectedImage.name)
// axios.post('/biggmount_home/Post-Items-Axios', formData, data)
axios.post('/biggmount_home/Post-Items-Axios', data)
router.push('/product-list');
},
}
});
The changes below have given me the result that I was looking to achieve:
Vue template
var ProductEdit = Vue.extend({
template: '#product-edit',
data: function () {
return {
product: findProduct(this.$route.params.product_id),
selectedImage: null,
};
},
methods: {
onFileChanged (event) {
this.selectedImage = event.target.files[0]
},
updateProduct: function () {
let product = this.product;
const formData = new FormData()
formData.append('Item Image', this.selectedImage, this.selectedImage.name)
formData.append('Item Name', product.itemfullhd)
axios.post('/biggmount_home/Post-Items-Axios', formData)
router.push('/product-list');
},
}
});
views.py
#api_view(['GET', 'POST', 'PUT', 'DELETE'])
def Post_Items_Axios(request):
if request.method == 'POST':
data_itemfullhd = request.data['Item Name']
td_items, created = Md_Items.objects.get_or_create(
itemfullhd = request.data['Item Name'],
)
td_items.image_file = request.data['Item Image']
td_items.save()
return HttpResponse('Success!')