31 votes

Union et intersection à Django

 class Tag(models.Model):
  name = models.CharField(maxlength=100)

class Blog(models.Model):
  name = models.CharField(maxlength=100)
  tags =  models.ManyToManyField(Tag)
 

Modèles simples juste pour poser ma question.

Je me demande comment puis-je interroger des blogs à l'aide de balises de deux manières différentes.

  • Entrées de blog marquées avec "tag1" ou "tag2": Blog.objects.filter(tags_in=[1,2]).distinct()
  • Blog objets qui sont tagguées avec « balise1 » et « tag2 »:?
  • Objets de blog étiquetés avec exactement "tag1" et "tag2" et rien d'autre: ??


Tag et Blog sont juste utilisés pour un exemple.

21voto

Clint Ecker Points 781

Vous pourriez utiliser des objets Q pour n ° 1:

# Blogs who have either hockey or django tags.
from django.db.models import Q
Blog.objects.filter(
    Q(tags__name__iexact='hockey') | Q(tags__name__iexact='django')
)

Les Unions et intersections, je crois, sont un peu en dehors de la portée de l'ORM de Django, mais il est possible de de ces. Les exemples suivants sont issus d'une application Django appelé appelé django-marquage qui fournit la fonctionnalité. Ligne 346 de models.py:

Pour la deuxième partie, vous êtes à la recherche pour une union de deux requêtes, essentiellement

def get_union_by_model(self, queryset_or_model, tags):
    """
    Create a ``QuerySet`` containing instances of the specified
    model associated with *any* of the given list of tags.
    """
    tags = get_tag_list(tags)
    tag_count = len(tags)
    queryset, model = get_queryset_and_model(queryset_or_model)

    if not tag_count:
        return model._default_manager.none()

    model_table = qn(model._meta.db_table)
    # This query selects the ids of all objects which have any of
    # the given tags.
    query = """
    SELECT %(model_pk)s
    FROM %(model)s, %(tagged_item)s
    WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
      AND %(tagged_item)s.tag_id IN (%(tag_id_placeholders)s)
      AND %(model_pk)s = %(tagged_item)s.object_id
    GROUP BY %(model_pk)s""" % {
        'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)),
        'model': model_table,
        'tagged_item': qn(self.model._meta.db_table),
        'content_type_id': ContentType.objects.get_for_model(model).pk,
        'tag_id_placeholders': ','.join(['%s'] * tag_count),
    }

    cursor = connection.cursor()
    cursor.execute(query, [tag.pk for tag in tags])
    object_ids = [row[0] for row in cursor.fetchall()]
    if len(object_ids) > 0:
        return queryset.filter(pk__in=object_ids)
    else:
        return model._default_manager.none()

Pour la partie #3 je crois que vous êtes à la recherche d'une intersection. Voir la ligne 307 de models.py

def get_intersection_by_model(self, queryset_or_model, tags):
    """
    Create a ``QuerySet`` containing instances of the specified
    model associated with *all* of the given list of tags.
    """
    tags = get_tag_list(tags)
    tag_count = len(tags)
    queryset, model = get_queryset_and_model(queryset_or_model)

    if not tag_count:
        return model._default_manager.none()

    model_table = qn(model._meta.db_table)
    # This query selects the ids of all objects which have all the
    # given tags.
    query = """
    SELECT %(model_pk)s
    FROM %(model)s, %(tagged_item)s
    WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
      AND %(tagged_item)s.tag_id IN (%(tag_id_placeholders)s)
      AND %(model_pk)s = %(tagged_item)s.object_id
    GROUP BY %(model_pk)s
    HAVING COUNT(%(model_pk)s) = %(tag_count)s""" % {
        'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)),
        'model': model_table,
        'tagged_item': qn(self.model._meta.db_table),
        'content_type_id': ContentType.objects.get_for_model(model).pk,
        'tag_id_placeholders': ','.join(['%s'] * tag_count),
        'tag_count': tag_count,
    }

    cursor = connection.cursor()
    cursor.execute(query, [tag.pk for tag in tags])
    object_ids = [row[0] for row in cursor.fetchall()]
    if len(object_ids) > 0:
        return queryset.filter(pk__in=object_ids)
    else:
        return model._default_manager.none()

16voto

Ycros Points 840

J'ai testé ces derniers avec Django 1.0:

Les requêtes "ou":

 Blog.objects.filter(tags__name__in=['tag1', 'tag2']).distinct()
 

ou vous pouvez utiliser la classe Q:

 Blog.objects.filter(Q(tags__name='tag1') | Q(tags__name='tag2')).distinct()
 

La requête "et":

 Blog.objects.filter(tags__name='tag1').filter(tags__name='tag2')
 

Je ne suis pas sûr du troisième, vous devrez probablement passer à SQL pour le faire.

9voto

zuber Points 1834

Veuillez ne pas réinventer la roue et utiliser l' application d'étiquetage django qui a été conçue exactement pour votre cas d'utilisation. Il peut effectuer toutes les requêtes que vous décrivez, et bien plus encore.

Si vous devez ajouter des champs personnalisés à votre modèle de balise , vous pouvez également jeter un œil à ma branche de django-tagging .

5voto

amit Points 41

Cela fera l'affaire pour vous

 Blog.objects.filter(tags__name__in=['tag1', 'tag2']).annotate(tag_matches=models.Count(tags)).filter(tag_matches=2)
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X