Django Serializer Not null constraint failed 오류 해결
Django Serializer로 데이터를 저장할 때 ForeignKey 값이 NULL로 인식되어 Not null constraint failed 오류가 발생합니다. 필드에 null=True를 추가하는 대신, Serializer에서 SerializerMethodField()를 제거해 원인을 없애는 방법을 정리합니다.
[01] Not NULL constraint failed: client_testclient.host_server_id
오류 해석
- Model의 Field가 NOT NULL인데, NULL 값을 입력
- client_testclient.host_server_id
- client = App 이름
- testclient = Client App의 models.py에 작성한 table 명
- host_server = testclient의 필드명
- id = host_server는 ForeignKey로 다른 테이블의 id 값을 연결
testclient의 host_server에 입력할 값이 NULL이어서 오류가 발생한다.
일반적인 해결법 = 해당 필드에 null=True 추가
- 입력 데이터에
null이 들어올 수 있다면, 해당 필드가 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) Model 수정사항 적용
# django 프로젝트 폴더 경로에서 실행
python manage.py makemigrations
python manage.py migrate
[02] Django Model ForeignKey
- testclient 테이블의 host_server 필드는 ForeignKey로 구성한다.
- 한 테이블의 필드가 다른 테이블의 기본 키(Primary Key)를 참조한다.
- 주로 일 대 다(One-to-Many) 관계에서 사용한다.
- 고객 테이블 한 건에 주문 테이블 여러 건이 연결된다.
- 고객 테이블의 고객ID가 기본 키이고, 주문 테이블에는 이 고객ID를 참조하는 ForeignKey를 만든다.
- 주요 옵션
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# 부모 객체가 삭제될 때, 이를 참조하는 모든 객체의 처리 # 함께 삭제 on_delete=models.CASCADE # 부모 객체가 삭제되지 않도록 함 on_delete=models.PROTECT # 부모 객체가 삭제되면 이를 참조하는 외래 키를 Null 설정 on_delete=models.SET_NULL # 부모 객체가 삭제되면 외래 키를 특정 값으로 설정 on_delete=models.SET_DEFAULT # Null 값 허용 null=True # 중복 값 허용하지 않음 unique=True
- [01]의 구조에서는 TestClient 테이블의 host_server 필드가 HostServer의 id 값을 참조한다.
- Django는 모델에 기본 키를 정의하지 않으면
id필드를 자동으로 만들어 기본 키로 사용한다.
- Django는 모델에 기본 키를 정의하지 않으면
[03] Django REST Framework의 Serializers
-
serializers.py는 Django REST Framework(DRF)에서 활용- API 요청과 응답 데이터를 직렬화, 역직렬화하는 모듈
- 직렬화(serialization)는 데이터 구조를 JSON, XML 등 특정 포맷으로 변환하는 작업이다.
- 역직렬화는 그 반대 방향의 변환이다.
- DRF의 serializer 모듈은 이 변환과 함께 데이터 유효성 검사를 수행한다.
-
class Meta:- 직렬화 대상을 지정한다.
- 직렬화할 모델과 필드, 읽기 전용 필드를 지정한다.
-
extra_kwargs로 필드별 필수 여부, 읽기 전용 여부, 쓰기 전용 여부, 기본값을 지정한다.
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 # 직렬화할 모델을 지정 fields = '__all__' # 모든 필드를 직렬화 # fields = ['name', 'age'] # 특정 필드만 직렬화 read_only_fields = ['age'] # 읽기 전용 필드를 지정 extra_kwargs = { # 추가 설정을 지정 'name': {'required': True}, # name 필드를 필수로 설정 'address': {'read_only': True}, # address 필드를 읽기 전용으로 설정 'email': {'write_only': True}, # email 필드를 쓰기 전용으로 설정 'is_active': {'default': False}, # is_active 필드의 기본값을 False로 설정 } # 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)
- 직렬화 대상을 지정한다.
-
serializers.SerializerMethodField()- 특정 필드를 사용자 정의 방식으로 직렬화할 때 사용한다.
- 이 필드는 읽기 전용이다.
- 예를 들어
User모델의 시리얼라이저에full_name필드를 추가하고,first_name과last_name을 결합한 값을 반환하게 만든다.
- 예를 들어
- 값을 반환하는 메서드는
get_필드명(self, obj)형태로 정의한다.- 메서드 이름의 필드명 부분은
SerializerMethodField()로 선언한 필드 이름과 일치해야 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
''' User 모델에 대한 Serializer에 `full_name`이라는 추가 필드를 생성 이 필드는 User모델의 first_name과 last_name을 결합하여 데이터를 생성 ''' # 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}'
- 메서드 이름의 필드명 부분은
-
serializers.PrimaryKeyRelatedField()- 연결된 객체를 기본 키(id) 값으로 직렬화한다.
-
SerializerMethodField()와 달리 쓰기가 가능하므로, ForeignKey 값을 입력받아야 하는 필드에 사용한다.
[04] Serializer를 활용할 때, Not NULL constraint failed 오류 이유
TestClientSerializer의 잘못된 구성
- host_server 필드를 그대로 직렬화하면 HostServer 테이블의 id 값이 출력된다.
-
응답을 읽기 쉽게 만들려고
SerializerMethodField()를 써서 HostServer의 address 값을 반환하도록 구성했다.SerializerMethodField()로 정의한 필드는 읽기 전용이다. views.py에서 이 필드에 값을 입력해도 반영되지 않으므로, 저장 시 host_server가 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
# 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
오류 수정
- 데이터를 읽을 때 id 값 대신 address가 출력되게 하려면 모델에
__str__메서드를 정의한다. -
Serializer에서는
SerializerMethodField()관련 코드를 삭제한다.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__'