【问题标题】:Django Rest Framework filter a calculated SerializerMethodField() in ViewSet using filterset_fieldsDjango Rest Framework 使用 filterset_fields 过滤 ViewSet 中计算的 SerializerMethodField()
【发布时间】:2021-06-20 12:12:12
【问题描述】:

我有一个 SerializerMethodField 计算下一个执行日期,将小时数添加到上次执行时间

简化代码,有这个模型:

class Activity(BaseModel):
    name = models.CharField(max_length=250)
    last_execution = models.DateTimeField()

    def __str__(self):
        return self.name

    class Meta:
        ordering = ('name',)

为了在下一次执行时间发送到前端,我的Serializer中有这个SerializerMethodField()

class ActivitySerializer(serializers.ModelSerializer):
    next_execution = serializers.SerializerMethodField('get_next_execution')

    class Meta:
        model = Activity
        fields = ('id', 'name', 'last_execution', 'next_execution',)

    def get_next_execution(self, data):
        # last = ActivityExecute.objects.filter(activity_id=data.id).order_by('-executed_at').first()
        # time = data.periodicity_type.time_in_hour
        # if last:
        #     if data.equipment.hour_meter > -1:
        #         return last.executed_at + timedelta(hours=time - (data.equipment.hour_meter - last.hour_meter))
        #     return last.executed_at + timedelta(hours=time)
        # 
        # return data.equipment.created_at + timedelta(hours=time)
        return data.last_execution + timedelta(hours=24)

但是当我尝试像这样将计算字段添加到filterset_fields 时:

class ActivityViewSet(viewsets.ModelViewSet):
    permission_classes = (IsAuthenticated,)

    serializer_class = ActivitySerializer
    filter_backends = (filters.DjangoFilterBackend,)
    filterset_fields = {
        'id': ['exact'],
        'name': ['icontains', 'exact'],
        'last_execution': ['exact'],
        'next_execution': ['exact'],
    }

    def get_object(self):
        return super(ActivityViewSet, self).get_object()

    def get_queryset(self):
        return Activity.objects.all()

我收到了这个错误: 'Meta.fields' must not contain non-model field names: next_execution

有没有办法将SerializerMethodField() 添加到filterset_fields?使用方法get_queryset 将使我在该计算字段(已注释)中的所有逻辑都被复制。该字段工作正常,问题只是前端使用查询参数过滤值。

【问题讨论】:

  • 我认为你最好的选择是在 Activity 模型上创建一个属性,如果使用 django-property-filter 包,它也可以在序列化程序和过滤器中使用。

标签: python django django-rest-framework django-filter


【解决方案1】:

如果您可以为您的项目添加一个包,使用django-property-filter 会很容易

只需将您的序列化程序代码转换为如下属性:

class Activity(BaseModel):
    name = models.CharField(max_length=250)
    last_execution = models.DateTimeField()

    def __str__(self):
        return self.name

    class Meta:
        ordering = ('name',)

    @property
    def next_execution(self):
        # all your logic in comment can be pasted here
        return self.last_execution + timedelta(hours=24)  

serializer 中删除您的SerializerMethodField()def get_next_execution,但将其保留在字段列表中,此属性也将作为字段列出

class ActivitySerializer(serializers.ModelSerializer):

    class Meta:
        model = Activity
        fields = ('id', 'name', 'last_execution', 'next_execution',)

现在,在视图中,从您的ViewSet 中删除filterset_fields,我们会将其添加到FilterSet 类中...

为您的ViewSet 创建一个FilterSet

from django_property_filter import PropertyFilterSet, PropertyDateFilter

class ActivityFilterSet(PropertyFilterSet):
    next_execution = PropertyDateFilter(field_name='next_execution', lookup_expr='exact') # this is the @property that will be filtered as a DateFilter, you can change the variable name, just keep the `field_name` right

    class Meta:
        model = Activity
        fields = { # here is the filterset_fields property
            'id': ['exact'],
            'name': ['icontains', 'exact'],
            'last_execution': ['exact'],
        }

然后,只需将此 FilterSet 添加到您的 ViewSet 中,奇迹就会发生

class ActivityViewSet(viewsets.ModelViewSet):
    permission_classes = (IsAuthenticated,)

    serializer_class = ActivitySerializer
    filter_backends = (filters.DjangoFilterBackend,)
    filterset_class = ActivityFilterSet

    def get_object(self):
        return super(ActivityViewSet, self).get_object()

    def get_queryset(self):
        return Activity.objects.all()

【讨论】:

    【解决方案2】:

    我真的不喜欢django-filter - 这是我更喜欢的一种方法,它让我们(几乎)可以通过几行代码从前端完全访问 django-orm:

    class BaseViewSet(viewsets.ModelViewSet):
    
        def get_queryset(self):
            queryset = self.queryset
            custom_filter = self.request.query_params.get('filter')
            custom_exclude = self.request.query_params.get('exclude')
        
            if custom_filter: 
                custom_filter = json.loads(custom_filter)
                queryset = queryset.filter(**custom_filter)
            
            if custom_exclude: 
                custom_exclude = json.loads(custom_exclude)
                queryset = queryset.exclude(**custom_exclude)
            
            return queryset
    

    现在我们可以像这样发送请求:

    // django-like filter:
    var filter = {
      last_execution : '2020-01-01 08:20:20'
    }
    
    $.ajax({
      url : 'api/activities/?filter=' + JSON.stringify(filter)
      type : 'GET',
      success : function(response){...},
      ...
    });
    

    基本上,您的网址现在看起来像:

    https://mywebsite.com/api/activities/?filter={"last_execution":"2020-01-01 08:20:20"}

    现在我们可以在 js 代码中为 last_execution_date 添加 24 小时来检索 activity(而不是在 python 端这样做):

    // can use libraries like moment.js etc:
    var filter = {
      last_execution : '2020-01-02 08:20:20'
    }
    
    $.ajax({...});
    

    就是这样!不再向每个视图集添加字段。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-08-18
      • 2017-12-12
      • 2014-07-01
      • 2019-06-22
      • 2016-07-21
      • 2016-09-25
      • 2016-05-23
      • 2020-04-23
      相关资源
      最近更新 更多