Django Rest API - 使用搜索过滤器搜索方法字段

9
我将翻译为:

我正在尝试过滤搜索一个REST 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`.

搜索是使用查询执行的,您无法在方法字段上进行搜索。 - dirkgroten
1
你能同时发布模型和错误吗? - Mirza715
有关这个问题,您有什么想法吗? - AlexW
你是否需要对所有这些使用 ?search=,或者可以使用支持 ?subnet=192.168/24&device=django&circut=31 样式查询的自定义过滤器?在这种情况下,后者更可取。 - Andrew
3个回答

5

您需要使用 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无效。” - AlexW
如果错误是由过滤器引起的,那么可能是由于这个DRF问题导致的,您可以通过升级来解决。否则,如果序列化程序在SubnetDetailsSet之外使用并且查询集没有注释,则也会出现此错误。如果您不想为某些原因进行注释,可以更改为return getattr(obj,'safe_subnet',None) - bdoubleu
我已经尝试升级和添加建议的内容,但我仍然收到相同的错误。 - AlexW
@AlexW,当您在shell中使用它时,注释是否有效?您需要查看回溯并查看是什么导致了错误。很可能SearchFilter在过滤之前不会调用视图的.get_queryset()方法,因此您需要弄清楚如何做到这一点。 - bdoubleu
注释是通过 shell 实现的,但我不知道如何编辑搜索过滤器以包括它,你知道怎么做吗?谢谢。 - AlexW

1

之前的回答使用 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']
        }

请注意:我正在注释filer中的safe_subnet,根据您使用的程度,您可能希望在模型管理器中设置此项!

我遇到了错误: safe_subnet = django_filters.IntegerField( AttributeError: 模块 'django_filters' 没有属性 'IntegerField' - AlexW
我仍然看到相同的 AttributeError: module 'django_filters.rest_framework' has no attribute 'IntegerField'。 - AlexW
抱歉...我太习惯于使用序列化器/模型了... 正确的名称是NumberFilter...响应已更新为CharFilter: 如果过滤器与您的数据类型不匹配,您可以在此处找到完整列表: https://django-filter.readthedocs.io/en/master/ref/filters.html#filters我猜你需要使用CharFilter,因为safe_subnetCharField - Julien Kieffer
好的,现在它又回到了最初的错误,原因不明,“无法将关键字'safe_subnet'解析为字段”。 - AlexW
你在 APIView 的 search_fields 中保留了 safe_subnet 吗? 如果有,请尝试不使用它。 - Julien Kieffer
我现在得到了一个错误,init()收到了一个意外的关键字参数'name',我认为这是在kwargs下出错了 {'label': '[invalid name]', 'name': 'safe_subnet', 'required': False} - AlexW

0

与其他(优秀)答案完全不同的方向。由于您希望能够经常过滤safe_subnet字段,为什么不让它成为模型中的实际数据库字段呢?您可以在其中一个save方法中计算和填充/更新该值,然后只需让django-filters发挥作用即可。这还具有直接通过SQL进行过滤的优点,理论上可以提供更好的性能。


我最终选择了这个方案,覆盖表单有效方法并在数据库中动态创建它。 - AlexW

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接