在Django的ModelViewSet中使用Django Filters,结合City与Country模型实现过滤
实现跨City和Country模型的名称过滤
看起来你想要打造一个能同时搜索城市、国家名称的API接口,用Django Filter结合ModelViewSet来完成这个需求对吧?我来一步步帮你搞定:
1. 先把依赖配置到位
首先确认你已经装了django-filter:
pip install django-filter
接着在项目的settings.py里把它加入INSTALLED_APPS:
INSTALLED_APPS = [ # ... 你的其他应用 'django_filters', ]
如果你想全局启用过滤后端,可以在REST_FRAMEWORK配置里添上,但你已经在ViewSet里指定了filter_backends,这一步也可以跳过:
REST_FRAMEWORK = { 'DEFAULT_FILTER_BACKENDS': [ 'django_filters.rest_framework.DjangoFilterBackend', 'rest_framework.filters.SearchFilter', ] }
2. 定义关联双模型的FilterSet
你需要写一个自定义FilterSet,让它能同时匹配City的name和关联Country的name字段。假设你的City模型有个外键country关联到Country模型:
import django_filters from django.db.models import Q from .models import City class LocationFilter(django_filters.FilterSet): name = django_filters.CharFilter(method='filter_by_location_name', label='Location Name') def filter_by_location_name(self, queryset, name, value): # 用Q对象实现OR查询:匹配城市名 或 所属国家名包含搜索词 return queryset.filter( Q(name__icontains=value) | Q(country__name__icontains=value) ) class Meta: model = City fields = ['name']
这里用__icontains是做不区分大小写的模糊匹配,要是需要精确匹配,换成__exact就行。
3. 完善你的ModelViewSet
你代码里的get_location_list看起来没写完,其实完全不需要自定义这个方法——Django Filter会自动帮你处理过滤逻辑。把ViewSet调整成这样就好:
from rest_framework import viewsets from rest_framework.permissions import AllowAny from django_filters.rest_framework import DjangoFilterBackend from .models import City from .serializers import CityListSerializer from .filters import LocationFilter class LocationSearchAPI(viewsets.ModelViewSet): queryset = City.objects.all().order_by('name') permission_classes = [AllowAny] serializer_class = CityListSerializer filter_backends = (DjangoFilterBackend,) # 要是还想用SearchFilter的全局搜索功能,就保留下面这行替换上面的filter_backends # filter_backends = (DjangoFilterBackend, SearchFilter) filter_class = LocationFilter # 除非有额外逻辑要处理,否则不需要重写list方法 # 真要重写的话可以参考下面的示例: # def list(self, request, *args, **kwargs): # queryset = self.filter_queryset(self.get_queryset()) # page = self.paginate_queryset(queryset) # if page is not None: # serializer = self.get_serializer(page, many=True) # return self.get_paginated_response(serializer.data) # serializer = self.get_serializer(queryset, many=True) # return Response(serializer.data)
4. 测试你的接口
现在你就可以用想要的URL格式测试了:
http://localhost:8000/api/v1/geo/location-search/?name=Santa+Fe
这个请求会返回所有名称包含"Santa Fe"的城市,以及所属国家名称包含"Santa Fe"的城市。
额外小提示
- 如果你的City模型关联Country的外键字段不是
country,记得把Q(country__name__icontains=value)里的country改成你实际的字段名。 - 要是想同时支持精确匹配和模糊匹配,可以在FilterSet里加多个过滤字段,比如
name_exact和name_contains。 - 要是需要返回国家的详细信息,确保你的
CityListSerializer里包含了Country的嵌套序列化器:
from rest_framework import serializers from .models import City, Country class CountrySerializer(serializers.ModelSerializer): class Meta: model = Country fields = ['id', 'name'] class CityListSerializer(serializers.ModelSerializer): country = CountrySerializer() class Meta: model = City fields = ['id', 'name', 'country']
内容的提问来源于stack exchange,提问作者KitKit




