Fixing the Django Serializer Not null constraint failed Error
I was trying to insert data into the database via a Django Serializer.
The value bound to a ForeignKey was being treated as NULL, so the insert failed.
The usual fix is to allow the field to accept NULL, but here I went a step further and looked at how to keep the ForeignKey from being interpreted as NULL in the first place — and explain why this happens.
[01] Not NULL constraint failed: client_testclient.host_server_id
Reading the error
- The model field is NOT NULL but a NULL value was inserted
- client_testclient.host_server_id
- client = the app name
- testclient = the table name defined in the client app’s
models.py - host_server = the field on testclient
- id = host_server is a ForeignKey referencing another table’s id
The value to be inserted into testclient.host_server is NULL, hence the error.
Common fix — add null=True to the field
- If incoming data can legitimately be
null, modify the table so the field accepts NULL.
1
2
3
4
5
6
7
8
9
10
11
12
# ex) client/models.py
class HostServer(models.Model):
address = models.GenericIPAddressField(unique=True, verbose_name='IP Address on Host SErver')
def __str__(self):
return self.address
class TestClient(models.Model):
address = models.GenericIPAddressField(null=True, verbose_name='Client IP')
# host_server = models.ForeignKey(HostServer, on_delete=models.CASCADE)
host_server = models.ForeignKey(HostServer, null=True, on_delete=models.CASCADE)
1
2
3
4
# ex) Apply the model changes
# Run inside the Django project folder
python manage.py makemigrations
python manage.py migrate
[02] Django Model ForeignKey
- The
host_serverfield on thetestclienttable is a ForeignKey- A field/column on one table references the primary key of another table
- Typically used for one-to-many relationships
- Customer table (one) ↔ Order table (many — one customer can have many orders)
- The customer ID is the primary key on the customer table; the order table holds a ForeignKey referencing it
- Common options
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# Behavior for objects referencing a parent that is deleted # Delete together on_delete=models.CASCADE # Prevent deletion of the parent on_delete=models.PROTECT # Set referencing foreign keys to NULL when the parent is deleted on_delete=models.SET_NULL # Set the foreign key to a specific value when the parent is deleted on_delete=models.SET_DEFAULT # Allow NULL null=True # Disallow duplicates unique=True
- In the structure in [01], the
host_serverfield on TestClient references the ID of HostServer- In Django, even if you don’t define one, an
idfield is auto-generated and used like an index.
- In Django, even if you don’t define one, an
[03] Serializers in Django REST Framework
-
serializers.pyis used by Django REST Framework (DRF)- A module for serializing and deserializing API request and response data
- Serialization converts data structures into formats like JSON or XML
- Deserialization is the reverse
- DRF’s serializer module makes this convenient
- It also validates data when reading from or saving to database models
-
class Meta:- Defines table metadata
- Specifies which model/fields to serialize, read-only fields, and so on
- With
extra_kwargs, you can set defaults, mark fields read-only, etc., so you can create/save DB objects without those fields
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
# models.py from django.db import models class MyModel(models.Model): name = models.CharField(max_length=100) age = models.IntegerField() address = models.CharField(max_length=100) email = models.EmailField() is_active = models.BooleanField(default=True) # serializers.py from rest_framework import serializers from myapp.models import MyModel class MyModelSerializer(serializers.ModelSerializer): class Meta: model = MyModel # The model to serialize fields = '__all__' # Serialize all fields # fields = ['name', 'age'] # Serialize specific fields only read_only_fields = ['age'] # Mark fields as read-only extra_kwargs = { # Extra settings 'name': {'required': True}, # Make the name field required 'address': {'read_only': True}, # Make address read-only 'email': {'write_only': True}, # Make email write-only 'is_active': {'default': False}, # Default is_active to False } # Example usage in views.py data = { 'name': 'John Doe', 'email': 'john@example.com' } serializer = MyModelSerializer(data=data) if serializer.is_valid(): instance = serializer.save() print(instance) else: print(serializer.errors)
- Defines table metadata
-
serializers.SerializerMethodField()- Use when you want to serialize a particular field with custom logic
- Used to define read-only fields
- On a serializer for the
Usermodel, you might add afull_namefield- that combines
first_nameandlast_namefrom the User model
- that combines
- On a serializer for the
- Use the
get_<field_name>(self, obj)method- to define how the
SerializerMethodField()value is computed - the field name part must match the field name declared as
SerializerMethodField
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
''' Add a `full_name` field to the User model's serializer. It combines first_name and last_name from the User model. ''' # serializers.py from rest_framework import serializers from django.contrib.auth.models import User class UserSerializer(serializers.ModelSerializer): full_name = serializers.SerializerMethodField() class Meta: model = User fields = ['username', 'email', 'full_name'] def get_full_name(self, obj): return f'{obj.first_name} {obj.last_name}'
- to define how the
-
serializers.PrimaryKeyRelatedField()- Defines table metadata
- Specifies which model/fields to serialize, read-only fields, and so on
1
# models.py
- Defines table metadata
[04] Why the Not NULL constraint failed error happens when using Serializers
Incorrect TestClientSerializer
- We wanted reading
host_serverto return theaddressfrom HostServer - Reading
host_serverdirectly would return the id stored in HostServer- So we changed it to expose the address for better readability
However, as shown above, if you remap a field via
SerializerMethodField(), that field becomes read-only. So whenviews.pytries to write to it, it errors out (NULL).1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
# client/models.py class HostServer(models.Model): address = models.GenericIPAddressField(unique=True, verbose_name='IP Address on Host SErver') class TestClient(models.Model): address = models.GenericIPAddressField(null=True, verbose_name='Client IP') host_server = models.ForeignKey(HostServer, on_delete=models.CASCADE) # client/serializers.py from rest_framework import serializers from .models import HostServer, Testclient class HostServerSerializer(serializers.ModelSerializer): class Meta: model = HostServer fields = ['address'] class TestClientSerializer(serializers.ModelSerializer): host_server = serializers.SerializerMethodField() class Meta: model = TestClient fields = '__all__' def get_host_server(self, obj): return obj.host_server.address # client/views.py
Fix
- If you want reads to surface the
addressinstead of the id,- it’s better to use the model’s
__str__method - and remove the
SerializerMethodField()code from the serializer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
# client/models.py class HostServer(models.Model): address = models.GenericIPAddressField(unique=True, verbose_name='IP Address on Host SErver') def __str__(self): return self.address class TestClient(models.Model): address = models.GenericIPAddressField(null=True, verbose_name='Client IP') host_server = models.ForeignKey(HostServer, on_delete=models.CASCADE) # client/serializers.py from rest_framework import serializers from .models import HostServer, Testclient class HostServerSerializer(serializers.ModelSerializer): class Meta: model = HostServer fields = ['address'] class TestClientSerializer(serializers.ModelSerializer): class Meta: model = TestClient fields = '__all__'
- it’s better to use the model’s