ModelViewSet 적용:
MembersViewSet과 AttendanceViewSet을 ModelViewSet으로 변경하여 CRUD 기능을 통합.MembersViewSet은 특정 선생님(로그인한 사용자)의 반에 속한 학생들만 조회, 생성, 수정, 삭제할 수 있도록 구현.AttendanceStatisticsViewSet, TeachersViewSet은 필요한 메서드만 구현하여 ViewSet을 사용.Router 설정:
DefaultRouter()를 사용해 ViewSet에 대한 URL을 자동으로 생성./attendance/api/members/로 접근 가능하도록 라우팅 설정.API URL 패턴 예시:
GET api/members/: 전체 Member 목록 조회.POST api/members/: 새로운 Member 생성.GET api/members/{id}/: 특정 Member 조회.PUT api/members/{id}/: 특정 Member 수정.DELETE api/members/{id}/: 특정 Member 삭제.# api.py
from rest_framework.viewsets import ModelViewSet
from attendance.models import Member
class MembersViewSet(ModelViewSet):
serializer_class = MemberSerializer
# 특정 사용자의 반에 속한 학생만 필터링
def get_queryset(self):
# 특정 사용자 (임시로 "teacher1@example.com")의 반에 속한 학생 조회
user = "teacher1@example.com"
return Member.objects.filter(teacher__email=user)
# urls.py
from rest_framework.routers import DefaultRouter
from django.urls import path, include
from .api import MembersViewSet
router = DefaultRouter()
router.register(r"members", MembersViewSet, basename="members")
urlpatterns = [
path("api/", include(router.urls)),
]
DefaultRouter()의 설정을 수정하여 /attendance/api/members/ 경로에서 API에 접근 가능하도록 처리.basename 관련 오류: basename을 "members"로 지정하여 문제 해결.permission_classes를 AllowAny로 설정하여 인증 없이 테스트 가능하도록 설정함.MembersViewSet에서 IsAuthenticated 권한을 요구할 경우, 인증되지 않아 오류 발생.임시로 권한을 비활성화하여 테스트 진행:
permission_classes = [AllowAny]를 사용하여 임시로 인증 없이도 API에 접근할 수 있도록 설정.코드 수정 예시:
IsAuthenticated 대신 AllowAny로 설정하여 인증 없이도 모든 사용자에게 접근 권한을 허용.# api.py
from rest_framework.permissions import AllowAny
from rest_framework.viewsets import ModelViewSet
from attendance.models import Member
class MembersViewSet(ModelViewSet):
serializer_class = MemberSerializer
permission_classes = [AllowAny] # 인증이 필요하지 않도록 설정
# 특정 사용자의 반에 속한 학생만 필터링
def get_queryset(self):
user = "teacher1@example.com" # 임시로 특정 사용자 지정
return Member.objects.filter(teacher__email=user)
permission_classes 설정이 잘못된 경우 발생한 오류. AllowAny로 설정하여 해결함.앱별 URL 접두어 설정:
attendance 앱의 경우 attendance/api/가 아닌 api/로만 접근하려는 요구가 있었으나, 일반적인 방식으로는 접두어를 유지하는 것이 바람직함.프로젝트 전체 URL 설정:
attendance, report, accounts와 같은 각 앱별로 URL을 관리할 수 있도록 프로젝트의 urls.py에서 각 앱의 URL을 include()로 관리.# 프로젝트 urls.py
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("api/", include("attendance.urls")), # attendance API 접두어 유지
path("report/", include("report.urls")),
path("accounts/", include("accounts.urls")),
]
개요
데코레이터
@csrf_exempt: CSRF 검사를 비활성화하는 데코레이터. 주로 API에서 CSRF 검사가 필요 없는 경우 사용.@method_decorator: 클래스 기반 뷰에서 특정 메서드에 데코레이터를 적용하는 데 사용.목적
프로젝트 예시
@method_decorator(csrf_exempt, name="dispatch")
class MembersViewSet(ModelViewSet):
serializer_class = MemberSerializer
그 밖의 여러 종류의 데코레이터
개요
class MembersViewSet(ModelViewSet)의 GET, POST, PATCH, DELETE 요청에 대한 처리설명:
ModelViewSet은 기본적으로 모든 CRUD(Create, Read, Update, Delete) 작업을 처리한다. 하지만 각 요청마다 반환해야 하는 필드가 다르다면, 이를 Serializer에서 처리하거나 ViewSet의 특정 메서드를 커스텀하여 처리하는 것이 가능하다.fields를 동적으로 변경하거나, 각 요청 메서드별로 get_serializer_class 메서드를 커스텀할 수 있다.예시:
class MembersViewSet(ModelViewSet):
queryset = Member.objects.all()
def get_serializer_class(self):
if self.action == 'create':
return CreateMemberSerializer # POST 요청에 대한 Serializer
elif self.action == 'update':
return UpdateMemberSerializer # PATCH 요청에 대한 Serializer
return MemberSerializer # 기본 GET 요청에 대한 Serializer
이전의 ModelViewSet은, 기본적으로 GET, POST, PATCH, DELETE 요청에 대해 하나의 serializer로 일관된 처리를 하고 있다. 하지만 각각의 HTTP 메서드(GET, POST, PATCH, DELETE)에서 요청하는 데이터(필드)가 다르다면, 각 요청에따른 필드를 처리하도록 커스텀 해야한다.
즉, 꼭 API를 나누지 않고도 하나의 ViewSet에서 요청 메서드에 맞게 적절한 처리를 할 수 있다.
DRF에서는 get_serializer_class() 메서드를 오버라이드하여 요청 메서드에 따라 다른 serializer를 사용할 수 있습니다.
예를 들어, GET 요청에서는 일부 필드만 반환하고, POST 요청에서는 모든 필드를 받도록 설정할 수 있습니다.
class MembersViewSet(ModelViewSet):
# 기본적으로 모든 메서드에서 사용할 수 있는 serializer class
serializer_class = MemberSerializer
# 로그인한 사용자의 반에 속한 학생만 필터링
def get_queryset(self):
user = "teacher1@example.com" # 임시 사용자 지정
return Member.objects.filter(teacher__email=user)
# 요청 메서드에 따라 다른 serializer 반환
def get_serializer_class(self):
if self.action == 'list' or self.action == 'retrieve':
# GET 요청에 대해서는 기본 필드만 반환
return MemberSerializer # 간략한 필드만 반환하는 serializer
elif self.action == 'create' or self.action == 'update':
# POST, PATCH 요청에 대해서는 모든 필드를 받아야 함
return FullMemberSerializer # 모든 필드를 처리하는 serializer
return super().get_serializer_class()
required 옵션을 활용모든 요청에서 동일한 serializer를 사용하고 싶다면, 각 필드에 대해 required 옵션을 사용하여 POST 요청에서는 필드가 필수이고, PATCH 요청에서는 선택적으로 받을 수 있게 설정할 수 있다.
class FullMemberSerializer(serializers.ModelSerializer):
class Meta:
model = Member
fields = ['id', 'name', 'grade', 'gender', 'attendance_count', 'absent_count', 'teacher']
extra_kwargs = {
'grade': {'required': True}, # POST 시 필수
'gender': {'required': True}, # POST 시 필수
'attendance_count': {'required': False}, # 선택 사항
'absent_count': {'required': False}, # 선택 사항
}
serializer 안에서 fields 속성을 동적으로 변경할 수도 있습니다.
class DynamicMemberSerializer(serializers.ModelSerializer):
class Meta:
model = Member
fields = ['id', 'name', 'grade', 'gender', 'attendance_count', 'absent_count', 'teacher']
def __init__(self, *args, **kwargs):
super(DynamicMemberSerializer, self).__init__(*args, **kwargs)
request = self.context.get('request')
if request and request.method == 'GET':
# GET 요청 시 'name'만 반환
self.fields = ['id', 'name']
elif request and request.method in ['POST', 'PATCH']:
# POST, PATCH 요청 시 모든 필드를 요구
self.fields = ['id', 'name', 'grade', 'gender', 'attendance_count', 'absent_count', 'teacher']
ModelSerializer는 Django REST Framework(DRF)에서 기본 Serializer 클래스보다 더 간편하고 효율적인 방법으로 Django 모델을 직렬화하기 위해 제공되는 클래스이다. ModelSerializer는 기존 Serializer에 비해 다음과 같은 장점이 있다.
ModelSerializer는 Django 모델과 직접 연결되어 있으며, 모델의 필드들을 자동으로 직렬화하고 역직렬화할 수 있다. 이 때문에 수동으로 필드를 정의할 필요가 없다.
# 예시: ModelSerializer 사용
class MemberSerializer(serializers.ModelSerializer):
class Meta:
model = Member # 모델 연결
fields = ['id', 'name', 'grade', 'gender'] # 모델 필드를 자동으로 직렬화
이와 비교하여, 일반 Serializer를 사용하면 모든 필드를 수동으로 지정해야한다.
# 예시: 일반 Serializer 사용
class MemberSerializer(serializers.Serializer):
id = serializers.IntegerField()
name = serializers.CharField(max_length=100)
grade = serializers.CharField(max_length=10)
gender = serializers.CharField(max_length=10)
ModelSerializer는 모델에서 정의된 필드 유형에 따라 자동으로 유효성 검사를 수행한다. 예를 들어, 모델에서 CharField로 정의된 필드에 빈 값을 입력하거나 IntegerField에 문자열을 입력하면, 기본적으로 자동으로 검증을 수행하고 오류 메시지를 반환한다.
ModelSerializer는 create() 및 update() 메서드를 자동으로 제공하여 모델 인스턴스를 생성하거나 업데이트하는 로직을 쉽게 구현할 수 있다. 직접 create() 및 update() 메서드를 작성할 필요가 없다.
class MemberSerializer(serializers.ModelSerializer):
class Meta:
model = Member
fields = "__all__"
자동으로 POST 요청에서 create() 메서드가 호출되고, PUT 요청에서 update() 메서드가 호출됩니다.
일반 Serializer에서는 create()와 update() 메서드를 수동으로 작성해야한다.
class MemberSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=100)
grade = serializers.CharField(max_length=10)
gender = serializers.CharField(max_length=10)
def create(self, validated_data):
return Member.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name)
instance.grade = validated_data.get('grade', instance.grade)
instance.gender = validated_data.get('gender', instance.gender)
instance.save()
return instance
ModelSerializer는 필드, 모델 및 관련된 옵션을 쉽게 설정할 수 있는 여러 기본 옵션을 제공한다.. fields, exclude, extra_kwargs와 같은 옵션을 통해 필드를 간편하게 조정할 수 있다.
class MemberSerializer(serializers.ModelSerializer):
class Meta:
model = Member
fields = ['id', 'name'] # 선택된 필드만 직렬화
extra_kwargs = {
'name': {'required': True}, # 추가 옵션 설정 가능
}
ModelSerializer는 Django 모델과 밀접하게 연동되어 있으므로, 이미 정의된 모델을 기반으로 직렬화 로직을 작성하는 데 재사용성이 높다. 모델의 변경 사항이 발생해도 ModelSerializer는 자동으로 그 변경 사항을 반영할 수 있습니다.
ModelSerializer는 외래 키 관계 또는 다대다 관계 등 모델 간의 관계를 직렬화할 때 유리합니다. 관계된 모델도 쉽게 직렬화할 수 있습니다.
class MemberSerializer(serializers.ModelSerializer):
teacher = TeacherSerializer() # 관계된 모델의 직렬화
class Meta:
model = Member
fields = ['id', 'name', 'teacher'] # 관계된 필드도 자동 직렬화
ModelSerializer를 사용하는 동안에도 커스텀 유효성 검사나 비즈니스 로직을 추가할 수 있습니다. 기본 제공 기능을 확장해 다양한 요구사항을 충족할 수 있습니다.
class MemberSerializer(serializers.ModelSerializer):
class Meta:
model = Member
fields = ['id', 'name', 'grade']
def validate_grade(self, value):
if value not in ['1', '2', '3']:
raise serializers.ValidationError("학년은 1, 2, 3 중 하나여야 합니다.")
return value
오류
IntegrityError at /attendance/api/members/2/ 발생: FOREIGN KEY constraint failed개요
원인:
attendance_attendance 테이블의 name_id 필드가 attendance_member 테이블에 존재하지 않는 값을 참조하고 있기 때문에 오류가 발생.해결 과정:
1. 문제를 일으키는 외래 키 필드를 가진 데이터 삭제 또는 수정.
2. 관련된 테이블의 외래 키 필드를 정확하게 매핑.
코드
# 문제 있는 데이터 조회
SELECT * FROM attendance_attendance WHERE name_id NOT IN (SELECT id FROM attendance_member);
# 조회된 문제 데이터 삭제
DELETE FROM attendance_attendance WHERE name_id = <문제 ID>;