Union und Intersect in Django
-
01-07-2019 - |
Frage
class Tag(models.Model):
name = models.CharField(maxlength=100)
class Blog(models.Model):
name = models.CharField(maxlength=100)
tags = models.ManyToManyField(Tag)
Einfache Modelle nur meine Frage zu stellen.
Ich frage mich, wie kann ich Blogs mit Hilfe von Tags auf zwei verschiedene Arten abgefragt werden.
- Blog-Einträge, die mit "tag1" oder "tag2" markiert sind:
Blog.objects.filter(tags_in=[1,2]).distinct()
- Blog-Objekte, die mit "tag1" markiert sind und "tag2":
- Blog-Objekte, die mit genau "tag1" markiert sind und "tag2" und nichts anderes: ??
Tag und Blog ist nur ein Beispiel verwendet wird.
Lösung
Sie könnten Q verwenden, um Objekte für # 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')
)
Gewerkschaften und Kreuzungen, glaube ich, sind ein wenig außerhalb des Bereichs des Django ORM, aber seine möglich auf diese. Die folgenden Beispiele sind von einer gerufenen django-Tagging , die die Funktionalität bereitstellt. Linie 346 von models.py :
Für Teil zwei, die Sie für eine Vereinigung von zwei Abfragen suchen, im Grunde
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()
Für einen Teil # 3 Ich glaube, du bist für eine Kreuzung suchen. Siehe Linie 307 von 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()
Andere Tipps
Ich habe diese aus mit Django getestet 1.0:
Die "oder" Abfragen:
Blog.objects.filter(tags__name__in=['tag1', 'tag2']).distinct()
oder Sie können die Q-Klasse verwenden:
Blog.objects.filter(Q(tags__name='tag1') | Q(tags__name='tag2')).distinct()
Die "und" Abfrage:
Blog.objects.filter(tags__name='tag1').filter(tags__name='tag2')
ich über die dritten nicht sicher bin, müssen Sie wahrscheinlich auf SQL fallen, es zu tun.
Bitte nicht das Rad neu zu erfinden und verwenden django-Tagging-Anwendung , die waren genau für Ihren Anwendungsfall gemacht. Es kann alle Anfragen tun Sie beschreiben, und vieles mehr.
Wenn Sie benutzerdefinierte Felder zu Ihrem Tag Modell hinzufügen möchten, können Sie auch einen Blick auf meine Zweig der django-Tagging .
Damit wird der Trick für Sie tun
Blog.objects.filter(tags__name__in=['tag1', 'tag2']).annotate(tag_matches=models.Count(tags)).filter(tag_matches=2)