如何在 Django 中创建自定义字段查找?
在过滤查询集时,django 提供了一组可以使用的查找:__contains
、__iexact
、__in
等等。我希望能够为我的经理提供新的查找,例如,有人可能会说:
twentysomethings = Person.objects.filter(age__within5=25)
并取回所有Person
年龄在 20 到 30 岁之间的对象。我需要继承QuerySet
orManager
类来执行此操作吗?它将如何实施?
如何在 Django 中创建自定义字段查找?
在过滤查询集时,django 提供了一组可以使用的查找:__contains
、__iexact
、__in
等等。我希望能够为我的经理提供新的查找,例如,有人可能会说:
twentysomethings = Person.objects.filter(age__within5=25)
并取回所有Person
年龄在 20 到 30 岁之间的对象。我需要继承QuerySet
orManager
类来执行此操作吗?它将如何实施?
一种更灵活的方法是编写自定义 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)
最好的做法是创建一个管理器方法,而不是创建一个字段查找,它可能看起来有点像这样:
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)
首先,让我说没有 Django 机器可以公开地促进你想要的东西。
(编辑 - 实际上从 Django 1.7 开始就有:https ://docs.djangoproject.com/en/1.7/howto/custom-lookups/ )
也就是说,如果您真的想完成此操作,请子类QuerySet
化并覆盖该_filter_or_exclude()
方法。然后创建一个自定义管理器,它只返回您的自定义QuerySet
(或猴子补丁 Django 的QuerySet
,呸)。我们在neo4djangoQuery
中这样做是为了在构建 Neo4j 特定对象时尽可能多地重用 Django ORM 查询集代码。
尝试(大致)这样的东西,改编自扎克的回答。我已经将字段查找解析的实际错误处理作为练习留给读者:)
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 来提高性能,但认为将其排除在外会更清楚。玩得开心!
从 Django 1.7 开始,有一种简单的方法可以实现它。您的示例实际上与文档中的示例非常相似:
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)
。