양파개발자 실바의 블로그

Django APIView Mixins

Django REST Framework에서 사용할 수 있는 유용한 Mixin 클래스들입니다. N+1 쿼리 문제를 해결하는 EagerLoadingMixin과 한글화된 에러 메시지를 제공하는 ModelSerializerMessageMixin을 포함합니다.


1. EagerLoadingMixin

효율적인 쿼리를 날리는데 도움을 주는 Mixin입니다. Django의 N+1 쿼리 현상을 미연에 방지하고자 View 레벨에서부터 관리할 수 있습니다. get_queryset(), serializer_class를 지정하는 APIView 클래스에 적합합니다.

class EagerLoadingMixin:
    """
    get_queryset() 를 사용하는 View, ViewSet 에서 활용
    - 로직에서 다른 모델과의 join 이 필요한 경우, 이를 미리 명시적으로 지정함으로써
    - 쿼리가 효율적으로 동작할 수 있도록 보장합니다.
    - 참고자료: https://www.notion.so/baropharm/select-by-eager-loading-f9327eeef4c64133959f0928421a6183

    <사용 예시>
    class AuthorViewSet(EagerLoadingMixin, viewsets.ModelViewSet):
        queryset = Author.objects.all()
        serializer_class = AuthorSerializer

        # 1. attribute 를 적거나
        queryset_prefetch_related_list = ["books"]

        # 2. 직접 함수를 override
        def setup_eager_loading(self, queryset):
            return queryset.prefetch_related('books')
    """

    queryset_select_related_list = []
    queryset_prefetch_related_list = []

    def setup_eager_loading(self, queryset: QuerySet) -> QuerySet:
        """
        Modify this method to add select_related and prefetch_related
        """
        if self.queryset_select_related_list:
            queryset = queryset.select_related(*self.queryset_select_related_list)

        if self.queryset_prefetch_related_list:
            queryset = queryset.prefetch_related(*self.queryset_prefetch_related_list)

        return queryset

    def get_queryset(self):
        queryset = super().get_queryset()
        return self.setup_eager_loading(queryset)

2. ModelSerializerMessageMixin

DRF ValidationError 응답(=400)으로 한글화된 메시지를 제공할 수 있는 Mixin입니다. Model의 모든 필드에 verbose_name이 세팅되어 있어야 하며, ValidationError의 경우에만 해당됩니다.

class ModelSerializerMessageMixin:
    """
    ModelSerializer 의 한글화 된 오류 메시지 제공해주는 Mixin

    - 전제조건: Model 의 모든 필드에 verbose_name 이 세팅되어있어야 함.
    - ValidationError 의 경우에만 해당됨
    - Model의 verbose_name을 사용하여 "{verbose_name} 입력 오류: "를 추가해줌

    [사용법]
    class YourModelSerializer(ModelSerializerMessageMixin, serializers.ModelSerializer):
        class Meta:
            model = YourModel
            fields = '__all__'  # 또는 필요한 필드만 지정
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.customize_error_messages()

    def customize_error_messages(self):
        for field_name, field in self.fields.items():
            try:
                model_field = self.Meta.model._meta.get_field(field_name)
                verbose_name = model_field.verbose_name

                # 기존 에러 메시지를 복사
                original_messages = field.error_messages.copy()

                # 모든 에러 메시지에 verbose_name 추가
                for key, message in original_messages.items():
                    field.error_messages[key] = _(f"{verbose_name} 입력 오류: {message}")

                # null, blank, required에 대해서는 특별한 메시지 사용
                special_messages = {
                    "null": _(f"{verbose_name} 입력 오류: 필수 입력 항목입니다."),
                    "blank": _(f"{verbose_name} 입력 오류: 필수 입력 항목입니다."),
                    "required": _(f"{verbose_name} 입력 오류: 필수 입력 항목입니다."),
                }
                field.error_messages.update(special_messages)

            except FieldDoesNotExist:  # 유효한 필드가 아닐때 (관련모델필드, SerializerMethodField 등)
                pass
            except Exception as e:
                logger.warning(
                    f"Error customizing messages for field {field_name}: {str(e)}"
                )