-
django-filters 를 사용하여 복잡한 환경에서 OrderingFilter 사용하기기술/Django 2021. 9. 24. 19:43
django-filters 를 사용한다면 OrderingFilter 를 FilterSet 쪽에 붙이는 것이 더 직관적이고 편할 수 있습니다.
django-filters 라이브러리에서 OrderingFilter 에 대해 간단한 설명이 있지만 조금 더 복잡하게 사용할 필요가 있었기 때문에 어떻게 사용했는지 공유하고자 합니다.
기본 사용법
OrderingFilter 를 한 키로 정의하고 그 안에 fields 속성에 Ordering 에 사용할 필드들을 나열합니다.
위 처럼 작성하면 ?ordering=username,-first_name 와 같이 QueryString 에 포함시킬 수 있습니다.
대부분의 간단한 정렬은 가능하지만, 예외 케이스가 몇개 존재합니다.
class UserFilter(FilterSet): ordering = OrderingFilter( fields=( ('username', 'account'), ('first_name', 'first_name'), ('last_name', 'last_name'), ) )
Annotate 필드를 정렬하기
특정 필터에만 Annotate 가 사용될 때 기본적인 OrderingFilter 로 해결하지 못했습니다.
이를 해결하기 위해서 OrderingFilter 를 커스텀 할 필요가 있었습니다.
제가 작성한 내용은 공식 문서에 나와있는 것에 기반하여 작성하였습니다.
class UserOrderingFilter(OrderingFilter): def __init__(self, *wargs, **kwargs): super().__init__(*args, **kwargs) # 모델에 없는 필드를 허용하기 위해 여기에서 정보를 전달합니다. self.extra['choices'] += [ ('user_type', '유저의 타입 ( 오름차순 )'), ('-user_type', '유저의 타입 ( 내림차순 )'), ] def filter(self, queryset, value): # user_type 이 있다면 annotate 하는 로직 if value and any(v in ['user_type', '-user_type']): queryset = queryset.annotate( user_type=Case( When( type=Value(User.TYPE_MERCHANT), then=Value(2), ), When( type=Value(User.TYPE_FRANCHISE), then=Value(1), ), default=Value(0), output_fields=models.IntegerField(), ), )
NULL 인 필드는 정렬 시 후순위로 두기
오름차순으로 정렬하게되면 NULL 인 필드들이 먼저나오는 문제가 있었습니다. 그 문제를 해결하기위해 .order_by(F('<field_name>').asc(nulls_last=True)) 를 설정했어야하지만, 관련한 내용을 찾아볼 수 없었습니다.
그래서 내부를 탐색하고 사용하는 입장에서는 이야기하지 않고는 어렵지만 나름 빠르게 해결할 수 있는 방법을 생각했습니다.
class FNullsLastOrderingFilter(OrderingFilter): """ ordering fields 의 key 에 F Object 가 온다면 nulls_last 를 적용하는 클래스 """ def get_ordering_value(self, param): descending = param.startswith('-') param = param[1:] if descending else param field_name = self.param_map.get(param, param) if isinstance(field_name, F): return field_name.desc(nulls_last=True) if descending else field_name.asc(nulls_last=True) else: return "-%s" % field_name if descending else field_name
위처럼 작성했으면 이제 Key 로 F Object 를 작성해주시면 됩니다.
그러면 ?ordering=last_login 으로 실행했을 때 오름차순으로 되지만 null 인 필드는 마지막에 나오게됩니다.
class UserFilter(FilterSet): ordering = OrderingFilter( fields=( (F('last_login'), 'last_login'), ) )
'기술 > Django' 카테고리의 다른 글
Traefik vs Caddy (1) 2021.09.24 django 에서 select_related, prefetch_related 에서 추가적인 ORM 을 사용하기 (0) 2021.09.24 Django 에서 일시적으로 auto_now, auto_now_add 를 disable 시키는 법 (0) 2021.09.24 Django Filters 에서 하나의 Key를 가지고 여러 값 필터하기 ( MultipleFilter ) (0) 2021.09.24 Django 에서 String datetime 을 aware 한 datetime 으로 변경하는 방법 (0) 2021.09.24