Filtruj według właściwości


98

Czy można filtrować zestaw zapytań Django według właściwości modelu?

mam metodę w swoim modelu:

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

a teraz chcę filtrować według tej właściwości, takiej jak:

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

czy to jest w jakiś sposób możliwe?


Jest w SQLAlchemy: docs.sqlalchemy.org/en/latest/orm/extensions/hybrid.html i można połączyć django z SQLAlchemy przez pypi.python.org/pypi/aldjemy, ale wątpię, czy można by je połączyć takie, jakie chcesz, żeby były.
rattray

Odpowiedzi:


79

Nie. Filtry Django działają na poziomie bazy danych, generując SQL. Aby filtrować na podstawie właściwości Pythona, musisz załadować obiekt do Pythona, aby ocenić właściwość - iw tym momencie wykonałeś już całą pracę, aby go załadować.


5
pech, że ta funkcja nie jest zaimplementowana, byłoby interesującym rozszerzeniem, które przynajmniej odfiltrowuje pasujące obiekty po zbudowaniu zestawu wyników.
schneck

1
jak sobie z tym poradzić w adminie? Czy jest jakieś obejście?
andilabs

41

Mogę źle zrozumieć twoje pierwotne pytanie, ale w Pythonie jest wbudowany filtr .

filtered = filter(myproperty, MyModel.objects)

Ale lepiej jest użyć rozumienia listy :

filtered = [x for x in MyModel.objects if x.myproperty()]

lub jeszcze lepiej, wyrażenie generatora :

filtered = (x for x in MyModel.objects if x.myproperty())

16
To działa, aby filtrować go, gdy masz obiekt Pythona, ale pyta o Django QuerySet.filter, który konstruuje zapytania SQL.
Glenn Maynard

1
racja, ale jak wyjaśniono powyżej, chciałbym dodać właściwość do mojego filtra bazy danych. Filtrowanie po wykonaniu zapytania jest dokładnie tym, czego chcę uniknąć.
schneck

20

Pomijając sugerowane obejście @ TheGrimmScientist, możesz utworzyć te „właściwości sql”, definiując je w Menedżerze lub QuerySet, a następnie ponownie użyć / połączyć / skomponować je:

Z menedżerem:

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)

Za pomocą 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()

Więcej informacji można znaleźć pod adresem https://docs.djangoproject.com/en/1.9/topics/db/managers/ . Zauważ, że wychodzę z dokumentacji i nie testowałem powyższego.


14

Wygląda na to, że moim rozwiązaniem będzie użycie F () z adnotacjami .

Nie będzie filtrować @property, ponieważ Frozmawia z bazą danych, zanim obiekty zostaną przeniesione do Pythona. Ale nadal umieszczam to tutaj jako odpowiedź, ponieważ moim powodem, dla którego chciałem filtrować według właściwości, było tak naprawdę chęć filtrowania obiektów według wyniku prostej arytmetyki na dwóch różnych polach.

więc coś w rodzaju:

companies = Company.objects\
    .annotate(chairs_needed=F('num_employees') - F('num_chairs'))\
    .filter(chairs_needed__lt=4)

zamiast definiować właściwość jako:

@property
def chairs_needed(self):
    return self.num_employees - self.num_chairs

następnie sporządzenie listy obejmującej wszystkie obiekty.


5

Miałem ten sam problem i opracowałem to proste rozwiązanie:

objects_id = [x.id for x in MyModel.objects.all() if x.myProperty == [...]]
MyModel.objects.filter(id__in=objects_id)

Wiem, że nie jest to najbardziej wydajne rozwiązanie, ale może pomóc w prostych przypadkach, takich jak moje


3

PROSZĘ, aby ktoś mnie poprawił, ale myślę, że znalazłem rozwiązanie, przynajmniej dla mojego własnego przypadku.

Chcę popracować nad wszystkimi elementami, których właściwości są dokładnie równe… cokolwiek.

Ale mam kilka modeli i ta procedura powinna działać dla wszystkich modeli. I tak:

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)

Dzięki tej uniwersalnej podprogramie mogę wybrać wszystkie te elementy, które dokładnie odpowiadają mojemu słownikowi kombinacji „określić” (nazwa właściwości, wartość właściwości).

Pierwszy parametr przyjmuje (modeles.Model),

drugi to słownik, taki jak: {"property1": "77", "property2": "12"}

I tworzy instrukcję SQL, taką jak

SELECT * from appname_modelname WHERE property1 = '77' AND property2 = '12'

i zwraca QuerySet dla tych elementów.

To jest funkcja testowa:

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 

I? Co myślisz?


Ogólnie wydaje się, że to przyzwoita praca. Nie powiedziałbym, że jest to idealne rozwiązanie, ale pokonuje konieczność rozwidlania repozytorium w celu zmodyfikowania modelu pakietów zainstalowanych z PyPI za każdym razem, gdy potrzebujesz czegoś takiego.
hlongmore

A teraz, kiedy miałem czas trochę się tym pobawić: prawdziwym minusem tego podejścia jest to, że querysets zwracane przez .raw () nie są pełnoprawnymi zestawami zapytań, przez co rozumiem, że brakuje metod queryset:AttributeError: 'RawQuerySet' object has no attribute 'values'
hlongmore

1

Wiem, że to stare pytanie, ale ze względu na skaczących tutaj myślę, że warto przeczytać poniższe pytanie i względną odpowiedź:

Jak dostosować filtr administratora w Django 1.4


1
Dla tych, którzy przeglądają tę odpowiedź - ten odsyłacz prowadzi do informacji o implementowaniu filtrów list w panelu Django Admin przy użyciu „SimpleListFilter”. Przydatna, ale bez odpowiedzi na pytanie, z wyjątkiem bardzo konkretnego przypadku.
jenniwren

0

Może to być również możliwe użycie adnotacji queryset które powielają się nieruchomość get / set-Logic, jak sugeruje np @rattray i @thegrimmscientist , w połączeniu zproperty . Może to dać coś, co działa zarówno na poziomie Pythona, jak i na poziomie bazy danych.

Nie jestem jednak pewien wad: zobacz przykład w tym pytaniu SO .


Link do pytania do recenzji kodu informuje, że został on dobrowolnie usunięty przez jego autora. Czy możesz zaktualizować swoją odpowiedź w tym miejscu, czy to za pomocą linku do kodu, czy z wyjaśnieniem, czy po prostu usunąć odpowiedź?
hlongmore

@hlongmore: Przepraszam za to. To pytanie zostało przeniesione do SO. Poprawiłem powyższy link.
djvg
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.