web-dev-qa-db-fra.com

Filtrer par propriété

Est-il possible de filtrer une requête Django par propriété de modèle?

j'ai une méthode dans mon modèle:

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

et maintenant je veux filtrer par cette propriété comme:

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

est-ce possible?

66
schneck

Nan. Les filtres Django fonctionnent au niveau de la base de données, générant du SQL. Pour filtrer en fonction des propriétés Python, vous devez charger l'objet dans Python pour évaluer la propriété. À ce stade, vous avez déjà effectué tout le travail pour le charger.

58
Glenn Maynard

Je comprends peut-être mal votre question initiale, mais il existe un filtre filter en python.

filtered = filter(myproperty, MyModel.objects)

Mais il vaut mieux utiliser une compréhension list :

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

ou mieux encore, une expression generator :

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

On dirait que utiliser F() avec des annotations sera ma solution à cela.

Il ne va pas filtrer par @property, car F parle à la base de données avant que les objets ne soient importés en python. Mais répondons toujours ici car la raison pour laquelle je voulais filtrer par propriété était vraiment de vouloir filtrer les objets par le résultat d’une simple arithmétique sur deux champs différents.

alors, quelque chose dans les lignes de:

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

plutôt que de définir la propriété comme étant:

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

puis faire une liste de compréhension sur tous les objets.

11
TheGrimmScientist

En supprimant la solution de contournement suggérée par @ TheGrimmScientist, vous pouvez créer ces "propriétés SQL" en les définissant dans le gestionnaire ou le QuerySet, puis en les réutilisant/les en chaîne:

Avec un manager:

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)

Avec un 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()

Voir https://docs.djangoproject.com/fr/1.9/topics/db/managers/ pour plus d'informations. Notez que je quitte la documentation et n’ai pas testé ce qui précède. 

8
rattray

S'IL VOUS PLAÎT quelqu'un me corrige, mais je suppose que j'ai trouvé une solution, au moins pour mon propre cas.

Je veux travailler sur tous les éléments dont les propriétés sont exactement égales à ... peu importe. 

Mais j'ai plusieurs modèles et cette routine devrait fonctionner pour tous les modèles. Et ça fait:

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)

Avec ce sous-programme universel, je peux sélectionner tous les éléments qui correspondent exactement à mon dictionnaire de combinaisons 'spécifier' (propertyname, propertyvalue).

Le premier paramètre prend un (models.Model), 

le second un dictionnaire comme: {"property1": "77", "property2": "12"}

Et cela crée une instruction SQL comme

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

et retourne un QuerySet sur ces éléments.

Ceci est une fonction de test:

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 

Et? Qu'est-ce que tu penses?

3
akrueger

je sais que c’est une vieille question, mais pour ceux qui sautent ici, je pense qu’il est utile de lire la question ci-dessous et la réponse relative:

Comment personnaliser le filtre d'administration dans Django 1.4

1
FSp

Il est également possible d’utiliser des annotations de requêtes qui dupliquent la propriété get/set-logic, comme suggéré par exemple. par @rattray et @thegrimmscientist , en conjonction avec the property. Cela pourrait produire quelque chose qui fonctionne à la fois au niveau Python et au niveau de la base de données. 

Pas sûr des inconvénients, cependant: voir ma question CodeReview pour un exemple.

0
djvg