【问题标题】:Creating custom Field Lookups in Django在 Django 中创建自定义字段查找
【发布时间】:2011-01-13 06:37:01
【问题描述】:

如何在 Django 中创建自定义 field lookups

过滤查询集时,django 提供了一组可供您使用的查找:__contains__iexact__in,等等。我希望能够为我的经理提供新的查找,例如,有人可能会说:

twentysomethings = Person.objects.filter(age__within5=25)

并取回所有年龄在 20 到 30 岁之间的 Person 对象。我是否需要继承 QuerySetManager 类来执行此操作?将如何实施?

【问题讨论】:

  • 这些答案对他的例子很有用(这可能是也可能不是他只是为了表达自己的观点而抛出的东西)。但如果有人能回答这个问题,我会很高兴的问。
  • @royal 我想我的回答已经涵盖了它——我在我正在研究的图书馆中发现了这个问题。

标签: python django django-queryset


【解决方案1】:

更灵活的方法是编写自定义 QuerySet 以及自定义管理器。使用 ozan 的代码:

class PersonQuerySet(models.query.QuerySet):
    def in_age_range(self, min, max):
        return self.filter(age__gte=min, age__lt=max)

class PersonManager(models.Manager):
    def get_query_set(self):
         return PersonQuerySet(self.model)

    def __getattr__(self, name):
        return getattr(self.get_query_set(), name)

class Person(models.Model):
    age = #...

    objects = PersonManager()

这允许您链接自定义查询。所以这两个查询都是有效的:

Person.objects.in_age_range(20,30)

Person.objects.exclude(somefield = some_value).in_age_range(20, 30)

【讨论】:

  • 太棒了!我仍然有点希望有一些 API 用于挂钩查找字段语法,以利用关系遍历逻辑。我会继续挖掘。与此同时,我认为这让我更接近于理解这些部分是如何联系在一起的。谢谢!
  • 您可以使用django-qmethod 更轻松/更干净地执行此操作。好像有一段时间没更新了,但是我用了很多年了,从来没有遇到过问题。
【解决方案2】:

最好的做法是创建一个管理器方法,而不是创建一个字段查找,它可能看起来有点像这样:

class PersonManger(models.Manager):
    def in_age_range(self, min, max):
        return self.filter(age__gte=min, age__lt=max)

class Person(models.Model):
    age = #...

    objects = PersonManager()

那么用法是这样的:

twentysomethings = Person.objects.in_age_range(20, 30)

【讨论】:

    【解决方案3】:

    首先,我要说的是,没有 Django 机制可以公开地促进你想要的东西。

    (编辑 - 实际上从 Django 1.7 开始就有:https://docs.djangoproject.com/en/1.7/howto/custom-lookups/

    也就是说,如果您真的想要完成此操作,请将QuerySet 子类化并覆盖_filter_or_exclude() 方法。然后创建一个自定义管理器,它只返回您的自定义 QuerySet(或猴子补丁 Django 的 QuerySet,呸)。我们在 neo4django 中执行此操作,以便在构建 Neo4j 特定的 Query 对象时尽可能多地重用 Django ORM 查询集代码。

    尝试(大致)这样的东西,改编自 Zach 的回答。我已经将字段查找解析的实际错误处理作为练习留给读者:)

    class PersonQuerySet(models.query.QuerySet):
        def _filter_or_exclude(self, negate, *args, **kwargs):
            cust_lookups = filter(lambda s: s[0].endswith('__within5'), kwargs.items())
            for lookup in cust_lookups:
                kwargs.pop(lookup[0])
                lookup_prefix = lookup[0].rsplit('__',1)[0]
                kwargs.update({lookup_prefix + '__gte':lookup[1]-5,
                               lookup_prefix + '__lt':lookup[1]+5})
            return super(PersonQuerySet, self)._filter_or_exclude(negate, *args, **kwargs)
    
    class PersonManager(models.Manager):
        def get_query_set(self):
             return PersonQuerySet(self.model)
    
    class Person(models.Model):
        age = #...
    
        objects = PersonManager()
    

    最后的评论 - 显然,如果您想链接自定义字段查找,这将变得非常麻烦。另外,我通常会在功能上写得更多一些,并使用 itertools 来提高性能,但认为将其排除在外会更清楚。玩得开心!

    【讨论】:

    • 谢谢老兄,这很有帮助。
    • 刚刚找到你的答案!非常感谢。这就是我一直在寻找的。​​span>
    【解决方案4】:

    从 Django 1.7 开始,有一种简单的方法可以实现它。您的示例实际上与the documentation 中的示例非常相似:

    from django.db.models import Lookup
    
    class AbsoluteValueLessThan(Lookup):
        lookup_name = 'lt'
    
        def as_sql(self, qn, connection):
            lhs, lhs_params = qn.compile(self.lhs.lhs)
            rhs, rhs_params = self.process_rhs(qn, connection)
            params = lhs_params + rhs_params + lhs_params + rhs_params
            return '%s < %s AND %s > -%s' % (lhs, rhs, lhs, rhs), params
    
    AbsoluteValue.register_lookup(AbsoluteValueLessThan)
    

    注册时,您可以直接使用Field.register_lookup(AbsoluteValueLessThan)

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-04-16
    • 1970-01-01
    • 2014-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-29
    • 2017-05-14
    相关资源
    最近更新 更多