如何在Django Rest Framework中筛选含指定IP的物理服务器?
嘿,这个问题我之前做DRF项目时也碰到过——遍历查询集真的不是啥好主意,不仅代码啰嗦,数据量一大性能直接拉胯。咱们分两种最常见的ipv4s存储场景来解决,保证高效还符合DRF的规范:
情况1:ipv4s是与IP模型的多对多关联字段
如果你的ipv4s是和独立的IPAddress模型做的多对多关联(这也是最推荐的规范存储方式),比如:
class IPAddress(models.Model): address = models.GenericIPAddressField(protocol='ipv4') class PhysicalServer(models.Model): # 其他业务字段... ipv4s = models.ManyToManyField(IPAddress, related_name='physical_servers')
这时候咱们可以用django-filter结合ORM的反向关联过滤,让数据库直接完成筛选,性能拉满:
- 先确保安装了
django-filter(没装的话跑pip install django-filter) - 定义一个FilterSet类,专门处理IP筛选:
from django_filters import rest_framework as filters from .models import PhysicalServer class PhysicalServerFilter(filters.FilterSet): # 自定义筛选参数ip_address ip_address = filters.CharFilter(method='filter_by_target_ip') class Meta: model = PhysicalServer fields = ['ip_address'] def filter_by_target_ip(self, queryset, name, value): # 利用多对多关联的反向查询,直接在数据库层面筛选 return queryset.filter(ipv4s__address=value)
- 把这个FilterSet绑定到你的列表视图:
from rest_framework import generics from .filters import PhysicalServerFilter from .serializers import PhysicalServerSerializer class PhysicalServerListAPIView(generics.ListAPIView): queryset = PhysicalServer.objects.all() serializer_class = PhysicalServerSerializer filter_backends = [filters.DjangoFilterBackend] filterset_class = PhysicalServerFilter
之后只要请求/api/physical-servers/?ip_address=192.168.1.1,就能直接拿到包含该IP的物理服务器列表了,完全是数据库层面的JOIN查询,高效又靠谱。
情况2:ipv4s是JSONField存储的IP数组
如果你的ipv4s是直接存在JSONField里的字符串数组(比如["192.168.1.1", "192.168.1.2"]),模型定义像这样:
from django.db import models class PhysicalServer(models.Model): # 其他业务字段... ipv4s = models.JSONField(default=list)
这种情况也不用遍历,直接用Django的JSONField查询API就行:
同样定义FilterSet:
from django_filters import rest_framework as filters from .models import PhysicalServer class PhysicalServerFilter(filters.FilterSet): ip_address = filters.CharFilter(method='filter_ip_in_json_list') class Meta: model = PhysicalServer fields = ['ip_address'] def filter_ip_in_json_list(self, queryset, name, value): # Django 3.2+支持JSON数组包含查询,数据库层面完成筛选 return queryset.filter(ipv4s__contains=[value])
然后同样把FilterSet绑定到视图就行。这里要注意,如果用的是PostgreSQL,JSONField的查询会更高效(支持JSONB索引),MySQL也能支持但性能稍弱,不过肯定比内存遍历强太多。
为啥绝对不要遍历查询集?
可能你会想到这种写法:
# 极度不推荐的写法! def get_queryset(self): target_ip = self.request.query_params.get('ip_address') if target_ip: matched_servers = [] # 把所有服务器加载到内存遍历,灾难级性能 for server in PhysicalServer.objects.all(): if target_ip in server.ipv4s: matched_servers.append(server) return matched_servers return super().get_queryset()
这种写法的问题简直致命:
- 数据量大时,会把所有物理服务器都加载到内存,直接导致内存溢出
- 完全没法利用数据库的索引优化,查询速度慢到离谱
- 如果你的视图用了分页,遍历后的列表会直接打破分页逻辑,返回全部数据
所以一定要优先用数据库层面的筛选,这才是DRF和Django ORM设计的正确打开方式。
内容的提问来源于stack exchange,提问作者qg_java_17137
相关产品推荐
相关产品推荐

