Django REST Framework (DRF)는 API 개발을 위한 다양한 강력한 기능을 내장하고 있습니다. 이 글에서는 API의 안정성과 데이터 처리 효율성을 높이는 중요한 기능인 요청 제한(Throttling), 데이터 직렬화(Serialization), 뷰(Views) 활용, 그리고 조건 검색(Conditional Search) 구현 방법에 대해 상세히 살펴보겠습니다.
1. 요청 제한 (Throttling)
요청 제한은 특정 사용자의 API 접근 빈도를 제어하여 과도한 요청으로 인한 서버 부하를 방지하거나, 서비스 오남용(예: 무작위 인증 코드 발송)을 막는 데 사용됩니다. 일반적으로 익명 사용자는 IP 주소로, 로그인한 사용자는 사용자 ID를 기준으로 식별합니다.
DRF에서 요청 제한을 사용하려면, 먼저 Django의 캐시 시스템을 설정해야 합니다. Redis를 캐시 백엔드로 사용하는 것이 일반적입니다.
pip install django-redis
settings.py에 캐시 설정을 추가합니다.
# settings.py
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1", # Redis 인스턴스 주소
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"PASSWORD": "your_redis_password", # Redis 비밀번호 (없다면 제거)
}
}
}
이제 사용자 정의 요청 제한 클래스를 생성합니다. 이 예시에서는 인증된 사용자와 익명 사용자에 대해 다른 제한을 적용합니다.
# app_common/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import exceptions, status
from rest_framework.throttling import SimpleRateThrottle
from django.core.cache import cache as default_cache
class CustomThrottleException(exceptions.APIException):
"""
요청 제한 초과 시 발생하는 커스텀 예외
"""
status_code = status.HTTP_429_TOO_MANY_REQUESTS
default_code = 'too_many_requests'
class CustomRequestThrottle(SimpleRateThrottle):
"""
사용자 또는 IP 주소 기반의 요청 제한 정책
"""
cache = default_cache
scope = "global_user_rate" # 캐시 키를 구성하는 범위
# 요청 제한율 설정: 익명 사용자는 분당 5회, 인증된 사용자는 시간당 60회
THROTTLE_RATES = {
"global_user_rate": "10/m", # 예시로 전체 사용자에 대해 분당 10회
"anonymous_rate": "5/m", # 익명 사용자는 분당 5회
"authenticated_rate": "60/h" # 인증된 사용자는 시간당 60회
}
def get_cache_key(self, request, view):
if request.user and request.user.is_authenticated:
# 인증된 사용자의 경우 사용자 ID를 식별자로 사용
self.scope = "authenticated_rate"
ident = request.user.pk
else:
# 익명 사용자의 경우 IP 주소를 식별자로 사용
self.scope = "anonymous_rate"
ident = self.get_ident(request)
# 캐시 키 형식: 'throttle_%(scope)s_%(ident)s'
return self.cache_format % {'scope': self.scope, 'ident': ident}
def throttle_failure(self):
wait_time = self.wait()
detail_message = {
"error_code": 429,
"message": "요청 빈도 제한 초과",
"detail": f"{int(wait_time)}초 후에 다시 시도해주세요."
}
raise CustomThrottleException(detail_message)
class ProductListAPIView(APIView):
"""
상품 목록을 제공하는 API 뷰 (요청 제한 적용)
"""
throttle_classes = [CustomRequestThrottle, ]
def get(self, request):
# 실제 상품 데이터 조회 및 반환 로직
return Response({"status": "success", "data": "상품 목록 데이터..."})
def post(self, request):
# 상품 생성 로직
return Response({"status": "success", "message": "상품이 성공적으로 생성되었습니다."})
urls.py에 뷰를 연결합니다.
# config/urls.py
from django.urls import path
from app_common import views
urlpatterns = [
path('api/products/', views.ProductListAPIView.as_view()),
]
전역 요청 제한 설정
모든 뷰에 동일한 요청 제한 정책을 적용하려면 settings.py에 전역 설정을 추가할 수 있습니다.
# settings.py
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": [
"app_common.views.CustomRequestThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"anonymous_rate": "5/m",
"authenticated_rate": "60/h"
}
}
2. 직렬화 (Serialization)
DRF의 직렬화기는 크게 두 가지 핵심 기능을 수행합니다. 첫째, 클라이언트로부터 받은 요청 데이터를 유효성 검사합니다. 둘째, 데이터베이스 객체(쿼리셋 또는 단일 객체)를 JSON 또는 XML과 같은 응답 가능한 형식으로 변환합니다.
이 기능들을 보여주기 위해 다음 모델들을 사용하겠습니다.
# app_common/models.py
from django.db import models
class Department(models.Model):
name = models.CharField(verbose_name="부서명", max_length=50, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Membership(models.Model):
LEVEL_CHOICES = (
(1, "브론즈"),
(2, "실버"),
(3, "골드"),
(4, "플래티넘"),
)
level_code = models.IntegerField(verbose_name="회원 등급", choices=LEVEL_CHOICES, default=1)
description = models.TextField(verbose_name="등급 설명", blank=True, null=True)
def __str__(self):
return self.get_level_code_display()
class UserProfile(models.Model):
user_name = models.CharField(verbose_name="사용자 이름", max_length=100, unique=True)
email_address = models.EmailField(verbose_name="이메일 주소", max_length=100, blank=True, null=True)
user_age = models.IntegerField(verbose_name="나이", default=0)
is_active = models.BooleanField(verbose_name="활성 여부", default=True)
department = models.ForeignKey(Department, verbose_name="소속 부서", on_delete=models.SET_NULL, null=True, blank=True)
membership_info = models.OneToOneField(Membership, verbose_name="회원 정보", on_delete=models.SET_NULL, null=True, blank=True)
def __str__(self):
return self.user_name
2.1. 데이터 유효성 검사 및 객체 생성
DRF 직렬화기는 Django의 Form과 ModelForm과 유사하게 동작하여, 들어오는 데이터의 유효성을 검사하고 데이터베이스 객체를 생성/업데이트합니다.
# app_common/serializers.py
from rest_framework import serializers
from app_common import models
class DepartmentSerializer(serializers.ModelSerializer):
class Meta:
model = models.Department
fields = ['id', 'name']
class MembershipSerializer(serializers.ModelSerializer):
class Meta:
model = models.Membership
fields = ['id', 'level_code', 'description']
class UserProfileCreateSerializer(serializers.ModelSerializer):
# 중첩된 직렬화기를 읽기 전용으로 설정하여 데이터 생성을 수동으로 처리
department = DepartmentSerializer(read_only=True)
membership_info = MembershipSerializer(read_only=True)
class Meta:
model = models.UserProfile
fields = ['user_name', 'email_address', 'user_age', 'is_active', 'department', 'membership_info']
extra_kwargs = {
'user_age': {'min_value': 0, 'required': False},
'email_address': {'required': False},
}
def validate_user_name(self, value):
if models.UserProfile.objects.filter(user_name=value).exists():
raise serializers.ValidationError("이미 존재하는 사용자 이름입니다.")
return value
def create(self, validated_data):
# department 및 membership_info를 직접 받아서 처리
department_id = self.context.get('department_id') # 뷰에서 전달받을 ID
membership_id = self.context.get('membership_id')
user_profile = models.UserProfile.objects.create(
department_id=department_id,
membership_info_id=membership_id,
**validated_data
)
return user_profile
위 UserProfileCreateSerializer는 user_name에 대한 커스텀 유효성 검사를 포함하며, create 메서드를 오버라이드하여 중첩된 관계 필드를 처리합니다.
2.2. 객체 직렬화
데이터베이스에서 조회한 객체나 쿼리셋을 클라이언트에 전송 가능한 JSON 형식으로 변환합니다. 커스텀 필드를 추가하거나 중첩된 관계를 표현할 수 있습니다.
# app_common/serializers.py (이어지는 코드)
class UserProfileDetailSerializer(serializers.ModelSerializer):
# 읽기 전용 필드로 Membership의 level_code_display 값을 가져옴
membership_level = serializers.CharField(source='membership_info.get_level_code_display', read_only=True)
# 중첩된 직렬화기로 Department 객체 전체를 포함
department_details = DepartmentSerializer(source='department', read_only=True)
# 커스텀 필드 추가
is_adult = serializers.SerializerMethodField()
class Meta:
model = models.UserProfile
fields = ['id', 'user_name', 'email_address', 'user_age', 'is_active',
'membership_level', 'department_details', 'is_adult']
def get_is_adult(self, obj):
""" 나이가 19세 이상이면 성인으로 간주 """
return obj.user_age >= 19
2.3. 유효성 검사 및 직렬화 통합
하나의 직렬화 클래스로 데이터 유효성 검사와 객체 직렬화를 모두 처리할 수 있습니다. 특히 복잡한 관계를 가진 모델에서 유용합니다.
# app_common/views.py (이어지는 코드)
from rest_framework import generics
from app_common.models import UserProfile, Department, Membership
from app_common.serializers import UserProfileCreateSerializer, UserProfileDetailSerializer
class UserProfileListCreateAPIView(APIView):
"""
사용자 프로필 목록 조회 및 신규 프로필 생성
"""
def get(self, request):
profiles = UserProfile.objects.all()
serializer = UserProfileDetailSerializer(profiles, many=True)
return Response(serializer.data)
def post(self, request):
# 부서와 회원 정보 ID를 요청 데이터에서 추출하거나 기본값 설정
department_id = request.data.get('department_id', None)
membership_id = request.data.get('membership_id', None)
serializer = UserProfileCreateSerializer(
data=request.data,
context={'department_id': department_id, 'membership_id': membership_id}
)
if serializer.is_valid(raise_exception=True):
# `perform_create`를 호출하여 객체 저장. 이때 `create` 메서드가 컨텍스트 데이터를 활용
user_profile = serializer.save()
# 저장된 객체를 UserProfileDetailSerializer로 다시 직렬화하여 상세 정보 반환
return Response(UserProfileDetailSerializer(user_profile).data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) # 이 줄은 raise_exception=True 때문에 일반적으로 실행되지 않음
# config/urls.py (업데이트)
urlpatterns += [
path('api/user-profiles/', views.UserProfileListCreateAPIView.as_view()),
]
3. 뷰 (Views)
DRF는 API 개발을 위한 다양한 수준의 뷰 클래스를 제공합니다. 점진적으로 추상화된 이 클래스들은 개발자가 반복적인 코드 작성을 줄이고 비즈니스 로직에 집중할 수 있도록 돕습니다.
3.1. APIView
django.views.View의 확장 버전으로, DRF의 가장 기본적인 뷰 클래스입니다. APIView는 CSRF 보호 면제, 요청/응답 객체 캡슐화, 인증, 권한, 요청 제한 등 DRF의 핵심 기능을 제공합니다. 데이터베이스 상호작용 없이 단순히 요청을 받아 응답을 반환하는 API에 적합합니다.
# app_common/views.py (이어지는 코드)
class SimpleMessageAPIView(APIView):
"""
간단한 메시지를 반환하는 API 뷰
"""
def get(self, request):
return Response({"message": "환영합니다! DRF API입니다."})
def post(self, request):
data = request.data.get('input_message', '입력된 메시지 없음')
return Response({"received_message": data, "status": "success"})
# config/urls.py (업데이트)
urlpatterns += [
path('api/message/', SimpleMessageAPIView.as_view()),
]
3.2. GenericAPIView
APIView를 상속받아 데이터베이스 모델과의 상호작용을 위한 추가 기능을 제공합니다. queryset, serializer_class, lookup_field 등의 속성을 정의하여 객체 목록 조회, 개별 객체 조회 등의 공통 로직을 간소화합니다. 주로 믹스인(Mixins)과 함께 사용되어 CRUD 작업을 쉽게 구현할 수 있도록 하는 '중간자' 역할을 합니다.
# app_common/views.py (이어지는 코드)
from rest_framework.generics import GenericAPIView
from app_common.serializers import UserProfileDetailSerializer
class UserProfileGenericAPIView(GenericAPIView):
"""
UserProfile 모델에 대한 GenericAPIView 예시
"""
queryset = UserProfile.objects.all()
serializer_class = UserProfileDetailSerializer
def get(self, request, *args, **kwargs):
# get_queryset()을 통해 필터링된 쿼리셋을 가져옴
profiles = self.get_queryset()
serializer = self.get_serializer(profiles, many=True)
return Response(serializer.data)
# config/urls.py (업데이트)
urlpatterns += [
path('api/generic-profiles/', UserProfileGenericAPIView.as_view()),
]
3.3. GenericViewSet
GenericAPIView와 ViewSetMixin을 결합한 클래스입니다. ViewSetMixin은 HTTP 메서드(GET, POST 등)를 list, create, retrieve, update, partial_update, destroy와 같은 액션 메서드에 매핑하여 뷰셋의 기능을 제공합니다. 이를 통해 하나의 뷰셋 클래스로 여러 URL 패턴을 처리할 수 있습니다.
# app_common/views.py (이어지는 코드)
from rest_framework.viewsets import GenericViewSet
class UserProfileGenericViewSet(GenericViewSet):
"""
UserProfile 모델에 대한 GenericViewSet 예시
"""
queryset = UserProfile.objects.filter(is_active=True)
serializer_class = UserProfileDetailSerializer
def list(self, request):
# 'list' 액션은 GET 요청에 매핑
profiles = self.get_queryset()
serializer = self.get_serializer(profiles, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
# 'retrieve' 액션은 GET 요청 (단일 객체)에 매핑
profile = self.get_object() # pk를 사용하여 객체 조회
serializer = self.get_serializer(profile)
return Response(serializer.data)
# config/urls.py (업데이트)
urlpatterns += [
path('api/viewsets/profiles/', UserProfileGenericViewSet.as_view({'get': 'list'})),
path('api/viewsets/profiles/<int:pk>/', UserProfileGenericViewSet.as_view({'get': 'retrieve'})),
]
3.4. 믹스인(Mixins) 및 ModelViewSet
DRF는 CreateModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin과 같은 믹스인 클래스를 제공합니다. 이 믹스인들을 GenericViewSet과 조합하여 필요한 CRUD 기능을 쉽게 추가할 수 있습니다.
가장 많이 사용되는 뷰셋은 이러한 믹스인들을 모두 포함하는 ModelViewSet입니다. ModelViewSet을 사용하면 단일 클래스로 모델의 모든 CRUD 작업을 처리하는 API를 빠르게 구축할 수 있습니다.
# app_common/views.py (이어지는 코드)
from rest_framework import viewsets, mixins
from app_common.serializers import UserProfileCreateSerializer # 생성 및 업데이트용
from app_common.serializers import UserProfileDetailSerializer # 조회용
class FullUserProfileViewSet(viewsets.ModelViewSet):
"""
UserProfile 모델에 대한 전체 CRUD 기능을 제공하는 ViewSet
"""
queryset = UserProfile.objects.all()
# HTTP 메서드에 따라 다른 직렬화기를 사용하려면 get_serializer_class 오버라이드
def get_serializer_class(self):
if self.action in ['create', 'update', 'partial_update']:
return UserProfileCreateSerializer
return UserProfileDetailSerializer
def perform_create(self, serializer):
# 객체 생성 전 추가 로직 (예: 기본 부서/회원 정보 설정)
department_obj = Department.objects.first() # 예시: 첫 번째 부서
membership_obj = Membership.objects.get(level_code=1) # 예시: 브론즈 회원
serializer.save(department=department_obj, membership_info=membership_obj, is_active=True)
def perform_update(self, serializer):
# 객체 업데이트 전 추가 로직
serializer.save()
def perform_destroy(self, instance):
# 객체 삭제 전 추가 로직 (예: is_active 필드만 비활성화)
instance.is_active = False
instance.save()
# instance.delete() # 실제 삭제를 원하면 이 줄 사용
# config/urls.py (업데이트)
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'full-profiles', FullUserProfileViewSet) # 'full-profiles' 경로로 CRUD 자동 매핑
urlpatterns += router.urls
뷰 선택 가이드:
- 데이터베이스와 무관한 단순 로직 API:
APIView - 모델 기반 CRUD API:
ModelViewSet(가장 일반적) 또는 필요한 믹스인과GenericViewSet조합 ModelViewSet의 기본 동작을 확장하거나 변경해야 할 때:perform_create,perform_update,perform_destroy등의 훅 메서드를 오버라이드하거나, 특정 액션에 대한 메서드를 재정의
3.5. 권한 (Permissions) 보충
DRF의 권한 클래스는 BasePermission을 상속받아 has_permission과 has_object_permission 메서드를 가집니다.
has_permission(request, view): 요청이 뷰에 도달하기 전에 실행되며, 전반적인 API 접근 권한을 확인합니다.has_object_permission(request, view, obj):self.get_object()가 호출될 때 실행되며, 특정 객체에 대한 작업(조회, 수정, 삭제) 권한을 확인합니다.
# app_common/permissions.py
from rest_framework.permissions import BasePermission
class IsAdminOrReadOnly(BasePermission):
message = {"code": 403, "message": "관리자만 쓰기 작업이 가능합니다."}
def has_permission(self, request, view):
if request.method in ['GET', 'HEAD', 'OPTIONS']:
return True # 읽기 권한은 모두에게 허용
# 쓰기 요청 (POST, PUT, PATCH, DELETE)은 관리자만 허용
return request.user and request.user.is_staff
def has_object_permission(self, request, view, obj):
if request.method in ['GET', 'HEAD', 'OPTIONS']:
return True
# 객체 수정/삭제는 해당 객체의 소유자 또는 관리자만 허용
# 예시: obj가 UserProfile 인스턴스이고, obj.user_name이 request.user.username과 같거나 관리자인 경우
return request.user and (request.user.is_staff or obj.user_name == request.user.username)
# 뷰에 적용 예시:
# class SomeProtectedAPIView(APIView):
# permission_classes = [IsAdminOrReadOnly]
# ...
4. 조건 검색 (Conditional Search)
DRF는 URL 쿼리 파라미터를 통해 API 응답 데이터를 필터링하는 기능을 제공합니다. 이는 /api/users?age=20&department=IT와 같은 형태로 구현됩니다.
4.1. 사용자 정의 필터
rest_framework.filters.BaseFilterBackend를 상속받아 filter_queryset 메서드를 구현하여 사용자 정의 필터를 만들 수 있습니다.
# app_common/filters.py
from rest_framework.filters import BaseFilterBackend
class AgeRangeFilterBackend(BaseFilterBackend):
"""
'min_age' 및 'max_age' 쿼리 파라미터를 사용하여 UserProfile을 나이로 필터링
"""
def filter_queryset(self, request, queryset, view):
min_age = request.query_params.get('min_age')
max_age = request.query_params.get('max_age')
if min_age:
queryset = queryset.filter(user_age__gte=min_age)
if max_age:
queryset = queryset.filter(user_age__lte=max_age)
return queryset
class DepartmentNameFilterBackend(BaseFilterBackend):
"""
'department_name' 쿼리 파라미터를 사용하여 부서명으로 UserProfile을 필터링
"""
def filter_queryset(self, request, queryset, view):
department_name = request.query_params.get('department_name')
if department_name:
queryset = queryset.filter(department__name__icontains=department_name)
return queryset
# app_common/views.py (이어지는 코드)
class FilteredUserProfileViewSet(viewsets.ModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserProfileDetailSerializer
filter_backends = [AgeRangeFilterBackend, DepartmentNameFilterBackend] # 필터 적용
# ... perform_create, update, destroy 등 다른 메서드 ...
# config/urls.py (업데이트)
router.register(r'filtered-profiles', FilteredUserProfileViewSet)
urlpatterns += router.urls
이제 /api/filtered-profiles/?min_age=20&department_name=IT와 같은 요청으로 데이터를 필터링할 수 있습니다.
4.2. DjangoFilterBackend (타사 필터)
django-filter 라이브러리는 DRF와 통합되어 모델 필터링을 위한 강력하고 유연한 기능을 제공합니다. 설치 후 settings.py에 등록해야 합니다.
pip install django-filter
# settings.py
INSTALLED_APPS = [
# ...
'django_filters',
# ...
]
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
# ...
}
적용 예시 1: filterset_fields
# app_common/views.py (이어지는 코드)
from django_filters.rest_framework import DjangoFilterBackend
class SimpleFilteredUserProfileViewSet(viewsets.ModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserProfileDetailSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['id', 'user_age', 'is_active'] # 쿼리 파라미터로 필터링할 필드 지정
# ...
# config/urls.py (업데이트)
router.register(r'simple-filtered-profiles', SimpleFilteredUserProfileViewSet)
urlpatterns += router.urls
이제 /api/simple-filtered-profiles/?user_age=25&is_active=true와 같이 필터링할 수 있습니다.
적용 예시 2: FilterSet 클래스 활용
더 복잡하거나 관계형 필드에 대한 필터링이 필요할 경우 FilterSet 클래스를 정의합니다.
# app_common/filters.py (이어지는 코드)
from django_filters import FilterSet, filters
class UserProfileFilterSet(FilterSet):
# 'min_id' 파라미터로 id가 특정 값 이상인 프로필 필터링 (gte: greater than or equal)
min_id = filters.NumberFilter(field_name='id', lookup_expr='gte')
# 'dept_name' 파라미터로 부서명(부분 일치)을 기준으로 필터링 (icontains: 대소문자 구분 없이 포함)
dept_name = filters.CharFilter(field_name='department__name', lookup_expr='icontains')
# 'level' 파라미터로 회원 등급을 정확히 필터링
membership_level = filters.ChoiceFilter(field_name='membership_info__level_code', choices=models.Membership.LEVEL_CHOICES)
class Meta:
model = models.UserProfile
fields = ['min_id', 'dept_name', 'membership_level', 'user_age']
# app_common/views.py (이어지는 코드)
class AdvancedFilteredUserProfileViewSet(viewsets.ModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserProfileDetailSerializer
filter_backends = [DjangoFilterBackend]
filterset_class = UserProfileFilterSet # 정의한 FilterSet 클래스 적용
# ...
# config/urls.py (업데이트)
router.register(r'advanced-filtered-profiles', AdvancedFilteredUserProfileViewSet)
urlpatterns += router.urls
이제 /api/advanced-filtered-profiles/?min_id=3&dept_name=영업&membership_level=2와 같이 필터링할 수 있습니다.
lookup_expr에는 exact, iexact, contains, icontains, startswith, istartswith, endswith, iendswith, gt, gte, lt, lte, in, range, isnull, regex, iregex 등 다양한 옵션이 있습니다.
4.3. DRF 내장 필터
DRF 자체적으로도 두 가지 유용한 필터를 제공합니다.
정렬 필터 (OrderingFilter)
응답 데이터를 특정 필드를 기준으로 오름차순 또는 내림차순으로 정렬할 수 있게 합니다.
# app_common/views.py (이어지는 코드)
from rest_framework.filters import OrderingFilter
class OrderedUserProfileViewSet(viewsets.ModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserProfileDetailSerializer
filter_backends = [OrderingFilter]
ordering_fields = ['user_age', 'user_name'] # 정렬 가능한 필드 지정
# ...
# config/urls.py (업데이트)
router.register(r'ordered-profiles', OrderedUserProfileViewSet)
urlpatterns += router.urls
사용 예시: /api/ordered-profiles/?ordering=user_age (나이 오름차순), /api/ordered-profiles/?ordering=-user_name (이름 내림차순)
검색 필터 (SearchFilter)
지정된 필드에 대해 쿼리 파라미터로 전달된 검색어를 포함하는 레코드를 필터링합니다 (부분 일치 검색).
# app_common/views.py (이어지는 코드)
from rest_framework.filters import SearchFilter
class SearchableUserProfileViewSet(viewsets.ModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserProfileDetailSerializer
filter_backends = [SearchFilter]
search_fields = ['user_name', 'email_address', 'department__name'] # 검색 가능한 필드 지정
# ...
# config/urls.py (업데이트)
router.register(r'searchable-profiles', SearchableUserProfileViewSet)
urlpatterns += router.urls
사용 예시: /api/searchable-profiles/?search=홍길동 (이름, 이메일, 부서명에서 '홍길동' 포함 여부 검색). DRF는 내부적으로 LIKE %검색어% 쿼리를 생성하여 검색을 수행합니다.