I am trying to upload a from on my VueJS client to my Flask Server. I am sending the image in a FormData which includes other form fields. I am able to access the other form fields. However, whatever that is send to my server does not seem to be able to be be saved in my flask app. If it helps the data is being sent to my api as a immutable multi-dictionary.
Vuejs
<template>
<ValidationObserver v-slot="{ handleSubmit }">
<b-form #submit.prevent="handleSubmit(onSubmit)" enctype="multipart/form-data">
<ValidationProvider rules="required" v-slot="{ errors }">
<b-form-group
id="price-group"
label="Price:"
label-for="price-input"
>
<b-form-input
id="price-input"
v-model="productForm.price"
type="text"
required
placeholder="Enter price of the product..."
></b-form-input>
</b-form-group>
<span>{{ errors[0] }}</span>
</ValidationProvider>
<b-button-group>
<b-button
type="submit" variant="primary">Submit</b-button>
</b-button-group>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
productForm: {
price: '',
image_set: {
coverphoto: null,
},
showMessage: false,
notification: '',
},
coverphotoURl: '',
};
},
methods: {
createProduct(payload) {
const path = 'http://127.0.0.1:5000/new_products';
axios.post(path, payload)
.then(() => {
this.showMessage = true;
this.notification = 'Registration Successful';
})
.catch(() => {
this.showMessage = true;
this.notification = payload;
});
},
initForm() {
this.productForm.price = '';
this.productForm.image_set.coverphoto = null;
},
onSubmit() {
const payload = new FormData();
payload.append('price', this.productForm.price);
payload.append('coverphoto', this.productForm.image_set.coverphoto, this.productForm.image_set.coverphoto.name);
this.createProduct(payload);
},
onFileChange(e) {
const file = e.target.files[0];
this.productForm.image_set.coverphoto = file;
this.coverphotoURl = URL.createObjectURL(file);
},
</script>
Flask File
#app.route('/new_products', methods=['POST'])
def new_products():
if request.method == 'POST':
data = request.form
if request.files:
images = request.files.getlist('coverphoto')
else:
return jsonify({'message' : 'No File Uploaded'})
print(images) # Print images will return [<FileStorage: 'Coffee Roasters.jpg' ('image/jpeg')>]
newProduct = Products(str(uuid.uuid4()), data['name'], data['tasting_notes'], data['origination'], data['pairing'], data['price'])
try:
db.session.add(newProduct)
db.session.commit()
filename = secure_filename('coverphoto.jpg')
img_path = os.path.join('/',app.config['UPLOAD_FOLDER'], filename)
images.save(img_path)
new_img = Images(str(uuid.uuid4()), img_path, newProduct.id)
db.session.add(new_img)
db.session.commit()
return jsonify({'message' : 'Upload Successful'})
except:
return jsonify({'message' : 'Upload Unsuccessful'})
It how can i convert [<FileStorage: 'Coffee Roasters.jpg' ('image/jpeg')>] to something that flask is able to save? Thank you for yur help.
I guess you should set the proper header in Axios to be able to retrieve the file on the server.
Use this:
headers: {'Content-Type': 'multipart/form-data' }
})
Related
I want to develop an interface that uploads a file with Vue js from my backend in django, I can't figure out the problem. but here is the error I got: "POST http://127.0.0.1:8000/upload/ 500 (Internal Server Error)"
here the class "upload" in view.py:
def upload(request):
obj = None
dict={}
if request.method == 'POST':
repo = Repository(username="fedoraAdmin",password="fedora2022")
obj = repo.get_object(type=FileObject)
obj.file.content = request.FILES['file']
obj.file.mimetype=request.FILES['file'].content_type
obj.file.label=request.FILES['file'].name
obj.label = request.POST['label']
obj.save()
dict=obj.index_data()
print(dict)
return JsonResponse(dict)
my form in forms.py:
class UploadForm(forms.Form):
label = forms.CharField(max_length=255, # fedora label maxes out at 255 characters
help_text='Preliminary title for the new object. 255 characters max.')
file = forms.FileField()
Finally, my vue js code:
<template>
<div class="container"> <h1 class="title mb-6">Uploader</h1> <div>
<form #submit.prevent="submitFile" enctype="multipart/form-data">
<input type="file" #change="uploadFile" ref="file">
<button>Upload!</button>
</form>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'archivage',
mounted() {
document.title = 'Archivage | fedora'
} ,
methods: {
uploadFile() {
this.file = this.$refs.file.files[0];
this.label=this.$refs.file.files[0].name
},
performUpload() {
const formData = {
label: this.label,
file: this.file}
axios
.post('/upload/',formData)
.then(response => {
console.log(response.data) ;
}) .catch(error => {
console.log(error)
})
},
submitFile: function(){
this.performUpload() },
}
}
</script>
I have a simple django search functionality using js/ajax. I want to add functionality so that when the queryset is greater than 5 a 'Show all' href will appear in the search results and it will redirect to a page with all the queryset.
This is for the case when a queryset returns a large number of results, rather than have them in one big box.
I thought I could just add a dictionary to my queryset, e.g. data.append({'pk': <add number to querset>, 'name': 'Show all results'}) but then I think this will mess around with the js logic with the forEach loop.
I'd want each search result up to 5 to link to the detail view, but then the last one should link to a completely different view.
I'm not sure what the best option is here.
My search in views.py:
def search_results(request):
"""
Handles search logic
"""
if request.is_ajax():
res = None
quote = request.POST.get('quote')
qs = Quote.objects.filter(name__icontains=quote)
if len(qs) > 0 and len(quote) > 0:
data = []
for pos in qs:
item = {
'pk': pos.pk,
'name': pos.name,
'image': str(pos.image.url)
}
data.append(item)
res = data
else:
res = 'No quotes found...'
return JsonResponse({'data': res})
return JsonResponse({})
and main.js that handles loading the search results:
const url = window.location.href
const searchForm = document.getElementById('search-form')
const searchInput = document.getElementById('search-input')
const resultsBox = document.getElementById('results-box')
const csrf = document.getElementsByName('csrfmiddlewaretoken')[0].value
const sendSearchData = (quote) => {
$.ajax({
type: 'POST',
url: 'search/',
data: {
'csrfmiddlewaretoken': csrf,
'quote': quote,
},
success: (res)=> {
console.log(res.data)
const data = res.data
let length = data.length
console.log(length)
if (Array.isArray(data)) {
resultsBox.innerHTML = ""
data.forEach(quote=> {
resultsBox.innerHTML += `
<a href="${url}${quote.pk}" class="item">
<div class="row mt-2 mb-2">
<div class="col-2">
<img src="${quote.image}" class="quote-img">
</div>
<div class="col-10">
<h5>${quote.name}</h5>
<p class="text-muted">${quote.seller}</p>
</div>
</div>
</a>
`
})
} else {
if (searchInput.value.length > 0) {
resultsBox.innerHTML = `<b>${data}</b>`
} else {
resultsBox.classList.add('not-visible')
}
}
error: (err)=> {
console.log(err)
}
}
})
}
searchInput.addEventListener('keyup', e=>{
console.log(e.target.value)
if (resultsBox.classList.contains('not-visible')){
resultsBox.classList.remove('not-visible')
}
sendSearchData(e.target.value)
})
I am trying to query the database based on what the user has clicked on the page and display the data retrieved by it without refreshing the page. I am using Ajax for this. Let me show you the codes
html
<label for="landacq" class="civil-label">Land Acquisation Cases</label>
<input class="civil-category" type="radio" name="civil-cat" id="landacq" value="land acquisation" hidden>
<label for="sc" class="civil-label">Supreme Court</label>
<input class="civil-court" type="radio" name="civil-court" id="sc" value="supreme court" hidden>
<label for="limitation" class="civil-label">Limitation</label>
<input class="civil-law-type" type="radio" name="civil-law-type" id="limitation" value="limitation" hidden>
js
for (i = 0; i < lawTypeInput.length; i++) {
lawTypeInput[i].addEventListener("click", (e) => {
e.preventDefault();
cat = civilCatval;
court = civilCourtval;
lawT = civillawTypeval;
console.log("this is from ajax : ", cat, court, lawT);
$.ajax({
type: "POST",
headers: { "X-CSRFToken": csrftoken },
mode: "same-origin", // Do not send CSRF token to another domain.
url: "civil",
data: {
"cat[]": civilCatval,
"court[]": civilCourtval,
"lawT[]": civillawTypeval,
},
success: function (query) {
showCivilQ(query);
// console.log(data);
},
error: function (error) {
console.log(error);
},
});
});
}
function showCivilQ(query) {
q.textContent = query;
console.log(query);
}
So here for example, if the user the click the radio button in the html, the values are grabbed by in js file and then sent to the url mentioned as a POST request. There these values are use to filter the database and return the objects like this
views.py
def civil_home(request):
if request.is_ajax():
get_cat = request.POST.get('cat[]')
get_court = request.POST.get('court[]')
get_lawT = request.POST.get('lawT[]')
query = Citation.objects.filter(law_type__contains ='civil' ,sub_law_type__contains= get_cat, court_name__contains = get_court, law_category__contains = get_lawT)
return HttpResponse(query)
else:
subuser = request.user
subscription = UserSubscription.objects.filter(user = subuser, is_active = True)
context = {
'usersub': subscription,
}
return render(request, 'civil/civil_home.html', context)
This is the result I am getting which is correct.
My Question is these objects contain attributes having some values in for eg, title, headnote etc. How can I display these attributes in the html rather than displaying the object names returned as shown in the Image like title of the citation, headnote of the citation etc
A solution could be to return a json object instead of the query resultset; because Ajax works well with json
You need a function that translates a Citation object into a dictionary (change it based on your real attributes). All elements must be translated into strings (see date example)
def citation_as_dict(item):
return {
"attribute1": item.attribute1,
"attribute2": item.attribute2,
"date1": item.date.strftime('%d/%m/%Y')
}
This dictionary must be translated into a json through import json package
def civil_home(request):
if request.is_ajax():
get_cat = request.POST.get('cat[]')
get_court = request.POST.get('court[]')
get_lawT = request.POST.get('lawT[]')
query = Citation.objects.filter(law_type__contains ='civil' ,sub_law_type__contains= get_cat, court_name__contains = get_court, law_category__contains = get_lawT)
response_dict = [citation_as_dict(obj) for obj in query]
response_json = json.dumps({"data": response_dict})
return HttpResponse(response_json, content_type='application/json')
else:
subuser = request.user
subscription = UserSubscription.objects.filter(user = subuser, is_active = True)
context = {
'usersub': subscription,
}
return render(request, 'civil/civil_home.html', context)
In your HTML page you should be able to parse the response as a normal JSON object
I figured out another way to do it, which is giving me the required results too.
Here I am filtering the values of the query, and then converting it to a list and passing it as a JsonResponse
views.py
def civil_home(request):
if request.method == "POST" and request.is_ajax():
get_cat = request.POST.get('cat[]')
get_court = request.POST.get('court[]')
get_lawT = request.POST.get('lawT[]')
query = Citation.objects.values().filter(law_type__contains ='civil' ,sub_law_type__contains= get_cat, court_name__contains = get_court, law_category__contains = get_lawT)
result = list(query)
return JsonResponse({"status": "success", "result": result})
else:
subuser = request.user
subscription = UserSubscription.objects.filter(user = subuser, is_active = True)
context = {
'usersub': subscription,
}
return render(request, 'civil/civil_home.html', context)
And then I am recieving the reponse here and iterrating over it to print the attributes in the html
js
for (i = 0; i < lawTypeInput.length; i++) {
lawTypeInput[i].addEventListener("click", (e) => {
e.preventDefault();
cat = civilCatval;
court = civilCourtval;
lawT = civillawTypeval;
console.log("this is from ajax : ", cat, court, lawT);
$.ajax({
type: "POST",
headers: { "X-CSRFToken": csrftoken },
mode: "same-origin", // Do not send CSRF token to another domain.
url: "civil",
data: {
"cat[]": civilCatval,
"court[]": civilCourtval,
"lawT[]": civillawTypeval,
},
success: function (response) {
console.log(response.result);
civilData = response.result;
if ((response.status = "success")) {
$("#queryResult").empty();
for (i = 0; i < civilData.length; i++) {
$("#queryResult").append(
`
${civilData[i].title}
<p>${civilData[i].headnote}</p>
`
);
}
} else {
$("#queryResult").empty();
$("#queryResult").append(
`
<p>No Citations Found</p>
`
);
}
},
error: function (error) {
console.log(error);
},
});
});
}
A csrf_token can be mentioned at the top of the html page and then it can be passed in the header to avoid any conflict.
I tried different ways to upload images to SQL, but the problem is that I cannot get the file's data at backend. Some answers say that it's because the form does not have an enctype but I tried that too. I'm not sure if it's applicable to Vue since I am using axios. Also, I tried using uploadFile as a parameter for the axios so $_FILES would also read it as $_GET, because it worked on some of my codes as well. By the way, the submitTestData is in another file in another folder, which is inside a store (VueX). I used dispatch to send the data towards the store so that it would finally send a post method to backend.
store.js
submitTestData2 ({ commit }, payload) {
console.log(payload.uploadFile)
return new Promise((resolve, reject) => {
const formData = new FormData()
formData.append('uploadFile', payload.uploadFile)
const config = {
headers: { 'Content-Type': 'multipart/form-data' }
}
axios
.post(
'http://localhost/MyComposer/',
{
token: payload.token,
subject: payload.subject,
timer: payload.timer,
question: payload.question,
answer: payload.answer,
formData
},
{
params: {
submitId: 7,
uploadFile: formData
},
config
}
)
.then(response => {
commit('SAVE_TEST_DATA', response.data)
console.log(response)
resolve(response)
})
.catch(error => {
reject(error)
})
})
},
AddTest.vue
<q-form class="q-pa-md" align="center">
<h5>Test Creation Form</h5>
<!-- <q-btn label="Add Subject" color="primary" to="/addsub" /> -->
<q-btn label="Return to Main" to="/dashboard" color="primary" />
<q-btn label="View Student Answers" color="primary" to="/subjectntestlist" />
<q-btn label="View Student Profile" color="primary" to="/studentprofile" />
<q-card>
<q-separator />
<q-card-section class="q-gutter-md" align="center">
<q-select
filled
v-model="testItems.subject"
:options="option"
map-options
emit-value
option-value="subjectId"
option-label="subjectName"
label="Choose a Subject"
style="width: 250px"
stack-label
input-debounce="0"
/>
<q-file
filled
v-model="testItems.uploadFile"
label="Upload File Here"
style="width: 500px"
/>
<h5>Timer</h5>
<q-input label="Minute(s)" name="timer" v-model="testItems.timer" style="width: 500px" />
<h5>Question</h5>
<q-input name="question" v-model="testItems.question" style="width: 500px" />
<h5>Answer</h5>
<q-input name="answer" v-model="testItems.answer" style="width: 500px" />
<br />
<q-btn label="Save Test Item" #click="submitTestData" />
</q-card-section>
</q-card>
</q-form>
submitTestData1() {
this.$store
.dispatch("submitTestData2", {
token: this.token,
subject: this.testItems.subject,
question: this.testItems.question,
answer: this.testItems.answer,
uploadFile: this.testItems.uploadFile,
timer: this.testItems.timer
})
.then(response => {
alert("Test was added to the database!");
});
},
<?php
namespace Classes;
use Classes\ConnectDb;
class TestClass
{
public function addTest()
{
$datab = new ConnectDb;
$db = $datab->Connect();
if (isset($_GET['submitId']) && $_GET['submitId'] == 7) {
$testdata = file_get_contents('php://input');
$testdecodedData = json_decode($testdata);
$subject = $testdecodedData->{'subject'};
$access_id = $testdecodedData->{'token'};
$question = $testdecodedData->{'question'};
$answer = $testdecodedData->{'answer'};
// $testImage = $testdecodedData->{'uploadFile'};
$testTimer = $testdecodedData->{'timer'};
$name = $_FILES['uploadFile'];
echo $name;
$testdataDb = array(
'SubjectId' => $subject,
'AccessId' => $access_id,
'Question' => $question,
'Answer' => $answer,
// 'TestImage' => $testImage,
'Timer' => $testTimer * 60
);
$testId = $db->insert('testdetails', $testdataDb);
if ($testId) {
echo 'Test details were added!';
}
}
}
You need to pass header as well like below
submitTestData ({ commit }, payload) {
console.log(payload.uploadFile)
return new Promise((resolve, reject) => {
const formData = new FormData()
formData.append('uploadFile', payload.uploadFile)
const config = {
headers:{'Content-Type' : 'multipart/form-data'}
};
axios
.post('http://localhost/MyComposer/',formData,config)
.then(response => {
commit('SAVE_TEST_DATA', response.data)
console.log(response)
resolve(response)
})
.catch(error => {
reject(error)
})
})
},
It looks like you're not getting the file's name properly from the $_FILES superglobal. You have $_FILES['uploadFile'] in your code however the $_FILES array is structured like this for uploads (uploadFile represents the name of the file upload input field from your form so this varies by input field name):
Array
(
[uploadFile] => Array
(
[name] => users_file_name.png
[type] => image/png
[tmp_name] => /path/to/temporary/files/abc123
[error] => 0
[size] => 12345
)
)
So to access the file's name, you need to change the code to this: $_FILES['uploadFile']['name'].
The actual file is stored in a temporary file location on the server so you'll need to grab that temporary file and move it somewhere else on your server. Something like this is what most people do:
$temp_file = $_FILES['uploadFile']['tmp_name'];
$target_upload_destination = 'path/to/desired/directory/' . basename($_FILES['uploadFile']['name']);
// Check to see that the file was moved to desired destination successfully
if (move_uploaded_file($temp_file, $target_upload_destination)) {
// do something here
} else {
// Fallback logic here
}
Obviously there should be some logic checks before moving the temp file on your server but, I hope you get the basic idea behind this. You should use the file path once it's moved for the DB insert. I hope this helps.
There are several problems here both in your client-side and server-side code.
Client side
If you want to send a file, you must use a multipart/form-data request with a FormData payload. You appear to be trying to combine a JSON payload with an embedded FormData which simply won't work.
You need something like this
const formData = new FormData()
Object.entries(payload).forEach(([key, val]) => {
// adds all the properties in "payload" to "formData"
formData.append(key, val)
})
axios.post('http://localhost/MyComposer', formData, {
params: { submitId: 7 }
})
❗ Note that there is no Content-type header added. Passing a FormData instance sets this automatically with the required mime boundaries.
Server-side
On the PHP side, you would get the token, subject, timer, etc values from $_POST
$subject = $_POST['subject'];
$access_id = $_POST['token'];
$question = $_POST['question'];
// etc
The upload file will be available in $_FILES (see https://www.php.net/manual/features.file-upload.post-method.php)
$uploadFile = $_FILES['uploadFile'];
if (!$uploadFile['error']) {
echo $uploadFile['name'];
}
I am trying to create an avatar editor following the Build a Forum video series.
I am on Laravel 5.8.34.
The console.log in the method #handleFileUpload(e)# shows the file uploaded.
The uploaded image appears on the page.
The console.log in the method #persist(file)# shows an empty object.
DATA FormData {}
The upload does not persist.
My Controller Method:
public function avatar_upload($id)
{
$validate = request()->validate([
'avatar' => ['required', 'image']
]);
$emp = Employee::with('user')->where('user_id', $id)->first();
$avatar = $emp->user->firstName . $emp->user->lastName . '.png';
Storage::disk('spaces')
->putFileAs('avatars', request()->file('avatar'), $avatar, 'public');
$emp->avatar = $avatar;
$emp->save();
return response([], 204);
} // end function
My Component:
<template>
<div>
<div class="text-center mb-4">
<div class="flex justify-center font-thin text-grey-dark text-2xl">
{{user.office}}
</div>
<div class="text-center">
<img class="relative rounded-lg"
:src="avatar">
</div>
<form #submit.prevent="handleFileUpload"
enctype="multipart/form-data"
v-if="canEdit">
<input
type="file"
name="avatar"
ref="file"
accept="image/png"
class="tw-input"
#change="handleFileUpload">
</form>
</div>
</div>
</template>
<script type="text/babel">
export default {
name: 'AvatarReplace',
data() {
return {
canEdit: true,
avatar: this.user.avatar
};
},
props: ['user'],
methods: {
handleFileUpload(e) {
if(! e.target.files.length) { return; } // end if
let file = e.target.files[0];
console.log('FILE', file);
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = e => {
this.avatar = e.target.result;
};
this.persist(file);
},
persist(file) {
let data = new FormData();
data.append('avatar', file);
console.log('DATA', data);
let path = `/api/staff/avatar_upload/${this.user.id}`;
axios.post(path, data)
.then((rsp) => {
//console.log(rsp);
//this.$toastr.s('File Uploaded');
});
}
}
};
</script>
This is not a normal form, Make axios knows that content-type is multipart/form-data
axios.post(path, data, {
headers: {
'Content-Type': 'multipart/form-data'
}
}).then((response) => {
//
});