【问题标题】:Django Tastypie Advanced Filtering: How to do complex lookups with Q objectsDjango Tastypie 高级过滤:如何使用 Q 对象进行复杂查找
【发布时间】:2012-04-18 18:55:28
【问题描述】:

我有一个基本的 Django 模型,例如:

class Business(models.Model):
    name = models.CharField(max_length=200, unique=True)
    email = models.EmailField()
    phone = models.CharField(max_length=40, blank=True, null=True)
    description = models.TextField(max_length=500)

我需要对上述模型执行一个复杂的查询,例如:

qset = (
    Q(name__icontains=query) |
    Q(description__icontains=query) |
    Q(email__icontains=query)
    )
results = Business.objects.filter(qset).distinct()

我使用 tastypie 尝试了以下操作,但没有成功:

def build_filters(self, filters=None):
    if filters is None:
        filters = {}
    orm_filters = super(BusinessResource, self).build_filters(filters)

    if('query' in filters):
        query = filters['query']
        print query
        qset = (
                Q(name__icontains=query) |
                Q(description__icontains=query) |
                Q(email__icontains=query)
                )
        results = Business.objects.filter(qset).distinct()
        orm_filters = {'query__icontains': results}

    return orm_filters

在美味派的 Meta 类中,我将过滤设置为:

filtering = {
        'name: ALL,
        'description': ALL,
        'email': ALL,
        'query': ['icontains',],
    }

有什么想法可以解决这个问题吗?

谢谢 - 牛顿

【问题讨论】:

    标签: python django tastypie django-q


    【解决方案1】:

    在astevanovic的回答中采用这个想法并稍微清理一下,以下应该可以工作并且更简洁。

    主要区别在于 apply_filters 通过使用 None 作为键而不是 custom(可能与列名冲突)变得更加健壮。

    def build_filters(self, filters=None):
        if filters is None:
            filters = {}
        orm_filters = super(BusinessResource, self).build_filters(filters)
    
        if 'query' in filters:
            query = filters['query']
            qset = (
                    Q(name__icontains=query) |
                    Q(description__icontains=query) |
                    Q(email__icontains=query)
                    )
            orm_filters.update({None: qset}) # None is used as the key to specify that these are non-keyword filters
    
        return orm_filters
    
    def apply_filters(self, request, applicable_filters):
        return self.get_object_list(request).filter(*applicable_filters.pop(None, []), **applicable_filters)
        # Taking the non-keyword filters out of applicable_filters (if any) and applying them as positional arguments to filter()
    

    【讨论】:

      【解决方案2】:

      你在正确的轨道上。但是,build_filters 应该将资源查找转换为 ORM 查找。

      默认实现将基于__ 的查询关键字拆分为key_bits、值对,然后尝试在查找的资源与其ORM 等效项之间找到映射。

      您的代码不应该在那里应用过滤器,只构建它。这是一个改进和固定的版本:

      def build_filters(self, filters=None):
          if filters is None:
              filters = {}
          orm_filters = super(BusinessResource, self).build_filters(filters)
      
          if('query' in filters):
              query = filters['query']
              qset = (
                      Q(name__icontains=query) |
                      Q(description__icontains=query) |
                      Q(email__icontains=query)
                      )
              orm_filters.update({'custom': qset})
      
          return orm_filters
      
      def apply_filters(self, request, applicable_filters):
          if 'custom' in applicable_filters:
              custom = applicable_filters.pop('custom')
          else:
              custom = None
      
          semi_filtered = super(BusinessResource, self).apply_filters(request, applicable_filters)
      
          return semi_filtered.filter(custom) if custom else semi_filtered
      

      因为您使用的是 Q 对象,标准的 apply_filters 方法不够智能,无法应用您的自定义过滤键(因为没有),但是您可以快速覆盖它并添加一个名为“自定义”的特殊过滤器。这样,您的build_filters 可以找到合适的过滤器,构造它的含义并将其作为自定义传递给 apply_filters,它会直接应用它,而不是尝试将其值从字典中解压缩为一个项目。

      【讨论】:

      • 字典没有“扩展”方法。应该是:orm_filters.update({'custom': qset})
      • 此解决方案会导致调用数据库两次(对于 semi_filtered,然后是自定义过滤器)。一个稍微不同的代码对我有用: if 'custom' in applicable_filters: custom = applicable_filters.pop('custom') return Outreaches.objects.filter(custom) else: return super(OutreachResource, self).apply_filters(request, applicable_filters)
      【解决方案3】:

      我这样解决了这个问题:

      Class MyResource(ModelResource):
      
        def __init__(self, *args, **kwargs):
          super(MyResource, self).__init__(*args, **kwargs)
          self.q_filters = []
      
        def build_filters(self, filters=None):
          orm_filters = super(MyResource, self).build_filters(filters)
      
          q_filter_needed_1 = []
          if "what_im_sending_from_client" in filters:
            if filters["what_im_sending_from_client"] == "my-constraint":
              q_filter_needed_1.append("something to filter")
      
          if q_filter_needed_1:
            a_new_q_object = Q()
            for item in q_filter_needed:
              a_new_q_object = a_new_q_object & Q(filtering_DB_field__icontains=item)
            self.q_filters.append(a_new_q_object)
      
        def apply_filters(self, request, applicable_filters):
          filtered = super(MyResource, self).apply_filters(request, applicable_filters)
      
          if self.q_filters:
            for qf in self.q_filters:
              filtered = filtered.filter(qf)
            self.q_filters = []
      
          return filtered
      

      这种方法感觉比我见过的其他方法更清晰地分离了关注点。

      【讨论】:

      • 将特定于请求的信息放在资源实例上是一个非常糟糕的主意。所以self.q_filters.append(a_new_q_object)。这是因为在具有多个线程的部署环境中,您最终可能会导致一个请求的状态影响另一个请求的状态。因此,例如,在一个请求中构建的所有过滤器实际上可以应用于完全不同的过滤器,具体取决于时间。请参阅此处的文档:django-tastypie.readthedocs.io/en/latest/… 这是到处传递bundle 对象解决的问题。
      猜你喜欢
      • 1970-01-01
      • 2016-04-12
      • 2011-02-12
      • 2016-06-06
      • 2016-01-19
      • 2016-02-02
      • 2015-01-22
      • 1970-01-01
      • 2021-05-27
      相关资源
      最近更新 更多