按属性筛选


95

是否可以通过模型属性过滤Django查询集?

我的模型中有一个方法:

@property
def myproperty(self):
    [..]

现在我想按此属性进行过滤,例如:

MyModel.objects.filter(myproperty=[..])

这有可能吗?


它在SQLAlchemy中:docs.sqlalchemy.org/en/latest/orm/extensions/hybrid.html,您可以通过pypi.python.org/pypi/aldjemy将django与SQLAlchemy连接,但是我怀疑两者是否可以连接您希望他们成为的方式。
拉特雷

Answers:


78

不。Django过滤器在数据库级别运行,生成SQL。要基于Python属性进行过滤,您必须将对象加载到Python中以评估该属性-到那时,您已经完成了加载该对象的所有工作。


4
不幸的是,尚未实现此功能,这将是一个有趣的扩展,它至少可以在构建结果集过滤掉匹配的对象。
schneck

1
如何在管理员中处理?有一些解决方法吗?
andilabs 2014年

39

我可能会误解您的原始问题,但是python中内置了一个过滤器

filtered = filter(myproperty, MyModel.objects)

但是最好使用列表理解

filtered = [x for x in MyModel.objects if x.myproperty()]

甚至更好的是生成器表达式

filtered = (x for x in MyModel.objects if x.myproperty())

14
一旦有了Python对象,该方法就可以对其进行过滤,但是他正在询问有关构造SQL查询的Django QuerySet.filter的信息。
格伦·梅纳德

1
是的,但是如上所述,我想将该属性添加到数据库过滤器中。查询完成后的过滤正是我要避免的事情。
schneck

18

摆脱@TheGrimmScientist建议的解决方法,您可以通过在Manager或QuerySet上定义这些“ sql属性”,然后重新使用/链接/组成它们,来制成这些“ sql属性”:

与经理一起:

class CompanyManager(models.Manager):
    def with_chairs_needed(self):
        return self.annotate(chairs_needed=F('num_employees') - F('num_chairs'))

class Company(models.Model):
    # ...
    objects = CompanyManager()

Company.objects.with_chairs_needed().filter(chairs_needed__lt=4)

使用QuerySet:

class CompanyQuerySet(models.QuerySet):
    def many_employees(self, n=50):
        return self.filter(num_employees__gte=n)

    def needs_fewer_chairs_than(self, n=5):
        return self.with_chairs_needed().filter(chairs_needed__lt=n)

    def with_chairs_needed(self):
        return self.annotate(chairs_needed=F('num_employees') - F('num_chairs'))

class Company(models.Model):
    # ...
    objects = CompanyQuerySet.as_manager()

Company.objects.needs_fewer_chairs_than(4).many_employees()

有关更多信息,请参见https://docs.djangoproject.com/en/1.9/topics/db/managers/。请注意,我将关闭文档,并且尚未测试以上内容。


14

看起来将F()与批注一起使用将是我的解决方案。

它不会被过滤@property,因为F在将对象带入python之前会与数据库进行对话。但是仍然把它作为答案,因为我想要按属性过滤的原因实际上是希望通过对两个不同字段进行简单算术运算的结果来过滤对象。

因此,类似以下内容:

companies = Company.objects\
    .annotate(chairs_needed=F('num_employees') - F('num_chairs'))\
    .filter(chairs_needed__lt=4)

而不是将属性定义为:

@property
def chairs_needed(self):
    return self.num_employees - self.num_chairs

然后对所有对象进行列表理解。


4

我遇到了同样的问题,并开发了这个简单的解决方案:

objects_id = [x.id for x in MyModel.objects.all() if x.myProperty == [...]]
MyModel.objects.filter(id__in=objects_id)

我知道这不是最有效的解决方案,但在像我这样的简单情况下可能会有所帮助


3

请有人纠正我,但我想至少在我自己的情况下,我已经找到了解决方案。

我想处理所有属性完全等于...的元素。

但是我有几个模型,这个例程应该适用于所有模型。它确实:

def selectByProperties(modelType, specify):
    clause = "SELECT * from %s" % modelType._meta.db_table

    if len(specify) > 0:
        clause += " WHERE "
        for field, eqvalue in specify.items():
            clause += "%s = '%s' AND " % (field, eqvalue)
        clause = clause [:-5]  # remove last AND

    print clause
    return modelType.objects.raw(clause)

通过这个通用子例程,我可以选择与我的“ specify”(属性名称,属性值)组合的字典完全相等的所有元素。

第一个参数采用(models.Model),

第二个字典,例如:{“ property1”:“ 77”,“ property2”:“ 12”}

它创建了一条SQL语句,例如

SELECT * from appname_modelname WHERE property1 = '77' AND property2 = '12'

并在这些元素上返回QuerySet。

这是一个测试功能:

from myApp.models import myModel

def testSelectByProperties ():

    specify = {"property1" : "77" , "property2" : "12"}
    subset = selectByProperties(myModel, specify)

    nameField = "property0"
    ## checking if that is what I expected:
    for i in subset:
        print i.__dict__[nameField], 
        for j in specify.keys():
             print i.__dict__[j], 
        print 

和?你怎么看?


通常,这似乎是一个不错的解决方法。我不会说这是理想的选择,但是每次需要这样的东西时,都需要派出一个存储库来修改从PyPI安装的软件包的模型,这是值得的。
hlongmore

现在,我有一点时间来使用它:这种方法的真正缺点是.raw()返回的查询集不是完整的查询集,这意味着缺少一些查询集方法:AttributeError: 'RawQuerySet' object has no attribute 'values'
hlongmore

1

我知道这是一个古老的问题,但是对于那些跳到这里的人来说,我认为阅读下面的问题和相对答案很有用:

如何在Django 1.4中自定义管理过滤器


1
对于那些略过此答案的人-此链接是有关使用“ SimpleListFilter”在Django Admin中实现列表过滤器的信息。很有用,但除了非常特殊的情况外,不能回答问题。
jenniwren

0

它也可能通过使用查询集批注复制属性get / set-逻辑,建议如@rattray@thegrimmscientist会同property。这可能会产生在Python级别数据库级别都可以使用的东西。

但是,不确定其缺点:请参阅此SO问题作为示例。


您的代码查看问题链接会通知您,该链接已被其作者自愿删除。您介意在此处通过代码链接或说明来更新答案,还是只是删除答案?
hlongmore

@hlongmore:对不起。这个问题移至SO。我修复了上面的链接。
djvg
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.