Создание пользовательских полевых поисков в 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 для производительности, но подумал, что было бы более ясно оставить это. Получайте удовольствие!