Создание пользовательских полевых поисков в Django
Как вы создаете пользовательский поиск полей в Django?
При фильтрации запросов, django предоставляет набор поисковых запросов, которые вы можете использовать: __contains
, __iexact
, __in
и т.д. Я хочу, чтобы можно было найти новый поиск для моего менеджера, так, например, кто-то мог сказать:
twentysomethings = Person.objects.filter(age__within5=25)
и вернуть все объекты Person
с возрастом от 20 до 30. Нужно ли мне подклассифицировать класс QuerySet
или Manager
для этого? Как это будет реализовано?
Ответы
Ответ 1
Как и в 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)
.
Ответ 2
Более гибкий способ сделать это - написать пользовательский 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)
Ответ 3
Вместо того, чтобы создавать поиск по полю, лучшей практикой было бы создать метод менеджера, который может выглядеть примерно так:
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)
Ответ 4
Во-первых, позвольте мне сказать, что не существует механизма Django, который должен публично облегчить то, что вы хотите.
(Edit - на самом деле с Django 1.7 есть: https://docs.djangoproject.com/en/1.7/howto/custom-lookups/)
Тем не менее, если вы действительно хотите это сделать, подкласс QuerySet
и переопределить метод _filter_or_exclude()
. Затем создайте пользовательский менеджер, который возвращает только пользовательский QuerySet
(или обезьяна-патч Django QuerySet
, yuck). Мы делаем это в neo4django для повторного использования как можно большего количества кода запроса Django ORM при создании объектов Query
, специфичных для Neo4j.
Попробуйте что-нибудь (примерно), как это, адаптировано из ответа Заха. Я оставил фактическую обработку ошибок для синтаксического анализа поля в качестве упражнения для читателя:)
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 для производительности, но подумал, что было бы более ясно оставить это. Получайте удовольствие!