Django-fr

Forum

#1 10-12-2012 14:24:40

Nicolas Steinmetz
Membre
Inscription : 11-08-2010
Messages : 96

Taggit et les filtres en AND

Hello,

Cherchant à rationnaliser différentes bases de codes, j'ai commencé à
migrer une app de gestion de marks en Flask/MongoDB vers Django/Postgres.
Comme il y a des tags aux marks, je ne me suis tourné vers django-taggit
mais je n'aurais peut être pas du.

En effet, lors d'une recherche multi tags, il fait du OR au lieu de AND et
sauf erreur, il ne sait pas le gérer. Avant que je désosse tout, je me suis
dit que la sagesse populaire de django-fr pourrait me remettre sur le bon
chemin.

Ex :

>>> from marks.models import Mark
>>> Mark.objects.filter(tag__name__in=['svg', 'xml']).count()
55
>>> Mark.objects.filter(tag__name__in=['svg']).count()
10
>>> Mark.objects.filter(tag__name__in=['xml']).count()
45
>>>
Mark.objects.filter(tag__name__exact='xml').filter(tag__name__exact='svg').count()
1

Ce qui est pratique avec le tag__name__in=<list>, c'est que je peux
justement lui passer une liste de 1 à n termes ; contrairement au dernier
exemple qui demande à ce que je sache le nombre de termes à filtrer.

Est-ce que quelqu'un sait comment je peux faire cette fichue requête pour
avoir un AND au lieu d'un OR ?

Une alternative à proposer ? J'espère ne surtout pas avoir à tomber dans du
raw sql ; ce serait indigne de django pout un besoin aussi simple ;-)

Merci d'avance,
NIcolas

Hors ligne

#2 10-12-2012 14:32:28

David Larlet
Membre
Inscription : 11-08-2010
Messages : 102

Re : Taggit et les filtres en AND

Le 10 déc. 2012 à 14:24, Nicolas Steinmetz <nsteinmetz _AT_ gmail.com> a écrit :
>
> Cherchant à rationnaliser différentes bases de codes, j'ai commencé à migrer une app de gestion de marks en Flask/MongoDB vers Django/Postgres. Comme il y a des tags aux marks, je ne me suis tourné vers django-taggit mais je n'aurais peut être pas du.
>
> En effet, lors d'une recherche multi tags, il fait du OR au lieu de AND et sauf erreur, il ne sait pas le gérer. Avant que je désosse tout, je me suis dit que la sagesse populaire de django-fr pourrait me remettre sur le bon chemin.

Hello Nicolas,

Je te conseille de regarder du côté des Q objects pour ça : https://docs.djangoproject.com/en/dev/topics/db/queries/#complex-lookups-with-q-objects

Bonne journée,
David

Hors ligne

#3 10-12-2012 14:36:56

Rémy HUBSCHER
Membre
Inscription : 11-08-2010
Messages : 161

Re : Taggit et les filtres en AND

Salut,

Intéressante question.
J'ai cherché un peu et j'ai trouvé ça :
https://groups.google.com/forum/?fromgroups=#!topic/django-users/X9TCSrBn57Y

Mais ça réponds partiellement à la question.

La bonne réponse c'est : Il n'y a pas de __exact__in

On ne peut pas faire de filter(tag__name__exact='xml',
tag__name__exact='svg') par ce qu'il chercherait à trouver un élément
dans les deux.

La meilleure solution AMHA c'est de faire un queryset par tag et de les
merger.

>>> from marks.models import Mark
>>> queryset = Mark.objects.none()
>>> queryset += Mark.objects.filter(tag_name__exact='xml')
>>> queryset += Mark.objects.filter(tag_name__exact='svg')

Tu dois pouvoir mettre ça dans un manager pour que ce soit plus joli.

    from djqmixin import Manager, QMixin

    class MarkQMixin(QMixin):
         def exact_in(tags):
             queryset = self.none()
             for tag in tags:
                 queryset += self.filter(tag_name__exact=tag)
             return queryset


Comme expliqué dans l'article ci-dessus c'est une mauvaise idée de
chainer les .filter car ça va créer une jointure à chaque fois et avec
un grand nombre de tag ça risque de tout casser.

Cordialement,

Rémy

Le 10/12/2012 14:24, Nicolas Steinmetz a écrit :
> Hello,
>
> Cherchant à rationnaliser différentes bases de codes, j'ai commencé à
> migrer une app de gestion de marks en Flask/MongoDB vers
> Django/Postgres. Comme il y a des tags aux marks, je ne me suis tourné
> vers django-taggit mais je n'aurais peut être pas du.
>
> En effet, lors d'une recherche multi tags, il fait du OR au lieu de
> AND et sauf erreur, il ne sait pas le gérer. Avant que je désosse
> tout, je me suis dit que la sagesse populaire de django-fr pourrait me
> remettre sur le bon chemin.
>
> Ex :
>
> >>> from marks.models import Mark
> >>> Mark.objects.filter(tag__name__in=['svg', 'xml']).count()
> 55
> >>> Mark.objects.filter(tag__name__in=['svg']).count()
> 10
> >>> Mark.objects.filter(tag__name__in=['xml']).count()
> 45
> >>>
> Mark.objects.filter(tag__name__exact='xml').filter(tag__name__exact='svg').count()
> 1
>
> Ce qui est pratique avec le tag__name__in=<list>, c'est que je peux
> justement lui passer une liste de 1 à n termes ; contrairement au
> dernier exemple qui demande à ce que je sache le nombre de termes à
> filtrer.
>
> Est-ce que quelqu'un sait comment je peux faire cette fichue requête
> pour avoir un AND au lieu d'un OR ?
>
> Une alternative à proposer ? J'espère ne surtout pas avoir à tomber
> dans du raw sql ; ce serait indigne de django pout un besoin aussi
> simple ;-)
>
> Merci d'avance,
> NIcolas
>
> --
> Nicolas Steinmetz
> http://www.steinmetz.fr - http://nicolas.steinmetz.fr/
>
>
> _______________________________________________
> django mailing list
> django _AT_ lists.afpy.org
> http://lists.afpy.org/mailman/listinfo/django

Hors ligne

#4 10-12-2012 14:40:26

Rémy HUBSCHER
Membre
Inscription : 11-08-2010
Messages : 161

Re : Taggit et les filtres en AND

Le 10/12/2012 14:32, David Larlet a écrit :
> Le 10 déc. 2012 à 14:24, Nicolas Steinmetz <nsteinmetz _AT_ gmail.com> a écrit :
>> Cherchant à rationnaliser différentes bases de codes, j'ai commencé à migrer une app de gestion de marks en Flask/MongoDB vers Django/Postgres. Comme il y a des tags aux marks, je ne me suis tourné vers django-taggit mais je n'aurais peut être pas du.
>>
>> En effet, lors d'une recherche multi tags, il fait du OR au lieu de AND et sauf erreur, il ne sait pas le gérer. Avant que je désosse tout, je me suis dit que la sagesse populaire de django-fr pourrait me remettre sur le bon chemin.
> Hello Nicolas,
>
> Je te conseille de regarder du côté des Q objects pour ça : https://docs.djangoproject.com/en/dev/topics/db/queries/#complex-lookups-with-q-objects
>
> Bonne journée,
> David
>
> _______________________________________________
> django mailing list
> django _AT_ lists.afpy.org
> http://lists.afpy.org/mailman/listinfo/django
C'est une bonne idée. Par contre comment tu peux automatiser la création
des Q object à partir d'une liste ?

Hors ligne

#5 10-12-2012 14:43:35

Rémy HUBSCHER
Membre
Inscription : 11-08-2010
Messages : 161

Re : Taggit et les filtres en AND

Le 10/12/2012 14:32, David Larlet a écrit :
> Le 10 déc. 2012 à 14:24, Nicolas Steinmetz <nsteinmetz _AT_ gmail.com> a écrit :
>> Cherchant à rationnaliser différentes bases de codes, j'ai commencé à migrer une app de gestion de marks en Flask/MongoDB vers Django/Postgres. Comme il y a des tags aux marks, je ne me suis tourné vers django-taggit mais je n'aurais peut être pas du.
>>
>> En effet, lors d'une recherche multi tags, il fait du OR au lieu de AND et sauf erreur, il ne sait pas le gérer. Avant que je désosse tout, je me suis dit que la sagesse populaire de django-fr pourrait me remettre sur le bon chemin.
> Hello Nicolas,
>
> Je te conseille de regarder du côté des Q objects pour ça : https://docs.djangoproject.com/en/dev/topics/db/queries/#complex-lookups-with-q-objects
>
> Bonne journée,
> David
>
> _______________________________________________
> django mailing list
> django _AT_ lists.afpy.org
> http://lists.afpy.org/mailman/listinfo/django
Oui c'est bien meilleur en effet :

    from djqmixin import Manager, QMixin

    from django.db.models import Q

             class MarkQMixin(QMixin):
                 def exact_in(tags):
                     tag_list = Q()
                     for tag in tags:
                         tag_list = tag_list | Q(tag_name__exact=tag)
                     return self.filter(tag_list)

ça me semble bien meilleur comme ça avec une seule requête plutôt qu'une
par tag.

Hors ligne

#6 10-12-2012 14:47:35

David Larlet
Membre
Inscription : 11-08-2010
Messages : 102

Re : Taggit et les filtres en AND

Le 10 déc. 2012 à 14:43, Rémy Hubscher <hubscher.remy _AT_ gmail.com> a écrit :
>
> Oui c'est bien meilleur en effet :
> from djqmixin import Manager, QMixin
> from django.db.models import Q
>
>
>         class MarkQMixin(QMixin):
>             def exact_in(tags):
>                 tag_list = Q()
>                 for tag in tags:
>                     tag_list = tag_list | Q(tag_name__exact=tag)
>                 return self.filter(tag_list)
>
>
> ça me semble bien meilleur comme ça avec une seule requête plutôt qu'une par tag.

Tu peux même aller plus loin avec du dynamique, du style :

    q = Q()
    q.add(Q(**{key: value}), Q.OR)

Mais pas sûr que ça réponde encore à la question originale wink

David

Hors ligne

#7 10-12-2012 15:49:15

Nicolas Steinmetz
Membre
Inscription : 11-08-2010
Messages : 96

Re : Taggit et les filtres en AND

Le 10 décembre 2012 14:43, Rémy Hubscher <hubscher.remy _AT_ gmail.com> a écrit :

> Oui c'est bien meilleur en effet :
>
> from djqmixin import Manager, QMixin
>
> from django.db.models import Q
>
>         class MarkQMixin(QMixin):
>             def exact_in(tags):
>                 tag_list = Q()
>                 for tag in tags:
>                     tag_list = tag_list | Q(tag_name__exact=tag)
>                 return self.filter(tag_list)
>
>
>  ça me semble bien meilleur comme ça avec une seule requête plutôt qu'une
> par tag.
>

J'avais exclu un peu hâtivement les Q() car le 1er paragraphe mettait en
avant les "OR" contrairement au "AND" des filtres de bases.

Du coup, j'en profite pour découvrir django-qmixin smile

Grâce à la déclaration du mixin, je suis sensé pouvoir faire :

Mark.objects.exact_in(['svg', 'xml'])

Si j'en crois le bout de doc et les erreurs que ça remonte, il manque une
définition de self :

>>> from marks.models import Mark
>>> Mark.objects.exact_in(['svg', 'xml'])
Traceback (most recent call last):
  File "<console>", line 1, in <module>
TypeError: exact_in() takes exactly 1 argument (2 given)

>>> Mark.objects.exact_in()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/Users/nsteinmetz/Documents/Projets/django/hsh/marks/models.py",
line 17, in exact_in
    return self.filter(tag_list)
NameError: global name 'self' is not defined

J'ai essayé avec :
    def exact_in(self, tags):
        ...

mais ça coince encore ; la tag_list n'est pas récupérable via self ?

Un peu rouillé dans mon code et les imbrications avec les mixins :-/

Hors ligne

#8 10-12-2012 15:55:29

Rémy HUBSCHER
Membre
Inscription : 11-08-2010
Messages : 161

Re : Taggit et les filtres en AND

Effectivement il manque le self.

Le tag_list est construit à partir de la liste passée en paramètre.

la bonne déclaration doit être :

Le 10/12/2012 15:49, Nicolas Steinmetz a écrit :
>
>     from djqmixin import Manager, QMixin
>
>     from django.db.models import Q
>
>              class MarkQMixin(QMixin):
>                  def exact_in(self, tags):
>                      tag_list = Q()
>                      for tag in tags:
>                          tag_list = tag_list | Q(tag_name__exact=tag)
>                      return self.filter(tag_list)
>
Ensuite on l'utilise comme ça :

>>> Mark.objects.exact_in(['svg', 'xml'])

Hors ligne

#9 10-12-2012 17:20:04

Nicolas Steinmetz
Membre
Inscription : 11-08-2010
Messages : 96

Re : Taggit et les filtres en AND

2012/12/10 Rémy Hubscher <hubscher.remy _AT_ gmail.com>

>  Effectivement il manque le self.
>

Et de mettre la class "dans" celle de mon modèle parent ; déduit de part
les écarts d'indentation smile

Reste que là, on reste dans du "OR" et pas dans du "AND"

>>>
Mark.objects.filter(tag__name__exact='xml').filter(tag__name__exact='svg').count()
1
>>> Mark.objects.exact_in(['svg', 'xml']).count()
55
>>> Mark.objects.filter(tag__name__in=['svg','xml']).count()
55

A suivre...

Hors ligne

#10 10-12-2012 17:22:33

Rémy HUBSCHER
Membre
Inscription : 11-08-2010
Messages : 161

Re : Taggit et les filtres en AND

Pourrais-tu me faire un export SQL que je fasse quelques tests ?

Le 10/12/2012 17:20, Nicolas Steinmetz a écrit :
> 2012/12/10 Rémy Hubscher <hubscher.remy _AT_ gmail.com
> <mailto:hubscher.remy _AT_ gmail.com>>
>
>     Effectivement il manque le self.
>
>
> Et de mettre la class "dans" celle de mon modèle parent ; déduit de
> part les écarts d'indentation smile
>
> Reste que là, on reste dans du "OR" et pas dans du "AND"
>
> >>>
> Mark.objects.filter(tag__name__exact='xml').filter(tag__name__exact='svg').count()
> 1
> >>> Mark.objects.exact_in(['svg', 'xml']).count()
> 55
> >>> Mark.objects.filter(tag__name__in=['svg','xml']).count()
> 55
>
> A suivre...
> --
> Nicolas Steinmetz
> http://www.steinmetz.fr - http://nicolas.steinmetz.fr/
>
>
> _______________________________________________
> django mailing list
> django _AT_ lists.afpy.org
> http://lists.afpy.org/mailman/listinfo/django

Hors ligne

#11 10-12-2012 17:23:56

Florian Strzelecki
Membre
Inscription : 11-08-2010
Messages : 40

Re : Taggit et les filtres en AND

En fait, ton problème est beaucoup plus complexe qu'il n'en a l'air.

En gros, ce que tu veux faire, c'est ça :

"Récupérer tous les objets Mark qui sont liés à tous ces N tags."

En SQL, cela donnerait des choses relativement complexe, à base de EXISTS
et autres petites choses.
Donc, déjà, je t'invite à le faire en SQL, et à voir ce que tu peux faire
avec Django autour de ça... ça devrait déjà aider un peu.


Le 10 décembre 2012 17:20, Nicolas Steinmetz <nsteinmetz _AT_ gmail.com> a écrit
:

> 2012/12/10 Rémy Hubscher <hubscher.remy _AT_ gmail.com>
>
>>  Effectivement il manque le self.
>>
>
> Et de mettre la class "dans" celle de mon modèle parent ; déduit de part
> les écarts d'indentation smile
>
> Reste que là, on reste dans du "OR" et pas dans du "AND"
>
> >>>
> Mark.objects.filter(tag__name__exact='xml').filter(tag__name__exact='svg').count()
> 1
> >>> Mark.objects.exact_in(['svg', 'xml']).count()
> 55
> >>> Mark.objects.filter(tag__name__in=['svg','xml']).count()
> 55
>
> A suivre...
> --
> Nicolas Steinmetz
> http://www.steinmetz.fr - http://nicolas.steinmetz.fr/
>
> _______________________________________________
> django mailing list
> django _AT_ lists.afpy.org
> http://lists.afpy.org/mailman/listinfo/django
>

Hors ligne

#12 10-12-2012 18:42:47

Nicolas Steinmetz
Membre
Inscription : 11-08-2010
Messages : 96

Re : Taggit et les filtres en AND

Le 10 décembre 2012 17:23, Florian Strzelecki
<florian.strzelecki _AT_ gmail.com>a écrit :

> En fait, ton problème est beaucoup plus complexe qu'il n'en a l'air.
>
> En gros, ce que tu veux faire, c'est ça :
>
> "Récupérer tous les objets Mark qui sont liés à tous ces N tags."
>
> En SQL, cela donnerait des choses relativement complexe, à base de EXISTS
> et autres petites choses.
> Donc, déjà, je t'invite à le faire en SQL, et à voir ce que tu peux faire
> avec Django autour de ça... ça devrait déjà aider un peu.


En MongoDB, tu peux le faire avec l'opérateur "all" [1] ; ce qui me donnait
précédemment le code suivant :

bookmarks.query.filter({'tag':{'$all': tag_list}}).descending('created_at')

C'est peut être du coup le coté "relationnel" qui flanche dans le cas de ma
migration à postgres.

[1] http://docs.mongodb.org/manual/reference/operators/#_S_all

Dans postgres, cela semble possible de le faire avec les arrays :
http://www.craigkerstiens.com/2012/08/20/arrays-in-postgres/

Vais peut être partir sur ce principe là sauf que l'extension django pour
supporter les arrays postgresql ne s'affiche pas dans l'admin :
http://www.craigkerstiens.com/2012/11/06/django-and-arrays/
https://github.com/niwibe/djorm-ext-pgarray

Il semblerait qu'un fork commence à gérer ça (intégration dans l'admin)

smile
---
Nicolas Steinmetz
http://www.steinmetz.fr - http://nicolas.steinmetz.fr/

Hors ligne

#13 10-12-2012 19:09:33

Rémy HUBSCHER
Membre
Inscription : 11-08-2010
Messages : 161

Re : Taggit et les filtres en AND

Ok dans ce cas tu veux effectivement faire des jointures.

https://github.com/Natim/django-exact__in

Le 10/12/2012 18:42, Nicolas Steinmetz a écrit :
>
> Le 10 décembre 2012 17:23, Florian Strzelecki
> <florian.strzelecki _AT_ gmail.com <mailto:florian.strzelecki _AT_ gmail.com>> a
> écrit :
>
>     En fait, ton problème est beaucoup plus complexe qu'il n'en a l'air.
>
>     En gros, ce que tu veux faire, c'est ça :
>
>     "Récupérer tous les objets Mark qui sont liés à tous ces N tags."
>
>     En SQL, cela donnerait des choses relativement complexe, à base de
>     EXISTS et autres petites choses.
>     Donc, déjà, je t'invite à le faire en SQL, et à voir ce que tu
>     peux faire avec Django autour de ça... ça devrait déjà aider un peu.
>
>
> En MongoDB, tu peux le faire avec l'opérateur "all" [1] ; ce qui me
> donnait précédemment le code suivant :
>
> bookmarks.query.filter({'tag':{'$all':
> tag_list}}).descending('created_at')
>
> C'est peut être du coup le coté "relationnel" qui flanche dans le cas
> de ma migration à postgres.
>
> [1] http://docs.mongodb.org/manual/reference/operators/#_S_all
>
> Dans postgres, cela semble possible de le faire avec les arrays :
> http://www.craigkerstiens.com/2012/08/20/arrays-in-postgres/
>
> Vais peut être partir sur ce principe là sauf que l'extension django
> pour supporter les arrays postgresql ne s'affiche pas dans l'admin :
> http://www.craigkerstiens.com/2012/11/06/django-and-arrays/
> https://github.com/niwibe/djorm-ext-pgarray
>
> Il semblerait qu'un fork commence à gérer ça (intégration dans l'admin)
>
> smile
> ---
> Nicolas Steinmetz
> http://www.steinmetz.fr - http://nicolas.steinmetz.fr/
>
>
> _______________________________________________
> django mailing list
> django _AT_ lists.afpy.org
> http://lists.afpy.org/mailman/listinfo/django

Hors ligne

#14 10-12-2012 20:43:43

Nicolas Steinmetz
Membre
Inscription : 11-08-2010
Messages : 96

Re : Taggit et les filtres en AND

Le 10 décembre 2012 19:09, Rémy Hubscher <hubscher.remy _AT_ gmail.com> a écrit :

>  Ok dans ce cas tu veux effectivement faire des jointures.
>
> https://github.com/Natim/django-exact__in
>

Nickel, ça marche au poil smile

Merci !

Hors ligne

Pied de page des forums