2008-09-20 10 views
31
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.Union et Intersect à Django

Je me demande comment je peux interroger les blogs en utilisant les tags de deux manières différentes.

  • entrées de blog qui sont tagguées avec "balise1" ou "tag2": Blog.objects.filter(tags_in=[1,2]).distinct()
  • objets blog 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 est juste utilisé pour un exemple.

+0

Découvrez [cette question] (http://stackoverflow.com/q/12752601/1226722) avec une réponse vraiment super. Peut-être utile (je suis conscient que cette question a ~ 6 ans, mais je l'ai toujours trouvé en cherchant des réponses!) – gregoltsov

+0

Ceci est plus un problème d'une ou d'une clause where plutôt qu'une union SQL réelle. Si vous cherchez une union, regardez https: // stackoverflow.com/questions/4411049/how-can-je-trouver-l'union-de-deux-django-querysets – jocassid

Répondre

21

Vous pouvez utiliser des objets pour Q # 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 syndicats et les intersections, je crois, sont un peu en dehors du champ d'application de Django ORM, mais il est possible de ceux-ci à. Les exemples suivants proviennent d'une application Django appelée django-tagging qui fournit la fonctionnalité. Line 346 of models.py:

Pour la deuxième partie, vous êtes à la recherche d'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 une partie # 3 Je crois que vous êtes à la recherche d'une intersection. Voir line 307 of 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() 
16

Je l'ai testé ces avec Django 1.0:

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

La requête "et":

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

Je ne suis pas sûr de la troisième, vous aurez probablement besoin de passer à SQL pour le faire.

+0

Hrm, cette requête "et" ressemble à un truc maniable, sauf que vous ne saurez pas au début combien de times .filter devra être appliqué. L'utilisateur pourrait être à la recherche de chien + chèvre + chat, auquel cas vous aurez besoin de .filter deux fois. – mlissner

+0

En ce qui concerne l'application dynamique de la requête "et" - il suffit de parcourir les balises et d'accumuler le filtrage en utilisant: query = query.filter (tags__name = 'tagN') – Lukasz

+0

Je pense que le premier exemple fait l'affaire. Je réfléchissais si oui ou non le distinct était nécessaire. En termes SQL, vous auriez 2 joint Blogs à BlogTagLink et BlogTagLink à Tag un enregistrement de blog donné serait listé plusieurs fois dans le jeu de résultats. – jocassid

9

S'il vous plaît ne pas réinventer la roue et utiliser django-tagging application qui a été fait exactement pour votre cas d'utilisation. Il peut répondre à toutes les questions que vous décrivez, et bien plus encore.

Si vous devez ajouter des champs personnalisés à votre modèle de tag, vous pouvez également jeter un oeil à my branch of django-tagging.

5

Cela fera l'affaire pour vous

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