Django rest api - 使用搜索过滤器搜索方法字段
Posted
技术标签:
【中文标题】Django rest api - 使用搜索过滤器搜索方法字段【英文标题】:Django rest api - searching a method field with the search filter 【发布时间】:2020-01-03 15:27:59 【问题描述】:我正在尝试过滤搜索其余 api 页面并希望使用方法字段作为搜索字段之一,但是当我这样做时,我收到一条错误消息,指出该字段无效,然后它会在我的模型中列出该字段作为唯一有效的来源
序列化器:
class SubnetDetailsSerializer(QueryFieldsMixin, serializers.HyperlinkedModelSerializer):
subnet = serializers.SerializerMethodField()
device = serializers.ReadOnlyField(
source='device.hostname',
)
circuit_name = serializers.ReadOnlyField(
source='circuit.name',
)
subnet_name = serializers.ReadOnlyField(
source='subnet.description',
)
safe_subnet = serializers.SerializerMethodField()
def get_safe_subnet(self, obj):
return ''.format(obj.subnet.subnet, obj.subnet.mask.replace('/','_'))
def get_subnet(self, obj):
return ''.format(obj.subnet.subnet, obj.subnet.mask)
class Meta:
model = DeviceCircuitSubnets
fields = ('id','device_id','subnet_id','circuit_id','subnet','safe_subnet','subnet_name','device','circuit_name')
观看次数:
class SubnetDetailsSet(viewsets.ReadOnlyModelViewSet):
queryset = DeviceCircuitSubnets.objects.all().select_related('circuit','subnet','device')
serializer_class = SubnetDetailsSerializer
permission_classes = (IsAdminUser,)
filter_class = DeviceCircuitSubnets
filter_backends = (filters.SearchFilter,)
search_fields = (
'device__hostname',
'circuit__name',
'subnet__subnet',
'safe_subnet'
)
如何在搜索字段中包含 safe_subnet?
谢谢
编辑 这是现在的代码
views.py
class SubnetDetailsSet(viewsets.ReadOnlyModelViewSet):
queryset = DeviceCircuitSubnets.objects.all()
serializer_class = SubnetDetailsSerializer
permission_classes = (IsAdminUser,)
filter_class = DeviceCircuitSubnets
filter_backends = (filters.SearchFilter,)
search_fields = (
'device__hostname',
'circuit__name',
'subnet__subnet',
'safe_subnet'
)
def get_queryset(self):
return (
super().get_queryset()
.select_related('circuit','subnet','device')
.annotate(
safe_subnet=Concat(
F('subnet__subnet'),
Replace(F('subnet__mask'), V('/'), V('_')),
output_field=CharField()
)
)
)
serializer.py
class SubnetDetailsSerializer(QueryFieldsMixin, serializers.HyperlinkedModelSerializer):
subnet = serializers.SerializerMethodField()
device = serializers.ReadOnlyField(
source='device.hostname',
)
circuit_name = serializers.ReadOnlyField(
source='circuit.name',
)
subnet_name = serializers.ReadOnlyField(
source='subnet.description',
)
def get_safe_subnet(self, obj):
return getattr(obj, 'safe_subnet', None)
def get_subnet(self, obj):
return ''.format(obj.subnet.subnet, obj.subnet.mask)
class Meta:
model = DeviceCircuitSubnets
fields = ('id','device_id','subnet_id','circuit_id','subnet','safe_subnet','subnet_name','device','circuit_name')
型号:
class DeviceCircuitSubnets(models.Model):
device = models.ForeignKey(Device, on_delete=models.CASCADE)
circuit = models.ForeignKey(Circuit, on_delete=models.CASCADE, blank=True, null=True)
subnet = models.ForeignKey(Subnet, on_delete=models.CASCADE)
active_link = models.BooleanField(default=False, verbose_name="Active Link?")
active_link_timestamp = models.DateTimeField(auto_now=True, blank=True, null=True)
错误:
Exception Type: ImproperlyConfigured at /api/subnets/
Exception Value: Field name `safe_subnet` is not valid for model `DeviceCircuitSubnets`.
【问题讨论】:
使用查询执行搜索,您不能在方法字段上进行搜索。 你能把模型和错误也贴出来吗? 对此有什么想法吗?谢谢 您需要对所有这些都使用 ?search=,还是可以使用支持?subnet=192.168/24&device=django&circut=31
样式查询的自定义过滤器?在这种情况下,后者更可取。
【参考方案1】:
您需要使用 safe_subnet
属性注释您的查询集,以便它可以搜索。
from django.db.models import F, Value as V
from django.db.models.functions import Concat, Replace
class SubnetDetailsSet(viewsets.ReadOnlyModelViewSet):
queryset = DeviceCircuitSubnets.objects.all()
serializer_class = SubnetDetailsSerializer
permission_classes = (IsAdminUser,)
filter_class = DeviceCircuitSubnets
filter_backends = (filters.SearchFilter,)
search_fields = (
'device__hostname',
'circuit__name',
'subnet__subnet',
'safe_subnet'
)
def get_queryset(self):
return (
super().get_queryset()
.select_related('circuit','subnet','device')
.annotate(
safe_subnet=Concat(
F('subnet__subnet'),
Replace(F('subnet__mask'), V('/'), V('_')),
output_field=CharField()
)
)
)
然后在您的序列化程序中,您可以使用以下内容。
def get_safe_subnet(self, obj):
return obj.safe_subnet
【讨论】:
我看到错误字段名称safe_subnet
对模型DeviceCircuitSubnets
无效。仍然
如果错误是由过滤器引起的,那么可能是这个DRF issue的结果,你应该可以通过升级来解决。否则,如果序列化程序在SubnetDetailsSet
之外使用并且查询集没有注释,那么您也会收到此错误。如果您出于某种原因不想注释,可以更改为return getattr(obj, 'safe_subnet', None)
。
我已尝试升级并添加建议的内容,但仍然出现相同的错误
@AlexW 当您在 shell 中使用注解时,它会起作用吗?您需要查看回溯并查看导致错误的原因。 SearchFilter
可能不会在过滤之前调用视图的 .get_queryset()
方法,因此您需要弄清楚如何做到这一点。
注释通过外壳工作是的,我不知道如何编辑搜索过滤器以包含它,你会碰巧知道怎么做吗?谢谢【参考方案2】:
annotate
的先前回答是一个非常好的开始:
from .rest_filters import DeviceCircuitSubnetsFilter
class SubnetDetailsSet(viewsets.ReadOnlyModelViewSet):
queryset = DeviceCircuitSubnets.objects.all()
serializer_class = SubnetDetailsSerializer
permission_classes = (IsAdminUser,)
# That's where hint lays
filter_class = DeviceCircuitSubnetsFilter
#filter_backends = (filters.SearchFilter,)
search_fields = (
'device__hostname',
'circuit__name',
'subnet__subnet',
'safe_subnet'
)
#No need to override your queryset
现在在rest_filters.py
from django_filters import rest_framework as filters
from django.db.models import F, Value as V
from django.db.models.functions import Concat, Replace
#.... import models
class DeviceCircuitSubnets(filters.FilterSet):
safe_subnet = filters.CharFilter(
name='safe_subnet',
method='safe_subnet_filter')
def safe_subnet_filter(self, queryset, name, value):
"""
Those line will make ?safe_subnet=your_pk available
"""
return queryset.annotate(
safe_subnet=Concat(
F('subnet__subnet'),
Replace(F('subnet__mask'), V('/'), V('_')),
output_field=CharField()
)
).filter(safe_subnet=value)
)
class Meta:
model = DeviceCircuitSubnets
# See https://django-filter.readthedocs.io/en/master/guide/usage.html#generating-filters-with-meta-fields
# This pattern is definitely a killer!
fields =
'device': ['exact', 'in'],
'circuit': ['exact', 'in'],
'subnet': ['exact', 'in'],
'active_link': ['exact'],
'active_link_timestamp': ['lte', 'gte']
请注意:我在文件管理器中注释safe_subnet
,根据您使用它的程度,您可能需要在模型的管理器中进行设置!
【讨论】:
我收到错误:safe_subnet = django_filters.IntegerField( AttributeError: module 'django_filters' has no attribute 'IntegerField' 我仍然看到相同的 AttributeError: module 'django_filters.rest_framework' has no attribute 'IntegerField' Sorry... so used to Serializers/Models... Correct name wasNumberFilter
... response is updated with CharFilter
: If filter does not match your data type, you can find完整列表在这里:django-filter.readthedocs.io/en/master/ref/filters.html#filters 我猜你需要 CharFilter 因为safe_subnet
是 CharField
好的,由于某种原因“无法将关键字 'safe_subnet' 解析为字段”
您是否在 APIView 中保留了 safe_subnet
search_fields
?如果是的话,你可以试试没有这个吗?【参考方案3】:
与其他(优秀)答案完全不同的方向。既然您希望能够在 safe_subnet
字段上频繁过滤,为什么不让它成为模型中的实际数据库字段呢?您可以在您的 save
方法之一期间计算和填充/更新该值,然后让 django-filters
做这件事。这还具有允许直接通过 SQL 进行过滤的优点,理论上可以提供更好的性能。
【讨论】:
我最终还是这样做了,覆盖了表单有效方法并在数据库中动态创建它以上是关于Django rest api - 使用搜索过滤器搜索方法字段的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 nodejs 和 express 在 REST API 中实现搜索和过滤
如何使用 django rest 框架从 GET 请求的查询参数中过滤多个 id?
如何在 Django REST API 中使用 GPS 位置?