How to upload an image to django from an html input file - django

I am developing a web service on django with frontend on react. I ran into a problem that I can't upload an image to django. Below is my component code where I'm trying to download it:
export function ProviderRegistration(){
const[logoField, setLogoField] = useState()
const token = useSelector((state) => state.user.token)
const [cookie, setCookie] = useCookies(['auth'])
const confirm = () => {
axios.post(`/providers/?username=${cookie.auth.login}`,
{
photo : logoField
},
{ "headers" : { "Authorization" : "token " + token }})
.then(res => console.log(res))
.catch(err => console.log(err))
}
return(
<div className="registration-provider-container">
<div className="registration-title">Provider registration</div>
<input type="file" className="registration-logo-add" onChange={(e) => setLogoField(e.target.value)}/>
<button className="registration-confirm" onClick={() => confirm()}>Confirm</button>
</div>
)}
And the endpoint processing this request
class Providers(viewsets.ModelViewSet):
filterset_class = ProvidersFilter
queryset = Provider.objects.all()
permission_classes = [IsAuthenticatedOrReadOnly, IsProviderPermission, IsOneToOneProviderPermission]
def get_serializer_class(self):
if self.action == 'list':
return GetProviderSerializer
else:
return PutProviderSerializer
def create(self, request):
username = request.GET.get('username', '')
user = User.objects.get(username=username).pk
request.data.update({'user' : user})
print(request.data)
return super().create(request)
When I try to upload an image, django returns the following error:
"The submitted data was not a file. Check the encoding type on the form."
And I haven't found a way to correctly upload an image to django using Ajax.
I also output what my browser sends to the server:
{photo : 'C:\\fakepath\\magnit.jpg'}

Related

How to upload an image using a post api in django

I am working on a project using django 3.1 for backend and vue 3 for frontend.
I am pretty new to Django so I am still learning the ropes and I do not know if what I am trying is totally wrong.
I created a model that holds a user email and and an image field as follows:
class UsedBike(models.Model):
sellerEmail = models.CharField(max_length=255)
image = models.ImageField(upload_to='uploads/', blank=True, null=True)
class Meta:
ordering = ('sellerEmail', )
def __str__(self):
return self.sellerEmail
def get_image(self):
if self.image:
return 'http://127.0.0.1:8000' + self.image.url
return ''
I created a serializer for my model as follows:
class UsedBikeSerializer(serializers.ModelSerializer):
class Meta:
model = UsedBike
fields = (
"id",
"sellerEmail",
"get_image",
)
and in the views file, I created a function for saving the data in the database:
#api_view(['POST'])
def sellBike(request):
serializer = UsedBikeSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
I also registered the url in the urls file.
As for vue part, I used axios to send my post request as follows:
submitData() {
const formData = {
sellerEmail: this.sellerEmail,
image: this.productImage
}
axios
.post("/api/v1/sell-bike/", formData)
.then(response => {
console.log(response)
}).catch(error => {
console.log(error)
})
},
where in the template I am getting the inputs like this:
<input type="text" v-model="sellerEmail">
<input type="file" accept="image/png, image/jpeg" id="imageInput" v-on:change="onFileChange()">
<button #click="submitData">Upload</button>
and the method onFileChange is:
onFileChange(e) {
let imageInput = document.getElementById("imageInput")
this.productImage = imageInput.files[0]
},
When I send the request, I get "POST /api/v1/sell-bike/ HTTP/1.1" 200 55 in the django terminal. However, If I print request.data I get the following:
{'sellerEmail': 'test#email.com', 'image': {}}
As you can see, image is empty and when I checked the database, the email part is filled correctly but image is empty.
How can I solve this problem?
Any help is appreciated.
IN your axios call, you need to include the below header as well:
submitData() {
const formData = new FormData();
formData.append('sellerEmail', this.sellerEmail);
formData.append('image', this.productImage);
const headers = {headers: { 'Content-Type': 'multipart/form-data' }}
axios
.post("/api/v1/sell-bike/", formData, headers)
.then(response => {
console.log(response)
}).catch(error => {
console.log(error)
})
}
Also, to save image to database, you also need to include the "image" in fields inside UsedBikeSerializer

Next.js not receiving cookie from Django

I am messing around with Next.js and regular Django to do some testing, study etc.
I am trying to make a basic authentication system that simply takes the email and password, send to the Django server and it simply returns a cookie with the user id.
When I login with valid credentials, the server returns an "OK" (as it is supposed to do), however I don't get any of the cookies I'm supposed to have (not even the default csrf token from Django).
(sample data for design testing)
Using Django 4.0.6 and Next.js 12.3.1
Login route on Django
#csrf_exempt
def login(req):
body = json.loads(req.body.decode("utf-8"))
res = None
if (User.objects.filter(email=body["email"])):
user = User.objects.get(email=body["email"])
if (user.password == body["password"]):
res = JsonResponse({"success": True})
res.status_code = 200
res.set_cookie("AuthToken", json.dumps(
{"id": user.id}),
max_age=60 * 60 * 24,
httponly=False,
samesite="strict",
)
else:
res = JsonResponse({"success": False})
res.status_code = 401
else:
res = JsonResponse({"success": False})
res.status_code = 404
res.__setitem__("Access-Control-Allow-Origin", "*")
print(res.cookies)
return res
My login page on Next
import Link from "next/link";
import { useRouter } from "next/router";
import React, { FormEvent, useState } from "react";
import styled from "styled-components";
import { CONFIG } from "../../../DESIGN_CONFIG";
export default function Login() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const router = useRouter();
async function logIn(e: FormEvent) {
e.preventDefault();
const body = JSON.stringify({ email: email, password: password });
console.log(body);
console.log(process.env.API_URL);
const res = await fetch(process.env.API_URL + "login/", {
method: "POST",
body: body,
});
console.log(res);
if (res.status === 200) {
router.push("/");
}
}
return (
<Background>
<LoginForm onSubmit={logIn}>
<h1>LOGIN</h1>
<label htmlFor="email">E-Mail</label>
<input
type="email"
name="email"
id="email"
onChange={(e) => setEmail(e.target.value)}
required
/>
<label htmlFor="password">Password</label>
<input
type="password"
name="password"
id="password"
onChange={(e) => setPassword(e.target.value)}
required
/>
<button type="submit">Log In</button>
<span>
Or{" "}
<Link href="/user/sign-up">
<a>sign-up</a>
</Link>
</span>
</LoginForm>
</Background>
);
}
// This way down just got the styled-components for background and login form
Prints in the console of the body and the res (and the api route):
I'm using no middleware on Next nor Django and haven't changed anything in the _app.js.
I tried using invalid credentials and got the expected behavior.
I tried using Postman and it gets the cookies.
I tried making a simpler cookie.
res.set_cookie("AuthToken", user.id)

Testing AJAX in Django

I want to test an AJAX call in my Django app.
What is does is adding a product to a favorite list. But I can't find a way to test it.
My views.py:
def add(request):
data = {'success': False}
if request.method=='POST':
product = request.POST.get('product')
user = request.user
splitted = product.split(' ')
sub_product = Product.objects.get(pk=(splitted[1]))
original_product = Product.objects.get(pk=(splitted[0]))
p = SavedProduct(username= user, sub_product=sub_product, original_product = original_product)
p.save()
data['success'] = True
return JsonResponse(data)
My html:
<form class="add_btn" method='post'>{% csrf_token %}
<button class='added btn' value= '{{product.id }} {{ sub_product.id }}' ><i class=' fas fa-save'></i></button
My AJAX:
$(".row").on('click', ".added", function(event) {
let addedBtn = $(this);
console.log(addedBtn)
event.preventDefault();
event.stopPropagation();
var product = $(this).val();
console.log(product)
var url = '/finder/add/';
$.ajax({
url: url,
type: "POST",
data:{
'product': product,
'csrfmiddlewaretoken': $('input[name=csrfmiddlewaretoken]').val()
},
datatype:'json',
success: function(data) {
if (data['success'])
addedBtn.hide();
}
});
});
The problem is that I pass '{{product.id }} {{ sub_product.id }}' into my views.
My test so far:
class Test_add_delete(TestCase):
def setUp(self):
self.user= User.objects.create(username="Toto", email="toto#gmail.com")
self.prod = Product.objects.create(
name=['gazpacho'],
brand=['alvalle'],
)
self.prod_2 = Product.objects.create(
name=['belvita'],
brand=['belvita', 'lu', 'mondelez'],
)
def test_add(self):
old_saved_products = SavedProduct.objects.count()
user = self.user.id
original_product = self.prod.id
sub_product = self.prod_2.id
response = self.client.post(reverse('finder:add', args=(user,))), {
'product': original_product, sub,product })
new_saved_products = SavedProducts.objects.count()
self.assertEqual(new_saved_products, old_saved_products + 1)
My test is not running and I get a SyntaxError 'product': original_product, sub_product. I know it's not the proper way to write it but my AJAX send the two ids with a space in between to the view.
If all you want to do is test if the data was actually saved, instead of just returning data['success'] = True you can return the whole entire new object... That way you can get back the item you just created from your API, and see all the other fields that may have been auto-gen (ie date_created and so on). That's a common thing you'll see across many APIs.
Another way to test this on a Django level is just to use python debugger
import pdb; pdb.set_trace() right before your return and you can just see what p is.
The set_trace() will stop python and give you access to the code scope from the command line. So just type 'l' to see where you are, and type(and hit enter) anything else that's defined, ie p which will show you what p is. You can also type h for the help menue and read the docs here

How to upload File from Angular 7 to Django - get error 403 (Forbidden)

I tried now in so many different ways, I can not get a file uploaded with Angular 7 to the Django Backend - shouldn't be so difficult?!
My .html:
<div class="form-group">
<label for="file">Choose File</label>
<input type="file"
id="file"
(change)="handleFileInput($event.target.files)">
</div>
uploader.component.ts
fileToUpload: File = null;
handleFileInput(files: FileList) {
this.fileToUpload = files.item(0);
this.uploadFileToActivity();
}
uploadFileToActivity() {
this.uploaderService.post(this.fileToUpload).subscribe(data => {
// do something, if upload success
}, error => {
console.log(error);
});
}
my uploader.service.ts that also shows the upload progress
public post( fileToUpload: File): Observable<number>{
const url = '/api/upload/';
var subject = new Subject<number>()
const req = new HttpRequest('POST', url, fileToUpload, {
reportProgress: true,
});
this.httpClient.request(req).subscribe(event => {
if (event.type === HttpEventType.UploadProgress) {
const percentDone = Math.round(100 * event.loaded / event.total);
subject.next(percentDone);
} else if (event instanceof HttpResponse) {
subject.complete();
}
});
return subject.asObservable();
}
And in the Django backend:
views.py:
def post(request):
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
#handle_uploaded_file(request.FILES['file'])
return HttpResponseRedirect('/success/url/')
else:
form = UploadFileForm()
return render(request, 'index.html', {'form': form})
forms.py:
class UploadFileForm(forms.Form):
title = forms.CharField(max_length=50)
file = forms.FileField()
and urls.py:
urlpatterns = [
path(r'api/upload/', views.post, name='post'),
]
When I ran this I get
zone.js:2969 POST http://127.0.0.1:8000/api/upload/ 403 (Forbidden)
Do I need to include an authorization token? If yes: how?
Thanks a lot!
EDIT: After the useful input from Martin Urbanec I inspected the file upload request in the Browser. Here the result:
Someone any idea what I need to change in my code above to make this work?
Content-Type header must be multipart/form-data to transfer any files. I recommend you to check if this header is sent to your Django backend.

I'm trying to get a rest resource to a python server with Ionic 3

How to title I'm trying to get a rest resource from a server.In particular I must post some data to the server(user and password) and If the request is successfull I simply print "Success" otherwise "error".When I make the request my server answer me with a code 200(which make me think that everything has come to and end) but in my ionic web page I found this error "Failed to load http://127.0.0.1:5000/: Request header field Access-Control-Allow-Origin is not allowed by Access-Control-Allow-Headers in preflight response".I tried to solve this problem implementing the crossdomain in my server as recommended by several guides but I could not get anything.I also tried to reach the rest resource using in my terminal the comand "curl POST --data "user=0124000769&password=dfgdsfgs" --verbose http://127.0.0.1:5000 " and this return me the right result so I think my server is working fine.To implement the user interface I'm using Ionic 3 framework.Thank you all in advance.Excuse me for my English.This is my code in Ionic:
login.ts:
#Component({
selector: 'page-home',
templateUrl: 'login.html'
})
export class LoginPage {
private mat:string;
private pass:string;
constructor(public navCtrl: NavController,public http: Http) {}
postRequest(){
var headers = new Headers();
headers.append("Accept","application/json");
headers.append("Content-Type","application/json");
headers.append('Access-Control-Allow-Origin', '*');
let options = new RequestOptions({headers:headers});
let postParams = {
mat:this.mat,
pass:this.pass
};
this.http.post("http://127.0.0.1:5000/",postParams,options).subscribe(data=>{
console.log("OK")},error2 => {
console.log("Error")
});
}
}
login.html:
<ion-header>
<ion-navbar>
<ion-title>
Login Page
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding id="page1">
<form id="page-form2">
<ion-item id="page-input3">
<ion-label>
Matricola
</ion-label>
<ion-input type="text" placeholder="" [(ngModel)]="mat" name="matricola"></ion-input>
</ion-item>
<ion-item id="page-input4">
<ion-label>
Password
</ion-label>
<ion-input type="password" placeholder="" [(ngModel)]="pass" name="password"></ion-input>
</ion-item>
</form>
<button id="page-button2" ion-button color="positive" block (click)="postRequest()">
Login
</button>
</ion-content>
This is the code of my server
My rest Resource:
#app.route('/',methods=['POST','OPTIONS'])
#crossdomain(origin='*')
def login():
username = request.get('mat')
password = request.get('pass')
s = Scraper(username, password)
return jsonify(s.login())
My crossdomain:
def crossdomain(origin=None, methods=None, headers=None, max_age=21600,
attach_to_all=True, automatic_options=True):
"""Decorator function that allows crossdomain requests.
Courtesy of
https://blog.skyred.fi/articles/better-crossdomain-snippet-for-flask.html
"""
if methods is not None:
methods = ', '.join(sorted(x.upper() for x in methods))
if headers is not None and not isinstance(headers, basestring):
headers = ', '.join(x.upper() for x in headers)
if not isinstance(origin, basestring):
origin = ', '.join(origin)
if isinstance(max_age, timedelta):
max_age = max_age.total_seconds()
def get_methods():
""" Determines which methods are allowed
"""
if methods is not None:
return methods
options_resp = current_app.make_default_options_response()
return options_resp.headers['allow']
def decorator(f):
"""The decorator function
"""
def wrapped_function(*args, **kwargs):
"""Caries out the actual cross domain code
"""
if automatic_options and request.method == 'OPTIONS':
resp = current_app.make_default_options_response()
else:
resp = make_response(f(*args, **kwargs))
if not attach_to_all and request.method != 'OPTIONS':
return resp
h = resp.headers
h['Access-Control-Allow-Origin'] = origin
h['Access-Control-Allow-Methods'] = get_methods()
h['Access-Control-Max-Age'] = str(max_age)
h['Access-Control-Allow-Credentials'] = 'true'
h['Access-Control-Allow-Headers'] = \
"Origin, X-Requested-With, Content-Type, Accept, Authorization"
if headers is not None:
h['Access-Control-Allow-Headers'] = headers
return resp
f.provide_automatic_options = False
return update_wrapper(wrapped_function, f)
return decorator
My Login function:
def login(self) :
result = {'login': '0' }
url = 'my_url'
//There is some action to control the data;
if not value :
return result
result['login'] = '1'
#Codifica 'result' in formato JSON
return json.dumps(result)
if you use chrome try to use --disable-web-security option.
Use this on terminal:
open -n -a /Applications/Google\ Chrome.app --args --user-data-dir="/tmp/someFolderName" --disable-web-security