Django Rest Framework自定义过滤器后端数据重复

时间:2019-07-15 11:59:17

标签: python django django-rest-framework

我正在尝试使我的自定义过滤器和排序后端与Django rest框架中的默认搜索后端一起使用。过滤和排序可以很好地相互配合,但是当查询中包含搜索并且我试图按对象名称对查询进行排序时,就会发生数据重复。

我尝试打印查询和查询大小,但是将其记录在过滤器中时似乎还可以,但是在响应中我有不同的对象计数(例如,过滤器查询中有79个对象,最终结果中有170个重复的对象)

这是我的过滤器集类

class PhonesFilterSet(rest_filters.FilterSet):
    brands = InListFilter(field_name='brand__id')
    os_ids = InListFilter(field_name='versions__os')
    version_ids = InListFilter(field_name='versions')
    launched_year_gte = rest_filters.NumberFilter(field_name='phone_launched_date__year', lookup_expr='gte')
    ram_gte = rest_filters.NumberFilter(field_name='internal_memories__value', method='get_rams')
    ram_memory_unit = rest_filters.NumberFilter(field_name='internal_memories__units', method='get_ram_units')

    def get_rams(self, queryset, name, value):
        #here is the problem filter
        #that not works with ordering by name
        q=queryset.filter(Q(internal_memories__memory_type=1) & Q(internal_memories__value__gte=value))
        print('filter_set', len(q))
        print('filter_set_query', q.query)
        return q


    def get_ram_units(self, queryset, name, value):
        return queryset.filter(Q(internal_memories__memory_type=1) & Q(internal_memories__units=value))


    class Meta:
        model = Phone
        fields = ['brands', 'os_ids', 'version_ids', 'status', 'ram_gte']

我的订购班:

class CustomFilterBackend(filters.OrderingFilter):
    allowed_custom_filters = ['ram', 'camera', 'year']

    def get_ordering(self, request, queryset, view):
        params = request.query_params.get(self.ordering_param)

        if params:
            fields = [param.strip() for param in params.split(',')]
            ordering = [f for f in fields if f in self.allowed_custom_filters]
            if ordering:
                return ordering

        # No ordering was included, or all the ordering fields were invalid

        return self.get_default_ordering(view)


    def filter_queryset(self, request, queryset, view):
        ordering = self.get_ordering(request, queryset, view)
        if ordering:
            if 'ram' in ordering:
                max_ram = Max('internal_memories__value', filter=Q(internal_memories__memory_type=1))
                queryset = queryset.annotate(max_ram=max_ram).order_by('-max_ram')
            elif 'camera' in ordering:
                max_camera = Max('camera_pixels__megapixels', filter=Q(camera_pixels__camera_type=0))    
                queryset = queryset.annotate(max_camera=max_camera).order_by('-max_camera')            
            elif 'year' in ordering:
                queryset = queryset.filter(~Q(phone_released_date=None)).order_by('-phone_released_date__year')
            elif 'name' in ordering:
                #here is the problem ordering
                #thats not working with filter
                #with one to many relations
                queryset = queryset.order_by('-brand__name', '-model__name')


        return queryset

Viewset类:

class PhoneViewSet(viewsets.ModelViewSet):
    queryset = Phone.objects.all()
    serializer_class = PhoneSerializer
    filter_backends = (filters.SearchFilter, CustomFilterBackend, django_filters.rest_framework.DjangoFilterBackend)
    search_fields = ('brand__name', 'model__name')
    ordering_fields = ('brand__name', 'model__name')
    filter_class = PhonesFilterSet

因此,当我应用带有过滤器和搜索的订购时,我期望没有数据重复。我的问题是,为什么过滤器和响应中的对象数量不同,而数据却在其中重复?我不知道从哪里开始调试。预先感谢。

1 个答案:

答案 0 :(得分:0)

使用distinct()应该可以解决此问题:

  

返回在SQL查询中使用QuerySet的新SELECT DISTINCT。这样可以从查询结果中消除重复的行。

     

默认情况下,QuerySet不会消除重复的行。实际上,这很少有问题,因为诸如Blog.objects.all()之类的简单查询不会带来重复结果行的可能性。但是,如果您的查询跨越多个表,则在评估QuerySet时可能会得到重复的结果。那是您使用distinct()的时候。

但是请注意,您仍然可能会得到重复的结果:

  

order_by()列中包含SQL SELECT调用中使用的任何字段。与distinct()结合使用有时会导致意外结果。如果按相关模型中的字段排序,则这些字段将添加到选定的列中,否则它们可能使重复的行看起来是不同的。由于多余的列不会出现在返回的结果中(它们仅用于支持排序),因此有时看起来好像正在返回不明显的结果。

https://docs.djangoproject.com/en/2.2/ref/models/querysets/#django.db.models.query.QuerySet.distinct

如果使用的是PostgreSQL,则可以指定DISTINCT应该应用的字段名称。这可能会有所帮助。 (我不确定。)有关此的更多信息,请参见上面的链接。

所以,我会return queryset.distinct()用在您认为遇到问题的方法中。我不会总是应用它(就像我在上面的注释中写的那样,用于调试),因为对于简单的查询您不需要它。