React and Django DRF
The backend works, when i test it with an http file. and directly at the endpoint.
POST http://localhost:8000/api/products/ HTTP/1.1
Content-Type: application/json
Authorization: Bearer my_access_token
{
"name": "test",
"price": 23,
"stock": 3,
"description": "test description",
"in_sale": true,
"sale_price": 21,
"color": [
{
"name":"black",
"color_code": "#000000"
}
]
}
However when i fill the forms created in react jsx and send it to the API, i get Key Error when i leave the Color Nested Serializer active, when i remove it, i get Bad Request.
Here is my serializer.py
class ProductCreateSerializer(serializers.ModelSerializer):
class ColorCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Color
fields = ('name', 'color_code')
# color = ColorCreateSerializer(many=True, required=False)
def update(self, instance, validated_data):
# color_data = validated_data.pop("color")
with transaction.atomic():
instance = super().update(instance, validated_data)
if color_data is not None:
instance.color.all().delete()
for item in color_data:
Color.objects.create(product=instance, **item)
return instance
def create(self, validated_data):
color_data = validated_data.pop('color')
with transaction.atomic():
product = Product.objects.create(**validated_data)
for item in color_data:
Color.objects.create(product=product, **item)
return product
class Meta:
model = Product
fields = (
'id',
'name',
'description',
'price',
'stock',
'in_sale',
'sale_price',
'image',
'color'
)
extra_kwargs = {
'id': {'read_only': True}
}
Here is my views.py
class ProductView(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
permission_classes = [AllowAny]
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
def perform_create(self, serializer):
product = serializer.save(user=self.request.user)
def get_serializer_class(self):
if self.action == 'create' or self.action == 'update':
return ProductCreateSerializer
return super().get_serializer_class()
And finally my CreateProduct.jsx file
function CreateProduct() {
const [colors, setColors] = useState([{ name: '', color_code: '' }]);
const handleColorChange = (index, event) => {
let data = [...colors];
data[index][event.target.name] = event.target.value
setColors(data);
}
const addColorFields = () => {
let newColor = { name: '', color_code: '' }
setColors([...colors, newColor])
}
const removeColorFields = (index) => {
let data = [...colors];
data.splice(index, 1)
setColors(data)
}
const [product, setProduct] = useState(
{
name: '',
description: '',
price: '',
stock: '',
image: null,
in_sale: false,
sale_price: '',
}
);
const handleProductInputChange = (event) => {
if (event.target.name === 'in_sale') {
setProduct({
...product,
[event.target.name]: event.target.checked
})
} else {
setProduct({
...product,
[event.target.name]: event.target.value
})
}
};
const [selectedFile, setSelectedFile] = useState(null)
const [image, setImage] = useState(null)
const handleFileChange = (event) => {
setSelectedFile(event.target.files[0]);
setImage(selectedFile)
}
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData();
Object.entries(product).forEach(([key, value]) => {
if (key === 'image' && value) {
formData.append(key, image);
} else {
formData.append(key, value);
}
});
await api.post(`my_endpoint/`, formData);
};
return (
<>
<section className="bg-light">
<div className="container pb-5">
<Form onSubmit={handleSubmit}>
{/* Product Info */}
<div className="row gutters-sm shadow p-4 rounded">
<h4 className="mb-4">Product Info</h4>
<div className="col-lg-5 mt-2">
<div className="card mb-3">
{image ?
<img src={image} />
:
<img src="http://127.0.0.1:8000/media/products/placeholder-image.png" alt="" />
}
</div>
</div>
{/* <!-- col end --> */}
<div className="col-lg-7 mt-2">
<div className="card">
<div className="card-body mb-4">
<div style={{ marginBottom: 5 }}>
<label htmlFor="image" className="mb-0">
Product Image
</label>
<input
type="file"
className="form-control form-control-sm"
name="image"
id="image"
onChange={handleFileChange}
/>
</div>
<div style={{ marginBottom: 5 }}>
<label htmlFor="name" className="mb-0">
Title
</label>
<input
type="text"
className="form-control form-control-sm"
id="name"
name="name"
value={product.name || ''}
onChange={handleProductInputChange}
/>
</div>
<div>
<label htmlFor="name" className="mb-0">
Description
</label>
<input
type="text"
className="form-control form-control-sm"
id="description"
name="description"
value={product.description || ''}
onChange={handleProductInputChange}
/>
</div>
<div>
<label htmlFor="price" className="mb-0">
Price
</label>
<input
type="number"
className="form-control form-control-sm"
name="price"
value={product.price || ''}
onChange={handleProductInputChange}
/>
</div>
<div>
<label htmlFor="stock" className="mb-0">
Stock Qty
</label>
<input
type="number"
className="form-control form-control-sm"
name="stock"
value={product.stock || ''}
onChange={handleProductInputChange}
/>
</div>
<div className='mt-3 mb-2'>
<Form.Check
type="checkbox"
name="in_sale"
label='On Sale'
value={product.in_sale}
onChange={handleProductInputChange}
/>
</div>
<div>
<label htmlFor="sale_price" className="mb-0">
Sale Price
</label>
<input
type="number"
className="form-control form-control-sm"
name="sale_price"
value={product.sale_price || ''}
onChange={handleProductInputChange}
/>
</div>
</div>
</div>
</div>
</div>
<div className="row gutters-sm shadow p-4 rounded">
<h4 className="mb-4">Color</h4>
<div className="col-md-12">
<div className="card mb-3">
<div className="card-body">
{colors.map((input, index) => (
<div className="row text-dark mb-3" key={index}>
<div className="col-lg-2 mb-2">
<label htmlFor="color_name" className="mb-2">
Color Name
</label>
<input
type="text"
className="form-control"
name="name"
id="color_name"
value={input.name}
onChange={(event) => handleColorChange(index, event)} />
</div>
<div className="col-lg-2 mb-2">
<label htmlFor="color_code" className="mb-2">
Color Code
</label>
<input
type="text"
className="form-control"
name="color_code"
id="color_code"
value={input.color_code}
onChange={(event) => handleColorChange(index, event)} />
</div>
<div className="col-lg-2 mt-2">
<button type='button' onClick={() => removeColorFields(index)} className='btn btn-danger mt-4'>Remove</button>
</div>
</div>
))}
<button type='button' onClick={addColorFields} className="btn btn-primary mt-2">
<i className="fas fa-plus" /> Add More...
</button>
</div>
</div>
</div>
</div>
)
}
export default CreateProduct
What's wrong here??? If the endpoints works, it should be a frontend error, right? I'm new at React. Trying the FullStack life here. Please Help!
import React, { useState } from 'react';
import Form from 'react-bootstrap/Form';
import api from './api'; // Adjust the import path as needed
function CreateProduct() {
const [colors, setColors] = useState([{ name: '', color_code: '' }]);
const [image, setImage] = useState(null);
const [selectedFile, setSelectedFile] = useState(null);
const handleColorChange = (index, event) => {
let data = [...colors];
data[index][event.target.name] = event.target.value
setColors(data);
}
const addColorFields = () => {
let newColor = { name: '', color_code: '' }
setColors([...colors, newColor])
}
const removeColorFields = (index) => {
let data = [...colors];
data.splice(index, 1)
setColors(data)
const [product, setProduct] = useState(
{
name: '',
description: '',
price: '',
stock: '',
image: null,
in_sale: false,
sale_price: '',
}
);
const handleProductInputChange = (event) => {
if (event.target.name === 'in_sale') {
setProduct({
...product,
[event.target.name]: event.target.checked
})
} else {
setProduct({
...product,
[event.target.name]: event.target.value
})
}
};
const handleFileChange = (event) => {
setSelectedFile(event.target.files[0]);
setImage(URL.createObjectURL(event.target.files[0]));
setProduct({
...product,
image: event.target.files[0]
});
};
setImage(selectedFile)
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData();
Object.entries(product).forEach(([key, value]) => {
if (key === 'image' && value) {
formData.append(key, value);
} else {
formData.append(key, value);
}
});
// Optionally add colors to formData if needed
// formData.append('colors', JSON.stringify(colors));
await api.post(`my_endpoint/`, formData);
};
return (
<>
<section className="bg-light">
<div className="container pb-5">
<Form onSubmit={handleSubmit}>
{/* Product Info */}
<div className="row gutters-sm shadow p-4 rounded">
<h4 className="mb-4">Product Info</h4>
<div className="col-lg-5 mt-2">
<div className="card mb-3">
{image ?
<img src={image} alt="preview" />
:
<img src="http://127.0.0.1:8000/media/products/placeholder-image.png" alt="" />
}
</div>
</div>
{/* <!-- col end --> */}
<div className="col-lg-7 mt-2">
<div className="card">
<div className="card-body mb-4">
<div style={{ marginBottom: 5 }}>
<label htmlFor="image" className="mb-0">
Product Image
</label>
<input
type="file"
className="form-control form-control-sm"
name="image"
id="image"
onChange={handleFileChange}
/>
</div>
<div style={{ marginBottom: 5 }}>
<label htmlFor="name" className="mb-0">
Title
</label>
<input
type="text"
className="form-control form-control-sm"
id="name"
name="name"
value={product.name || ''}
onChange={handleProductInputChange}
/>
</div>
<div>
<label htmlFor="description" className="mb-0">
Description
</label>
<input
type="text"
className="form-control form-control-sm"
id="description"
name="description"
value={product.description || ''}
onChange={handleProductInputChange}
/>
</div>
<div>
<label htmlFor="price" className="mb-0">
Price
</label>
<input
type="number"
className="form-control form-control-sm"
name="price"
value={product.price || ''}
onChange={handleProductInputChange}
/>
</div>
<div>
<label htmlFor="stock" className="mb-0">
Stock Qty
</label>
<input
type="number"
className="form-control form-control-sm"
name="stock"
value={product.stock || ''}
onChange={handleProductInputChange}
/>
</div>
<div className='mt-3 mb-2'>
<Form.Check
type="checkbox"
name="in_sale"
label='On Sale'
checked={product.in_sale}
onChange={handleProductInputChange}
/>
</div>
<div>
<label htmlFor="sale_price" className="mb-0">
Sale Price
</label>
<input
type="number"
className="form-control form-control-sm"
name="sale_price"
value={product.sale_price || ''}
onChange={handleProductInputChange}
/>
</div>
</div>
</div>
</div>
</div>
<div className="row gutters-sm shadow p-4 rounded">
<h4 className="mb-4">Color</h4>
<div className="col-md-12">
<div className="card mb-3">
<div className="card-body">
{colors.map((input, index) => (
<div className="row text-dark mb-3" key={index}>
<div className="col-lg-2 mb-2">
<label htmlFor={`color_name_${index}`} className="mb-2">
Color Name
</label>
<input
type="text"
className="form-control"
name="name"
id={`color_name_${index}`}
value={input.name}
onChange={(event) => handleColorChange(index, event)} />
</div>
<div className="col-lg-2 mb-2">
<label htmlFor={`color_code_${index}`} className="mb-2">
Color Code
</label>
<input
type="text"
className="form-control"
name="color_code"
id={`color_code_${index}`}
value={input.color_code}
onChange={(event) => handleColorChange(index, event)} />
</div>
<div className="col-lg-2 mt-2">
<button type='button' onClick={() => removeColorFields(index)} className='btn btn-danger mt-4'>Remove</button>
</div>
</div>
))}
<button type='button' onClick={addColorFields} className="btn btn-primary mt-2">
<i className="fas fa-plus" /> Add More...
</button>
</div>
</div>
</div>
</div>
<button type="submit" className="btn btn-success mt-4">Create Product</button>
</Form>
</div>
</section>
</>
);
}
export default CreateProduct;
}
export default CreateProduct
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class ColorCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Color
fields = ('name', 'color_code')
class ProductCreateSerializer(serializers.ModelSerializer):
color = ColorCreateSerializer(many=True, required=False)
class Meta:
model = Product
fields = (
'id',
'name',
'description',
'price',
'stock',
'in_sale',
'sale_price',
'image',
'color'
)
extra_kwargs = {
'id': {'read_only': True}
}
def update(self, instance, validated_data):
color_data = validated_data.pop("color", None)
with transaction.atomic():
instance = super().update(instance, validated_data)
if color_data is not None:
instance.color.all().delete()
for item in color_data:
Color.objects.create(product=instance, **item)
return instance
def create(self, validated_data):
color_data = validated_data.pop('color', [])
with transaction.atomic():
product = Product.objects.create(**validated_data)
for item in color_data:
Color.objects.create(product=product, **item)
return product