python - objects - Unión e intersección en Django
union e interseccion de conjuntos en c++ (4)
Esto hará el truco para ti
Blog.objects.filter(tags__name__in=[''tag1'', ''tag2'']).annotate(tag_matches=models.Count(tags)).filter(tag_matches=2)
class Tag(models.Model):
name = models.CharField(maxlength=100)
class Blog(models.Model):
name = models.CharField(maxlength=100)
tags = models.ManyToManyField(Tag)
Modelos simples solo para hacer mi pregunta.
Me pregunto cómo puedo consultar blogs usando etiquetas de dos maneras diferentes.
- Entradas de blog etiquetadas con "tag1" o "tag2":
Blog.objects.filter(tags_in=[1,2]).distinct()
- Los objetos del blog que están etiquetados con "tag1" y "tag2" :?
- Los objetos del blog etiquetados con exactamente "tag1" y "tag2" y nada más: ??
La etiqueta y el blog solo se usan como ejemplo.
He probado esto con Django 1.0:
Las consultas "o":
Blog.objects.filter(tags__name__in=[''tag1'', ''tag2'']).distinct()
o podrías usar la clase Q:
Blog.objects.filter(Q(tags__name=''tag1'') | Q(tags__name=''tag2'')).distinct()
La consulta "y":
Blog.objects.filter(tags__name=''tag1'').filter(tags__name=''tag2'')
No estoy seguro acerca de la tercera, probablemente deba bajar a SQL para hacerlo.
Podría usar objetos Q para # 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'')
)
Creo que las uniones e intersecciones están un poco fuera del alcance del ORM de Django, pero es posible que esto ocurra. Los siguientes ejemplos provienen de una aplicación de Django llamada django-tagging que proporciona la funcionalidad. Línea 346 de models.py :
Para la segunda parte, estás buscando una unión de dos consultas, básicamente
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()
Para la parte # 3, creo que estás buscando una intersección. Vea la línea 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()
Por favor, no reinvente la rueda y use la django-tagging que fue hecha exactamente para su caso de uso. Puede hacer todas las consultas que describes, y mucho más.
Si necesita agregar campos personalizados a su modelo de Etiqueta, también puede echar un vistazo a mi rama de django-etiquetado .