如何将这些列表过滤器转换为数据库查询?

时间:2012-05-25 14:38:02

标签: django

修改

为了防止每个人阅读太多:问题是我不明白是什么

Q(tag_rep__tag=tag.id)

确实适用于此型号:

class Item(models.Model):

    tag_rep=generic.GenericRelation(TaggedItem)

很可能tag_rep__tag会返回标记列表,而不是单个标记。所以我不能像我做的那样做查询。我如何比较tag_rep__tag以确定它是否

  1. 包含任何标签列表
  2. 包含所有标签列表

  3. 我正在尝试为具有标记标记的项目构建基于查询集的tagfilter。我已经成功完成了过滤器的创建,但它们基于pythons filter并返回一个列表:

    class TagFilter(object):
    
        def __init__(self,any_tags,avoid_tags,all_tags):
             # all tags as tag instances
             self.any_tags = any_tags
             self.avoid_tags = avoid_tags
             self.all_tags = all_tags
    
        def seek_any_tags(self, item):
            for tag in self.any_tags:
                if tag in item.tags:
                   return True
            return False
    
        def items_with_any_tags(self,items):
            return filter(self.seek_any_tags,items)
    

    这很好用,但现在我需要一个queryset来传递给一个formset。因此,当前方法不再起作用,因为它返回一个列表。为了能够访问我模型的标签,我做了这个:

    from django.contrib.contenttypes import generic
    from tagging.models import TaggedItem
    
    class Item(models.Model):
    
        # some_fields here
    
        tag_rep = generic.GenericRelation(TaggedItem) 
    

    过滤器seek_any_tags是我唯一可以以数据库查询形式重建的过滤器。在阅读了一些SO帖子并进行了大量的谷歌搜索后,我想出了这个解决方案:

    def seek_any_tags_qs(self,items):
    
        if self.any_tags!=None:
            q = reduce(lambda x, y: x | y, [Q(tag_rep__tag=tag.id) for tag in self.any_tags])
            items = items.filter(q)
    
        return items
    

    非常好,是的,我为此感到有点自豪。但是现在还有两个我想构建的过滤器:avoid_any_tagsseek_all_tags。我尝试以与seek_any_tags过滤器相同的方式执行它们:

    def avoid_any_tags_qs(self,items):
    
        if self.avoid_tags != None:
            q = reduce(lambda x, y: x | y, [Q(tag_rep__tag=tag.id) for tag in self.avoid_tags])
            items = items.exclude(q)
    
        return items
    
    
    def seek_all_tags_qs(self,items):
    
        if self.all_tags != None:
            q = reduce(lambda x, y: x & y, [Q(_tags__contains=tag.name) for tag in self.seek_tags])
            items.filter(q)
    
        return items
    

    avoid_any_tags应排除具有avoid_tags中任何标记的每个项目。最后一个seek_all_tags应该只返回包含all_tags中所有标记的项目。

    但是avoid_any_tags无效。它只排除了在avoid_tags中没有标签的元素。如果某个项目的任何标记不在avoid_tags中,则不会将其排除。为什么不呢?

2 个答案:

答案 0 :(得分:1)

我认为这不是一个完全合适的查询(来自您的示例):

Q(tag_rep__tag=tag.id)

我假设您正在接近examples listed here,并且该TaggedItem的标记字段是一个slug(字符串)字段?所以马上过滤,过滤id等于slugfield似乎不对。此外,您的GenericRelation表示列表(或该问题的查询集)是正确的。它是一个反向引用,返回TaggedItem,指定其foreignkey字段。所以你可以这样做:Item.tag_rep.all()

你的问题中有太多额外的信息,它有点难以理解,所以我只是忽略了大部分内容并在顶部解决你的摘要。

# Any of a list of strings*
any_tags = ['foo', 'bar']
Item.objects.filter(tag_rep__tag__in=any_tags)

至于“在给定标签列表中包含所有标签”,也许有一个非常复杂的基于django查询的方法来做到这一点,但这里有一个方法,中间步骤在客户端:

# items having all tags in a given list of tags
from collections import defaultdict

tag_vals = Item.objects.all().values_list('id', 'tags__tag').distinct()
# produces result like:  `[(1, u'bdfl'), (1, u'boom'), (2, u'bar'), ...)`

all_tags = set(['boom', 'fizz'])

tag_groups = defaultdict(set)
for id_, tag in tag_vals:
    tag_groups[id_].add(tag)
# produces tag_groups like `{1: set([u'fizz', u'foo', u'boom']), ...}`

item_ids = [id_ for id_,tags in d.iteritems() if tags.issubset(all_tags)]
Item.objects.filter(id__in=item_ids)

在最后一个示例中,它将匹配Item具有标记['a,'b']且all_tags为['a','b',c']的情况,但它不匹配Item具有比all_tags中更多标记的情况,但是那些它匹配的。要使其匹配任何一种方式,您必须修改该item_ids过滤器:

item_ids = [id_ for id_,tags in d.iteritems() if \
                tags.issubset(all_tags) or tags.issuperset(all_tags)]
Item.objects.filter(id__in=item_ids)

答案 1 :(得分:0)

简而言之,这是有效的代码:

# does the item have any of the seek tags?
has_seek_tags = Q(tags__id__in=self.seek_tags)
items = items.filter(has_seek_tags).distinct()

# does the item have any of the avoid tags?    
has_avoid_tags= Q(tags__id__in=self.avoid_tags)
items = items.exclude(has_avoid_tags).distinct()

# now apply the queries
for tag_id in self.all_tags:
    items = items.filter(has_avoid_tags)

我还是不明白的是,为什么for循环中的items.filter与此不一样:

q = reduce(lambda x,y: x&y, [Q(tags__id__exact=tag_id) for tag_id in self.all_tags)
items = items.filter(q)

但也许这是一个自己的问题。