Union and Intersect in Django

前端 未结 4 803
面向向阳花
面向向阳花 2020-12-24 12:17
class Tag(models.Model):
  name = models.CharField(maxlength=100)

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


        
相关标签:
4条回答
  • 2020-12-24 12:22

    This will do the trick for you

    Blog.objects.filter(tags__name__in=['tag1', 'tag2']).annotate(tag_matches=models.Count(tags)).filter(tag_matches=2)
    
    0 讨论(0)
  • 2020-12-24 12:25

    You could use Q objects for #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')
    )
    

    Unions and intersections, I believe, are a bit outside the scope of the Django ORM, but its possible to to these. The following examples are from a Django application called called django-tagging that provides the functionality. Line 346 of models.py:

    For part two, you're looking for a union of two queries, basically

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

    For part #3 I believe you're looking for an intersection. See 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()
    
    0 讨论(0)
  • 2020-12-24 12:44

    I've tested these out with Django 1.0:

    The "or" queries:

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

    or you could use the Q class:

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

    The "and" query:

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

    I'm not sure about the third one, you'll probably need to drop to SQL to do it.

    0 讨论(0)
  • 2020-12-24 12:47

    Please don't reinvent the wheel and use django-tagging application which was made exactly for your use case. It can do all queries you describe, and much more.

    If you need to add custom fields to your Tag model, you can also take a look at my branch of django-tagging.

    0 讨论(0)
提交回复
热议问题