0
votes

I have a 'category' class that I use as a foreign key in my class 'product', I'm filling a dropdown with an object list of the current categories from my database, but I'm not sure how to pass it to my REST API.

I'm not sure if I'm not sending the selected data to my backend or I messed up my serializers.

I get this error alert in the console:

django.db.utils.IntegrityError: null value in column "category_id" violates not-null constraint DETAIL: Failing row contains (8, Title, , desc, 123, , , 2021-05-04 21:30:26.875778+00, null, 1).

FORM:

 <form @submit.prevent="submitForm">
                    <div class="field">
                        <label>TITLE</label>
                        <div class="control">
                            <input type="text" class="input" v-model="name">
                        </div>
                    </div>

                    <div class="field">
                        <label>DESCRIPTION</label>
                        <div class="control">
                            <input type="text" class="input" v-model="description">
                        </div>
                    </div>

                    <div class="field">
                        <label>PRICE</label>
                        <div class="control">
                            <input type="number" class="input" v-model="price">
                        </div>
                    </div>

                    <div class="field">
                        <label>CATEGORY</label>
                        <select v-model="category">              
                            <option v-for="category in categoryList" :key="category.id" :value="category.id">{{ category.name }}</option>
                        </select>
                    </div>

                    <div class="notification is-danger" v-if="errors.length">
                        <p v-for="error in errors" v-bind:key="error">{{ error }}</p>
                    </div>

                    <div class="field">
                        <div class="control">
                            <button class="button is-dark">CREATE</button>
                        </div>
                    </div>
                </form>

VUE:

<script>
import axios from 'axios'
import { toast } from 'bulma-toast'

export default {
    name: 'CreatePost',
     data(){
        return{
            name: '',
            description: '',
            price: '',
            category: {},
            errors: [],
            categoryList: []
        }
    },
    mounted() {
        this.getCategoryList()
        document.title = 'Creating post | PLACE'
    },
    methods: {
        getCategoryList() {
            axios
            .get('/api/v1/category-list/')
            .then(response => {
                this.categoryList = response.data
            })
            .catch(error => {
                console.log(error)
            })

        },
        submitForm(){
            this.errors = []

            if (this.name === ''){
                this.errors.push('Title input is requiered.')
            }

            if (this.description === ''){
                this.errors.push('Description input is requiered.')
            }

            if (this.price === ''){
                this.errors.push('Price input is requiered.')
            }

            if (!this.errors.length){
                const data = {
                    name: this.name,
                    description: this.description,
                    price: this.price,
                    category: this.category
                }

                axios
                    .post("/api/v1/create-post/", data)
                    .then(response => {
                        toast({
                            message: 'Post created!',
                            type: 'is-success',
                            dismissible: true,
                            pauseOnHover: true,
                            duration: 2000,
                            position: 'bottom-right',
                        })

                        this.$router.push('/my-account')
                    })
                    .catch(error =>{
                        if (error.response){
                            for (const property in error.response.data) {
                                this.errors.push(`${property}: ${error.response.data[property]}`)
                            }

                            console.log(JSON.stringify(error.response.data))
                        }else if (error.message){
                            this.errors.push('Something bad happened...')
                            
                            console.log(JSON.stringify(error))
                        }
                    })
            }
        }
    }
}
</script>

DRF VIEW:

@api_view(['POST'])
@authentication_classes([authentication.TokenAuthentication])
@permission_classes([permissions.IsAuthenticated])
def create(request):
    serializer = CreatePostSerializer(data=request.data)

    if serializer.is_valid():
        serializer.save(user = request.user)

        return Response(serializer.data, status=status.HTTP_201_CREATED)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

DRF Serializer (I'm not using images currently):

class CreatePostSerializer(serializers.ModelSerializer):

    class Meta:
        model = Product
        fields = [
            "id",
            "name",
            "category",
            "description",
            "price"
        ]

Model:

from django.contrib.auth.models import User

class Category(models.Model):
    name    =   models.CharField(max_length=255)
    slug    =   models.SlugField() 

    class Meta: #
        ordering = ('name',) 
    
    def __str__(self): 
        return self.name
    
    def get_absolute_url(self): 
        return  f'/{self.slug}/'
        
class Product(models.Model):
    user = models.ForeignKey(User, related_name='product', on_delete=models.CASCADE)
    category    =   models.ForeignKey(Category, related_name='products', on_delete=models.CASCADE)
    name    =   models.CharField(max_length=255)
    slug    =   models.SlugField()
    description =   models.TextField(blank=True, null=True, max_length=455)
    price   =   models.IntegerField()
    image   =   models.ImageField(upload_to='uploads/', blank=True, null=True)
    thumbnail   =   models.ImageField(upload_to='uploads/', blank=True, null=True)
    date_added  =   models.DateTimeField(auto_now_add=True)

Thanks for reading!

1

1 Answers

0
votes

In the html form I've added enctype="multipart/form-data" so that you can submit image or file too as you're creating a product so there might be product image.

<form @submit.prevent="submitForm" enctype="multipart/form-data">
                    <div class="field">
                        <label>TITLE</label>
                        <div class="control">
                            <input type="text" class="input" v-model="name">
                        </div>
                    </div>

                    <div class="field">
                        <label>DESCRIPTION</label>
                        <div class="control">
                            <input type="text" class="input" v-model="description">
                        </div>
                    </div>

                    <div class="field">
                        <label>PRICE</label>
                        <div class="control">
                            <input type="number" class="input" v-model="price">
                        </div>
                    </div>

                    <div class="field">
                        <label>CATEGORY</label>
                        <select v-model="category">              
                            <option v-for="category in categoryList" :key="category.id" :value="category.id">{{ category.name }}</option>
                        </select>
                    </div>

                    <div class="notification is-danger" v-if="errors.length">
                        <p v-for="error in errors" v-bind:key="error">{{ error }}</p>
                    </div>

                    <div class="field">
                        <div class="control">
                            <button class="button is-dark">CREATE</button>
                        </div>
                    </div>
                </form>

in the script you need to return category as string not an object. Also need to add a header

<script>
    import axios from 'axios'
    import { toast } from 'bulma-toast'
    
    export default {
        name: 'CreatePost',
         data(){
            return{
                name: '',
                description: '',
                price: '',
                category: '',
                errors: [],
                categoryList: []
            }
        },
        mounted() {
            this.getCategoryList()
            document.title = 'Creating post | PLACE'
        },
        methods: {
            getCategoryList() {
                axios
                .get('/api/v1/category-list/')
                .then(response => {
                    this.categoryList = response.data
                })
                .catch(error => {
                    console.log(error)
                })
    
            },
            submitForm(){
                this.errors = []
    
                if (this.name === ''){
                    this.errors.push('Title input is requiered.')
                }
    
                if (this.description === ''){
                    this.errors.push('Description input is requiered.')
                }
    
                if (this.price === ''){
                    this.errors.push('Price input is requiered.')
                }
    
                if (!this.errors.length){
                    const config = {
                        headers: {
                        "content-type": "multipart/form-data",
                        },
                    };
                    let formData = new FormData();
                    formData.append("name", this.name);
                    formData.append("category", this.category);
                    formData.append("price", this.price);
                    formData.append("description", this.description);
    
                    axios
                        .post("/api/v1/create-post/", formData, config)
                        .then(response => {
                            toast({
                                message: 'Post created!',
                                type: 'is-success',
                                dismissible: true,
                                pauseOnHover: true,
                                duration: 2000,
                                position: 'bottom-right',
                            })
    
                            this.$router.push('/my-account')
                        })
                        .catch(error =>{
                            if (error.response){
                                for (const property in error.response.data) {
                                    this.errors.push(`${property}: ${error.response.data[property]}`)
                                }
    
                                console.log(JSON.stringify(error.response.data))
                            }else if (error.message){
                                this.errors.push('Something bad happened...')
                                
                                console.log(JSON.stringify(error))
                            }
                        })
                }
            }
        }
    }
    </script>

It will solve the frontend error.