Filtrer par propriété

95

Est-il possible de filtrer un ensemble de requêtes 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 d'une manière ou d'une autre?

schneck
la source
C'est dans SQLAlchemy: docs.sqlalchemy.org/en/latest/orm/extensions/hybrid.html et vous pouvez connecter django à SQLAlchemy via pypi.python.org/pypi/aldjemy mais je doute que les deux puissent être connectés la façon dont vous voulez qu’elles soient.
rattray

Réponses:

78

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é - et à ce stade, vous avez déjà fait tout le travail pour le charger.

Glenn Maynard
la source
5
la malchance que cette fonctionnalité ne soit pas implémentée, serait une extension intéressante pour au moins filtrer les objets correspondants après la construction du jeu de résultats.
schneck
1
comment y faire face en admin? Existe-t-il une solution de contournement?
andilabs
39

Je ne comprends peut-être pas votre question initiale, mais il existe un filtre intégré en python.

filtered = filter(myproperty, MyModel.objects)

Mais il est préférable d'utiliser une compréhension de liste :

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

ou mieux encore, une expression génératrice :

filtered = (x for x in MyModel.objects if x.myproperty())
Clint
la source
15
Cela fonctionne pour le filtrer une fois que vous avez un objet Python, mais il pose des questions sur Django QuerySet.filter, qui construit des requêtes SQL.
Glenn Maynard
1
à droite, mais comme expliqué ci-dessus, je voudrais ajouter la propriété à mon filtre de base de données. le filtrage après que la requête a été faite est exactement ce que je veux éviter.
schneck
19

En supprimant la solution de contournement suggérée par @ TheGrimmScientist, vous pouvez créer ces "propriétés sql" en les définissant sur le Manager ou le QuerySet, et réutiliser / chaîner / les composer:

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/en/1.9/topics/db/managers/ pour plus d'informations. Notez que je sors de la documentation et que je n'ai pas testé ce qui précède.

hochet
la source
14

On dirait que l' utilisation de F () avec des annotations sera ma solution à cela.

Il ne va pas filtrer @property, car il Fparle à la base de données avant que les objets ne soient introduits dans Python. Mais toujours en le mettant ici comme réponse car ma raison de vouloir 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.

donc, quelque chose du genre:

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 compréhension de liste sur tous les objets.

TheGrimmScientist
la source
5

J'ai eu le même problème et j'ai développé cette solution simple:

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

Je sais que ce n'est pas la solution la plus performante, mais peut aider dans des cas simples comme le mien

Vitaliser
la source
3

VEUILLEZ que quelqu'un me corrige, mais je suppose que j'ai trouvé une solution, au moins pour mon propre cas.

Je veux travailler sur tous ces é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 il 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» (nom de propriété, valeur de propriété).

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 renvoie 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?

Akrueger
la source
En général, cela semble être un travail décent. Je ne dirais pas que c'est l'idéal, mais il vaut mieux devoir créer un référentiel pour modifier le modèle des packages que vous avez installés à partir de PyPI à chaque fois que vous avez besoin de quelque chose comme ça.
hlongmore
Et maintenant que j'ai eu le temps de jouer un peu avec: le vrai inconvénient de cette approche est que les ensembles de requêtes renvoyés par .raw () ne sont pas des ensembles de requêtes à part entière, ce qui signifie qu'il y a des méthodes d'ensembles de requêtes qui manquent:AttributeError: 'RawQuerySet' object has no attribute 'values'
hlongmore
1

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

FSp
la source
1
Pour ceux qui parcourent cette réponse - ce lien est vers des informations sur l'implémentation des filtres de liste dans Django Admin en utilisant "SimpleListFilter". Utile, mais pas une réponse à la question sauf dans un cas très particulier.
jenniwren
0

Il peut également être possible d'utiliser des annotations de jeu de requêtes qui dupliquent la propriété get / set-logic, comme suggéré par exemple par @rattray et @thegrimmscientist , en conjonction avec le 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 cette question SO pour un exemple.

djvg
la source
Le lien de votre question de révision de code indique qu'il a été volontairement supprimé par son auteur. Pourriez-vous mettre à jour votre réponse ici, soit avec un lien vers le code, soit avec une explication, ou simplement supprimer votre réponse?
hlongmore
@hlongmore: Désolé pour ça. Cette question a été déplacée vers SO. J'ai corrigé le lien ci-dessus.
djvg