Фильтр по собственности

98

Можно ли фильтровать набор запросов Django по свойству модели?

у меня есть метод в моей модели:

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

и теперь я хочу отфильтровать это свойство, например:

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

это как-то возможно?

Schneck
источник
Он находится в SQLAlchemy: docs.sqlalchemy.org/en/latest/orm/extensions/hybrid.html, и вы можете подключить django к SQLAlchemy через pypi.python.org/pypi/aldjemy, но я сомневаюсь, что эти два могут быть связаны такими, какими вы хотите их видеть.
rattray

Ответы:

79

Неа. Фильтры Django работают на уровне базы данных, генерируя SQL. Для фильтрации на основе свойств Python вам необходимо загрузить объект в Python для оценки свойства - и к этому моменту вы уже выполнили всю работу по его загрузке.

Гленн Мейнард
источник
5
К несчастью, эта функция не реализована, было бы интересным расширением, по крайней мере, для отфильтровывания совпадающих объектов после создания набора результатов.
schneck
1
как с этим бороться в админке? Есть ли обходной путь?
andilabs
41

Возможно, я неправильно понимаю ваш исходный вопрос, но в 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())
Клинт
источник
16
Это работает для его фильтрации, если у вас есть объект Python, но он спрашивает о Django QuerySet.filter, который создает запросы SQL.
Glenn Maynard
1
верно, но, как объяснялось выше, я хотел бы добавить свойство в свой фильтр базы данных. фильтрация после выполнения запроса - это именно то, чего я хочу избежать.
schneck
20

Отказавшись от предлагаемого обходного пути @ TheGrimmScientist, вы можете создать эти «свойства sql», определив их в Manager или QuerySet, и повторно использовать / связать / составить их:

С менеджером:

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

затем составление списка по всем объектам.

Ученый Гримм
источник
5

У меня была такая же проблема, и я разработал это простое решение:

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)

С помощью этой универсальной подпрограммы я могу выбрать все те элементы, которые точно соответствуют моему словарю комбинаций «указать» (имя свойства, значение свойства).

Первый параметр принимает (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
И теперь, когда у меня было время , чтобы играть с ним немного: реальным недостаток этого подхода заключается в том, что querysets возвращаемого .r () не является ее полноправным querysets, с помощью которого я имею в виду есть методы QuerySet, которые отсутствуют:AttributeError: 'RawQuerySet' object has no attribute 'values'
hlongmore
1

Я знаю, что это старый вопрос, но ради тех, кто здесь прыгает, я думаю, было бы полезно прочитать приведенный ниже вопрос и относительный ответ:

Как настроить фильтр администратора в Django 1.4

ФСП
источник
1
Для тех, кто просматривает этот ответ - эта ссылка предназначена для информации о реализации фильтров списка в Django Admin с использованием «SimpleListFilter». Полезно, но не ответ на вопрос, за исключением очень специфического случая.
jenniwren
0

Он также может быть возможным использовать QuerySet аннотации , которые дублируют свойство Get / Set-логику, как это было предложено , например , с помощью @rattray и @thegrimmscientist , в сочетании сproperty . Это может дать что-то, что работает как на уровне Python, так и на уровне базы данных.

Однако не уверен в недостатках: см. Этот вопрос SO для примера.

djvg
источник
Ссылка на вопрос для проверки кода уведомляет о том, что автор удалил ее добровольно. Не могли бы вы обновить здесь свой ответ, добавив ссылку на код или объяснение, или просто удалите свой ответ?
hlongmore
@hlongmore: извините за это. Этот вопрос был перемещен в SO. Исправил ссылку выше.
djvg