Question

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

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

Des modèles simples pour poser ma question.

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

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

Tag and Blog est utilisé uniquement à titre d'exemple.

Était-ce utile?

La solution

Vous pouvez utiliser les objets Q pour # 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')
)
Je pense que les unions et les intersections sortent un peu du cadre de l'ORM de Django, mais il est possible d'y répondre. Les exemples suivants proviennent d'une application Django appelée django-tagging qui fournit cette fonctionnalité. ligne 346 du fichier modèles.py :

Pour la deuxième partie, vous recherchez 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 troisième partie, je pense que vous recherchez une intersection. Voir la ligne 307 de modèles.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()

Autres conseils

Je les ai testés avec Django 1.0:

Le " ou " requêtes:

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

Les " et " requête:

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.

Veuillez ne pas réinventer la roue et utiliser la application de marquage Django qui était fait exactement pour votre cas d'utilisation. Il peut faire 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 consulter ma branche de django-tagging .

Cela fera l'affaire pour vous

Blog.objects.filter(tags__name__in=['tag1', 'tag2']).annotate(tag_matches=models.Count(tags)).filter(tag_matches=2)
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top