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 was NumberFilter... 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 - 使用搜索过滤器搜索方法字段的主要内容,如果未能解决你的问题,请参考以下文章

django rest框架过滤器

如何使用 nodejs 和 express 在 REST API 中实现搜索和过滤

如何使用 django rest 框架从 GET 请求的查询参数中过滤多个 id?

如何在 Django REST API 中使用 GPS 位置?

django-rest-framework- 使用“或”过滤来自一个 url 参数的多个值

带有视图集路由器查询集过滤器的 Django REST 框架