How It Works Features Pricing Blog Error Guides
Log In Start Free Trial
Django · Python

Fix ValidationError: {"price": ["A valid number is required."]} in Django

This error means a Django REST Framework serializer rejected the input data during validation. Fix it by ensuring the request data matches the serializer field types, checking that required fields are present, and verifying that custom validate methods return the validated data instead of silently discarding it.

Reading the Stack Trace

Traceback (most recent call last): File "/venv/lib/python3.11/site-packages/rest_framework/views.py", line 509, in dispatch response = self.handle_exception(exc) File "/venv/lib/python3.11/site-packages/rest_framework/views.py", line 469, in handle_exception self.raise_uncaught_exception(exc) File "/venv/lib/python3.11/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception raise exc File "/venv/lib/python3.11/site-packages/rest_framework/views.py", line 506, in dispatch response = handler(request, *args, **kwargs) File "/app/api/views.py", line 18, in create serializer.is_valid(raise_exception=True) File "/venv/lib/python3.11/site-packages/rest_framework/serializers.py", line 235, in is_valid raise ValidationError(self.errors) rest_framework.exceptions.ValidationError: {"price": ["A valid number is required."]}

Here's what each line means:

Common Causes

1. Client sending wrong data type

The API client sends a string or null for a numeric field instead of a valid number.

// Frontend JavaScript
fetch('/api/products/', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name: 'Widget', price: '' }),  // Empty string
});

2. Custom validate method not returning data

A custom validate method runs checks but forgets to return the validated data, causing it to become None.

class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = ['name', 'price', 'category']

    def validate_price(self, value):
        if value <= 0:
            raise serializers.ValidationError('Price must be positive.')
        # Missing: return value

3. Nested serializer missing many=True

A nested serializer receives a list but is not configured with many=True, causing a type mismatch.

class OrderSerializer(serializers.ModelSerializer):
    items = OrderItemSerializer()  # Missing many=True for list data

    class Meta:
        model = Order
        fields = ['id', 'items']

The Fix

Always return the validated value from validate_<field> methods. Add null checks for required fields. Use the validate() method for cross-field validation. DRF silently sets the field to None if validate_<field> does not return a value.

Before (broken)
class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = ['name', 'price', 'category']

    def validate_price(self, value):
        if value <= 0:
            raise serializers.ValidationError('Price must be positive.')
        # Missing return!
After (fixed)
class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = ['name', 'price', 'category']

    def validate_price(self, value):
        if value is None:
            raise serializers.ValidationError('Price is required.')
        if value <= 0:
            raise serializers.ValidationError('Price must be positive.')
        return value  # Always return the validated value

    def validate(self, attrs):
        """Cross-field validation."""
        if attrs.get('category') == 'free' and attrs.get('price', 0) > 0:
            raise serializers.ValidationError('Free products must have a price of 0.')
        return attrs

Testing the Fix

import pytest
from rest_framework.test import APITestCase, APIClient
from products.models import Product


class TestProductSerializer(APITestCase):
    def setUp(self):
        self.client = APIClient()

    def test_create_product_with_valid_data(self):
        response = self.client.post('/api/products/', {
            'name': 'Widget',
            'price': '19.99',
            'category': 'gadgets',
        }, format='json')
        assert response.status_code == 201

    def test_create_product_with_invalid_price(self):
        response = self.client.post('/api/products/', {
            'name': 'Widget',
            'price': 'not-a-number',
            'category': 'gadgets',
        }, format='json')
        assert response.status_code == 400
        assert 'price' in response.json()

    def test_create_product_with_negative_price(self):
        response = self.client.post('/api/products/', {
            'name': 'Widget',
            'price': '-5.00',
            'category': 'gadgets',
        }, format='json')
        assert response.status_code == 400

    def test_create_product_with_empty_price(self):
        response = self.client.post('/api/products/', {
            'name': 'Widget',
            'price': '',
            'category': 'gadgets',
        }, format='json')
        assert response.status_code == 400

Run your tests:

pytest

Pushing Through CI/CD

git checkout -b fix/serializer-validation-return,git add api/serializers.py,git commit -m "fix: return validated values from serializer validate methods",git push origin fix/serializer-validation-return

Your CI config should look something like this:

name: CI
on:
  pull_request:
    branches: [main]
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_DB: test_db
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
        ports:
          - 5432:5432
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
          cache: 'pip'
      - run: pip install -r requirements.txt
      - run: pytest --tb=short -q

The Full Manual Process: 18 Steps

Here's every step you just went through to fix this one bug:

  1. Notice the error alert or see it in your monitoring tool
  2. Open the error dashboard and read the stack trace
  3. Identify the file and line number from the stack trace
  4. Open your IDE and navigate to the file
  5. Read the surrounding code to understand context
  6. Reproduce the error locally
  7. Identify the root cause
  8. Write the fix
  9. Run the test suite locally
  10. Fix any failing tests
  11. Write new tests covering the edge case
  12. Run the full test suite again
  13. Create a new git branch
  14. Commit and push your changes
  15. Open a pull request
  16. Wait for code review
  17. Merge and deploy to production
  18. Monitor production to confirm the error is resolved

Total time: 30-60 minutes. For one bug.

Or Let bugstack Fix It in Under 2 minutes

Every step above? bugstack does it automatically.

Step 1: Install the SDK

pip install bugstack

Step 2: Initialize

import bugstack

bugstack.init(api_key=os.environ["BUGSTACK_API_KEY"])

Step 3: There is no step 3.

bugstack handles everything from here:

  1. Captures the stack trace and request context
  2. Pulls the relevant source files from your GitHub repo
  3. Analyzes the error and understands the code context
  4. Generates a minimal, verified fix
  5. Runs your existing test suite
  6. Pushes through your CI/CD pipeline
  7. Deploys to production (or opens a PR for review)

Time from error to fix deployed: Under 2 minutes.

Human involvement: zero.

Try bugstack Free →

No credit card. 5-minute setup. Cancel anytime.

Deploying the Fix (Manual Path)

  1. Run the full test suite locally to confirm serializer validation.
  2. Open a pull request with the serializer fixes.
  3. Wait for CI checks to pass on the PR.
  4. Have a teammate review and approve the PR.
  5. Merge to main and verify API validation in staging.

Frequently Asked Questions

BugStack runs the fix through your existing test suite, generates tests for valid, invalid, and edge-case inputs for every serializer field, and validates that error responses are properly formatted before marking it safe to deploy.

BugStack never pushes directly to production. Every fix goes through a pull request with full CI checks, so your team can review it before merging.

Pass error_messages to the serializer field, override default_error_messages on a custom field, or raise serializers.ValidationError with your custom message in validate_<field> methods.

Use raise_exception=True in API views so DRF returns a standardized 400 response. Check manually when you need custom error handling logic before returning the response.