前言
某些需求,前端可能需要后端数据的过滤,排序条件来查询数据,但是自己写这些接口又很是费劲,这不,今天推荐一个第三方包 django_filters GitHub - carltongibson/django-filter: A generic system for filtering Django QuerySets based on user selections
官方文档:django-filter — django-filter 22.1 documentation
文档说的很详细,本文就不在一一说明了,仅仅演示一下平常使用的过滤
模型定义
先看一下模型定义,该模型定义了一个文件存储相关信息
class FileInfo(models.Model):
# 文件下载连接实效4个小时,通过缓存进行存储
owner_id = models.ForeignKey(to=User, on_delete=models.CASCADE, verbose_name="所属用户ID")
aliyun_drive_id = models.ForeignKey(to=AliyunDrive, on_delete=models.CASCADE, verbose_name="所属阿里云盘ID")
name = models.CharField(max_length=256, verbose_name="文件名字")
file_id = models.CharField(max_length=64, verbose_name="文件id")
drive_id = models.CharField(max_length=64, verbose_name="drive_id")
created_at = models.DateTimeField(verbose_name="上传时间", auto_now_add=True)
size = models.BigIntegerField(verbose_name="文件大小")
content_type = models.CharField(max_length=64, verbose_name="文件类型")
content_hash = models.CharField(max_length=64, verbose_name="content_hash")
crc64_hash = models.CharField(max_length=64, verbose_name="crc64_hash")
downloads = models.BigIntegerField(verbose_name="下载次数", default=0)
description = models.CharField(max_length=256, verbose_name="备注信息", blank=True)
class Meta:
verbose_name = '文件信息'
verbose_name_plural = "文件信息"
def __str__(self):
return f"所属用户:{self.owner_id}-文件名:{self.name}-下载次数:{self.downloads}-文件大小:{self.size}"
需求如下
- 根据备注进行模糊搜索
- 根据文件名进行模糊搜索
- 根据文件大小排序
- 根据上传时间排序
- 根据下载次数排序
实现
定义一个filterset类,继承filter
from django_filters import rest_framework as filters
from rest_framework.viewsets import ModelViewSet
from rest_framework import serializers
from rest_framework.filters import OrderingFilter
from rest_framework.filters import BaseFilterBackend
from rest_framework.pagination import PageNumberPagination
class PageNumber(PageNumberPagination):
page_size = 20 # 每页显示多少条
page_size_query_param = 'size' # URL中每页显示条数的参数
page_query_param = 'page' # URL中页码的参数
max_page_size = 100 # 最大页码数限制
class OwnerUserFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
return queryset.filter(owner_id=request.user)
class FileInfoSerializer(serializers.ModelSerializer):
class Meta:
model = models.FileInfo
exclude = ["owner_id", "aliyun_drive_id"]
read_only_fields = list(
set([x.name for x in models.FileInfo._meta.fields]) - {"description"})
class FileInfoFilter(filters.FilterSet):
description = filters.CharFilter(field_name='description', lookup_expr='icontains')
name = filters.CharFilter(field_name='name', lookup_expr='icontains')
class Meta:
model = FileInfo
fields = ['name']
class FileInfoView(ModelViewSet):
queryset = FileInfo.objects.all()
serializer_class = FileInfoSerializer
pagination_class = PageNumber
filter_backends = [OwnerUserFilter, filters.DjangoFilterBackend, OrderingFilter]
ordering_fields = ['size', 'created_at', 'downloads']
filterset_class = FileInfoFilter
解释一下定义的方法
1.定义一个专属用户过滤,只过滤当前用户数据
from rest_framework.filters import BaseFilterBackend
class OwnerUserFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
return queryset.filter(owner_id=request.user)
2.定义用户名和备注过滤,通过lookup_expr定义过滤方式
class FileInfoFilter(filters.FilterSet):
description = filters.CharFilter(field_name='description', lookup_expr='icontains')
name = filters.CharFilter(field_name='name', lookup_expr='icontains')
class Meta:
model = FileInfo
fields = ['name']
3.定义排序,排序的话,直接用drf自带的排序就行,直接写
ordering_fields = ['size', 'created_at', 'downloads']
4.分页的话,通过自带的 pagination 实现
from rest_framework.pagination import PageNumberPagination
class PageNumber(PageNumberPagination):
page_size = 20 # 每页显示多少条
page_size_query_param = 'size' # URL中每页显示条数的参数
page_query_param = 'page' # URL中页码的参数
max_page_size = 100 # 最大页码数限制
这样,一些简单的过滤查询就实现了
关于时间查询,比如要查询时间是否大于当前时间,获取取时间段,还可以通过自定义查询方式
class ShareCodeFilter(filters.FilterSet):
description = filters.CharFilter(field_name='description', lookup_expr='icontains')
expired = filters.BooleanFilter(field_name='expired_time', method='filter_expired')
def filter_expired(self, queryset, name, value):
default_timezone = timezone.get_default_timezone()
now_time = timezone.make_aware(datetime.datetime.now(), default_timezone)
if value:
key = 'lt'
else:
key = 'gt'
lookup = '__'.join([name, key])
print({lookup: now_time})
return queryset.filter(**{lookup: now_time})
class Meta:
model = ShareCode
fields = ['short', 'expired', 'description']
上面就展示了要查询数据是否过期,查询已经完成,具体展示就需要前端来配合实现。
相关源码可以参考 https://github.com/nineaiyu/xshare