Django rest api - 使用搜索过滤器搜索方法字段

Django rest api - searching a method field with the search filter

我正在尝试过滤搜索其余 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`.

您需要使用 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

上一个 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 字段上进行过滤,为什么不让它成为您模型中的实际数据库字段呢?您可以在您的 save 方法之一期间计算并 populate/update 值,然后让 django-filters 执行它。这还有一个优点是可以直接通过 SQL 进行过滤,理论上可以提供更好的性能。