- python3.6, django 2.1, django-rest-framework 3.8을 사용합니다.
django에서의 filter는 보통 query_set()을 통한, 즉 쿼리를 통해 조건을 추가 하여 데이터를 반환하는 경우가 많습니다.
이를 더욱 재사용가능하게 만든 패키지가 django-filter입니다. 이를 활용하면 간단하게 filter / order by를 추가할 수 있습니다.
Using django-filter
먼저 해당 패키지를 설치 합니다.
pip3 install django-filter
settings.py의 rest_framework 의 옵션에 filter사용을 선언합니다.
# settings.py
REST_FRAMEWORK = {
.............
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
.............
}
이제 사용하는 ModelviewSet에 filterset_fields를 선언하고, 필터링하고 싶은 필드 명을 추가하면 됩니다.
해당 모델은 post모델로 모델의 구조는 앞썬 포스팅을 참조해주시기 바랍니다.
저는 post모델의 칼럼중 message의 값을 filter 하려 합니다.
# views.py
class PostViewSet(ModelViewSet):
filterset_fields = ('message',)
이제 url을 다음과 같이 요청하면 요청한 값만 필터링되어 나오는 것을 볼 수 있습니다.
http://127.0.0.1:8080/api/v1/post/?message=post 2
filterset_fields = ()의 선언으로 간편하게 filter를 구현할수 있습니다. 하지만 해당 필터는 equal조건으로 다른 다양한 조건을 위해서는 filterSet을 구현해야 합니다.
Declaring filters
다양한 필터 조건을 해보기 위해 칼럼을 추가하려 합니다.
post모델에 public / count 칼럼 추가 합니다.
# models.py
class Post(OwnedModel):
message = models.TextField(blank=True, help_text="메시지")
public = models.BooleanField(default=False, help_text="공개 여부")
count = models.IntegerField(default=0, help_text="읽은 유저 수")
created_at = models.DateTimeField(auto_now_add=True)
모델 적용을 위해 migrate를 실시 합니다.
$ ./manage.py makemigrations
$ ./manage.py migrate
추가된 칼럼의 데이터 변경를 변경합니다.
저는 count / public의 값을 아래와 같이 변경하였습니다.
이제 실제 필터를 구현할 차례입니다.
먼저 django_filters의 다양한 필터를 통해 구현합니다. serialzer의 선언과 비슷하다고 생각하시면 됩니다.
필터링의 선언은 크게
- url에 어떻게 선언할 것인가
- 어떤 타입으로 받을 것인가
- 어떤 조건으로 필터링 할것인가
로 요약이 됩니다.
count__gt = NumberFilter(field_name="count", lookup_expr="gt")
위를 예로 보면 url에서는 count__gt로 값을 줄 경우 작동합니다.
NumberFilter()는 해당 값은 Numbe형태로만 받을 수 있습니다. 다른 데이터형은 에러를 줍니다.
field_name은 모델에 선언된 칼럼 중 count 칼럼의 값과 비교하며,
lookup_expr은 비교를 어떻게 할 것인가입니다. "gt"의 경우 큰 것을 뜻합니다.
즉 위의 코드는 count__gt로 들어온 값이 있다면 해당 값보다 count가 큰 값만 리턴됩니다.
이제 filters의 전체 소스입니다.
# filters.py
from django_filters import (
FilterSet,
CharFilter,
NumberFilter,
BooleanFilter,
)
from .models import Post
class PostFilter(FilterSet):
count = NumberFilter()
count__gt = NumberFilter(field_name="count", lookup_expr="gt")
count__lt = NumberFilter(field_name="count", lookup_expr="lt")
public = BooleanFilter(field_name="public")
message = CharFilter(lookup_expr="icontains")
class Meta:
model = Post
fields = ["message", "count", "public"]
def __init__(self, *args, **kwargs):
super(PostFilter, self).__init__(*args, **kwargs)
views.py에는 앞서 선언한 filter를 viewSet에 적용합니다.
# views.py
from .filters import PostFilter
class PostViewSet(ModelViewSet):
......
filter_class = PostFilter
serializer도 변경되었습니다. 앞 썬 포스팅에선 onwer에 대한 정보가 없었으나, owner의 str()를 반환하도록 하였습니다. (유저 객체의 str()은 username을 반환합니다.)
#serializers.py
class PostSerializer(serializers.ModelSerializer):
owner = serializers.SerializerMethodField()
def get_owner(self, obj):
return str(obj.owner)
class Meta:
model = Post
fields = ["id", "message", "created_at", "count", "public", "owner"]
이제 준비는 끝났습니다. 결과를 확인해 봅시다.
1. /api/v1/post/?message=2 검색
message에 2가 들어간 데이터가 검색되는 것을 확인할 수 있습니다. filter CharFilter() "icontains"로 검색 조건이 되어 있습니다.
2. /api/v1/post/?count__gt=10 검색
count의 값 중 10보다 큰 값을 검색합니다.
3. /api/v1/post/?public=False 검색
public의 값이 False인 값을 검색합니다. filter에서는 BooleanFilter()로 선언되어 있습니다.
ordering
filter과 같이 ordering도 선언식으로 쓸 수 있습니다.
함수는 OrderingFilter를 사용하며, 해당 url로 받을 변수의 이름을 지정하는 order_by_field, 그리고 ordering을 할 필드 명으로 구분됩니다. 오름 차수는 필드명만, 내림 차수는 -필드명으로 앞에 -를 붙입니다.
먼저 filter에 order_by_field / ordering를 선언해 줍니다.
from django_filters.filters import OrderingFilter
class PostFilter(FilterSet):
...................
order_by_field = 'ordering'
ordering = OrderingFilter(
fields=(
'message',
'count',
'public',
)
)
1. api/v1/post/?public=False&ordering=count 검색
public로 필터링된 후 count가 오름 차순으로 정렬됩니다.
2. api/v1/post/?public=False&ordering=-count 검색
public로 필터링된 후 count가 내림 차순으로 정렬됩니다.
django-filter을 통해 간편하게 filter / ording를 구현하였습니다. 이로써, 쿼리에 추가적인 작업이 필요 없이 간편하게 filter / ordering를 쓸 수 있게 되었습니다.
앞 썬 예제에서는 모델 칼럼의 단순 비교만 나왔습니다.
만일 foreign key와 모델로 연결된 칼럼이 있다면 쿼리에서 쓰던 방식과 동일하게 filter를 선언할 수 있습니다.
먼저 테스트를 위해 기존의 req.user를 통해서 로그인한 데이터만 보이면 쿼리문을 변경하였습니다.
filter()의 조건을 임시로 제거하였습니다.
# views.py
class PostViewSet(ModelViewSet):
.............
def get_queryset(self):
return super().get_queryset().filter()
filters.py에 owner의 필드로 검색하려 합니다. owner는 user 모델의 foreign key로 되어 있습니다.
만일 user의 username으로 검색하고 싶다면 아래와 같이 lookup_expr를 username__icontains로 하면 됩니다.
#filters.py
class PostFilter(FilterSet):
..............
owner__name = CharFilter(field_name="owner", lookup_expr="username__icontains")
1. api/v1/post/?owner__name=admin 검색
유저의 이름이 admin으로 등록된 post가 검색됩니다.
2. /api/v1/post/?owner__name=test 검색
유저의 이름이 test로 등록된 post가 검색됩니다.
참고 자료
https://django-filter.readthedocs.io/en/stable/ref/filterset.html
'web > Django_rest_framework' 카테고리의 다른 글
[Django rest framework] 번외. swagger 적용하기 (0) | 2020.08.05 |
---|---|
[Django rest framework] 5. test (1) | 2020.07.05 |
[Django rest framework] 3. pagination (0) | 2020.06.18 |
[Django rest framework] 2. token / authentication (0) | 2020.06.07 |
[Django rest framework] 1. 기존 셋팅 + CRUD (0) | 2020.06.04 |