ViewSet과 ModelViewSet

김의석 ·2024년 9월 13일

Django

목록 보기
39/39

1. DRF ViewSet과 ModelViewSet 적용

문제 발생

  • 기존 APIView 클래스를 사용한 API 설계를 ViewSet과 ModelViewSet으로 변경하여 API 구조를 간소화하고자 함.
  • 모든 CRUD 작업이 필요한 경우와 특정 기능만 필요한 경우를 구분하여 ViewSet, ModelViewSet을 적용할 필요가 있음.

해결 과정

  1. ModelViewSet 적용:

    • MembersViewSetAttendanceViewSetModelViewSet으로 변경하여 CRUD 기능을 통합.
    • MembersViewSet은 특정 선생님(로그인한 사용자)의 반에 속한 학생들만 조회, 생성, 수정, 삭제할 수 있도록 구현.
    • AttendanceStatisticsViewSet, TeachersViewSet은 필요한 메서드만 구현하여 ViewSet을 사용.
  2. Router 설정:

    • DefaultRouter()를 사용해 ViewSet에 대한 URL을 자동으로 생성.
    • API 경로가 /attendance/api/members/로 접근 가능하도록 라우팅 설정.
  3. 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)),
]

이슈 및 해결 방법

  • 404 오류: URL 경로 문제를 해결하기 위해 DefaultRouter()의 설정을 수정하여 /attendance/api/members/ 경로에서 API에 접근 가능하도록 처리.
  • basename 관련 오류: basename"members"로 지정하여 문제 해결.
  • 로그인 없이 테스트하려는 경우: permission_classesAllowAny로 설정하여 인증 없이 테스트 가능하도록 설정함.

2. Permission 관련 이슈 해결

문제 발생

  • 로그인 기능이 구현되지 않은 상태에서 MembersViewSet에서 IsAuthenticated 권한을 요구할 경우, 인증되지 않아 오류 발생.

해결 과정

  1. 임시로 권한을 비활성화하여 테스트 진행:

    • 로그인 기능이 구현되지 않았으므로 permission_classes = [AllowAny]를 사용하여 임시로 인증 없이도 API에 접근할 수 있도록 설정.
  2. 코드 수정 예시:

    • 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)

이슈 및 해결 방법

  • 'BasePermissionMetaclass' object is not iterable 오류: 인증이 필요한 permission_classes 설정이 잘못된 경우 발생한 오류. AllowAny로 설정하여 해결함.

3. URL 설정 및 앱별 접두어 관리

문제 발생

  • 프로젝트에서 앱별로 접두어를 설정하여 URL을 관리.

해결 과정

  1. 앱별 URL 접두어 설정:

    • 각 앱에 고유한 URL 접두어를 사용하여 명확한 URL 구조를 유지할 수 있도록 설정.
    • 예를 들어 attendance 앱의 경우 attendance/api/가 아닌 api/로만 접근하려는 요구가 있었으나, 일반적인 방식으로는 접두어를 유지하는 것이 바람직함.
  2. 프로젝트 전체 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")),
]

이슈 및 해결 방법

  • 앱별 접두어 사용: 각 앱에 고유한 접두어를 설정하여 URL을 명확히 관리하고, 이후 요구에 따라 접두어를 수정할 수 있도록 프로젝트 전체 구조를 관리함.

4. 데코레이터 (Decorator) 사용

개요

  • 데코레이터의 사용목적에 대해 정리

데코레이터

  • 데코레이터는 함수 또는 클래스에 추가적인 기능을 쉽게 부여할 수 있는 Python의 문법. Django에서는 데코레이터를 사용하여 뷰에 다양한 기능을 추가할 수 있습니다.
  • Django에서 자주 사용되는 데코레이터:
    • @csrf_exempt: CSRF 검사를 비활성화하는 데코레이터. 주로 API에서 CSRF 검사가 필요 없는 경우 사용.
    • @method_decorator: 클래스 기반 뷰에서 특정 메서드에 데코레이터를 적용하는 데 사용.

목적

  • 데코레이터를 사용하면 기존의 함수를 수정하지 않고, 그 함수에 새로운 기능을 추가할 수 있다. 이를 통해 코드의 가독성, 유지보수성이 향상되며, 반복적인 코드를 줄일 수 있다.

프로젝트 예시

@method_decorator(csrf_exempt, name="dispatch")
class MembersViewSet(ModelViewSet):
    serializer_class = MemberSerializer

그 밖의 여러 종류의 데코레이터


5. ModelViewSet에서 각 요청에 대한 필드 처리

개요

  • class MembersViewSet(ModelViewSet)GET, POST, PATCH, DELETE 요청에 대한 처리

설명:

  • ModelViewSet은 기본적으로 모든 CRUD(Create, Read, Update, Delete) 작업을 처리한다. 하지만 각 요청마다 반환해야 하는 필드가 다르다면, 이를 Serializer에서 처리하거나 ViewSet의 특정 메서드를 커스텀하여 처리하는 것이 가능하다.
  • Serializer에서 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

6. Serializer에서 각 요청에 대한 필드 처리

이전의 ModelViewSet은, 기본적으로 GET, POST, PATCH, DELETE 요청에 대해 하나의 serializer로 일관된 처리를 하고 있다. 하지만 각각의 HTTP 메서드(GET, POST, PATCH, DELETE)에서 요청하는 데이터(필드)가 다르다면, 각 요청에따른 필드를 처리하도록 커스텀 해야한다.

즉, 꼭 API를 나누지 않고도 하나의 ViewSet에서 요청 메서드에 맞게 적절한 처리를 할 수 있다.

1) 메서드에 따라 다른 Serializer 사용

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()

2) 하나의 Serializer에서 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},  # 선택 사항
        }

3) HTTP 메서드에 따라 필드 선택

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']

7. ModelSerializer에서 각 요청에 대한 필드 처리

ModelSerializer는 Django REST Framework(DRF)에서 기본 Serializer 클래스보다 더 간편하고 효율적인 방법으로 Django 모델을 직렬화하기 위해 제공되는 클래스이다. ModelSerializer는 기존 Serializer에 비해 다음과 같은 장점이 있다.

1) 자동 필드 생성

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)

2) 자동 유효성 검사

ModelSerializer는 모델에서 정의된 필드 유형에 따라 자동으로 유효성 검사를 수행한다. 예를 들어, 모델에서 CharField로 정의된 필드에 빈 값을 입력하거나 IntegerField에 문자열을 입력하면, 기본적으로 자동으로 검증을 수행하고 오류 메시지를 반환한다.

3) 자동 생성 및 업데이트 로직

ModelSerializercreate()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

4) 기본 옵션 제공

ModelSerializer는 필드, 모델 및 관련된 옵션을 쉽게 설정할 수 있는 여러 기본 옵션을 제공한다.. fields, exclude, extra_kwargs와 같은 옵션을 통해 필드를 간편하게 조정할 수 있다.

class MemberSerializer(serializers.ModelSerializer):
    class Meta:
        model = Member
        fields = ['id', 'name']  # 선택된 필드만 직렬화
        extra_kwargs = {
            'name': {'required': True},  # 추가 옵션 설정 가능
        }

5) 코드 재사용성

ModelSerializer는 Django 모델과 밀접하게 연동되어 있으므로, 이미 정의된 모델을 기반으로 직렬화 로직을 작성하는 데 재사용성이 높다. 모델의 변경 사항이 발생해도 ModelSerializer는 자동으로 그 변경 사항을 반영할 수 있습니다.

6) 관계 모델의 직렬화 지원

ModelSerializer는 외래 키 관계 또는 다대다 관계 등 모델 간의 관계를 직렬화할 때 유리합니다. 관계된 모델도 쉽게 직렬화할 수 있습니다.

class MemberSerializer(serializers.ModelSerializer):
    teacher = TeacherSerializer()  # 관계된 모델의 직렬화

    class Meta:
        model = Member
        fields = ['id', 'name', 'teacher']  # 관계된 필드도 자동 직렬화

7) 추가적인 맞춤형 로직 포함 가능

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

8. FOREIGN KEY constraint failed 오류

오류

  • IntegrityError at /attendance/api/members/2/ 발생: FOREIGN KEY constraint failed

개요

  • 데이터베이스의 외래 키(Foreign Key) 제약 조건에 위배되는 문제가 발생할 때 발생. 즉, 외래 키 필드에 지정된 값이 참조하는 테이블에 존재하지 않을 때 발생한다.

원인:

  • 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>;

profile
널리 이롭게

0개의 댓글