ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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'),
        )
      )

     

    댓글

Designed by Tistory.