В этом посте есть хорошая информация, поэтому я не думаю, что ее следует удалить, но есть гораздо более простое решение
Я быстро взглянул на исходный код для django-тегов. Похоже, они используют инфраструктуру ContentType и общие отношения, чтобы осуществить это.
Благодаря этому вы сможете создать общее обратное отношение в своем классе Location, чтобы получить легкий доступ к объектам TaggedItem для данного местоположения, если вы еще этого не сделали:
from django.contrib.contenttypes import generic
from tagging.models import TaggedItem
class Location(models.Model):
...
tagged_items = generic.GenericRelation(TaggedItem,
object_id_field="object_id",
content_type_field="content_type")
...
Разъяснение
Мой оригинальный ответ предложил сделать это:
untagged_locs = Location.objects.filter(tagged_items__isnull=True)
Хотя это будет работать для «нормального соединения», на самом деле это не работает, потому что структура типов контента добавляет дополнительную проверку content_type_id
в SQL для isnull
:
SELECT [snip] FROM `sotest_location`
LEFT OUTER JOIN `tagging_taggeditem`
ON (`sotest_location`.`id` = `tagging_taggeditem`.`object_id`)
WHERE (`tagging_taggeditem`.`id` IS NULL
AND `tagging_taggeditem`.`content_type_id` = 4 )
Вы можете взломать его, перевернув его так:
untagged_locs = Location.objects.exclude(tagged_items__isnull=False)
Но это не совсем правильно.
Я также предложил это, но было отмечено, что аннотации не работают должным образом с каркасом типов контента.
from django.db.models import Count
untagged_locs = Location.objects.annotate(
num_tags=Count('tagged_items')).filter(num_tags=0)
Приведенный выше код работает для меня в моем ограниченном тестовом примере, но он может содержать ошибки, если у вас есть другие объекты taggable в вашей модели. Причина в том, что он не проверяет content_type_id
, как указано в билете . Он сгенерировал следующий SQL:
SELECT [snip], COUNT(`tagging_taggeditem`.`id`) AS `num_tags`
FROM `sotest_location`
LEFT OUTER JOIN `tagging_taggeditem`
ON (`sotest_location`.`id` = `tagging_taggeditem`.`object_id`)
GROUP BY `sotest_location`.`id` HAVING COUNT(`tagging_taggeditem`.`id`) = 0
ORDER BY NULL
Если Location
является вашим единственным тегируемым объектом, то вышеприведенное сработает.
Предлагаемое решение
Если не использовать механизм аннотаций, вот что я хотел бы сделать тем временем:
untagged_locs_e = Location.objects.extra(
where=["""NOT EXISTS(SELECT 1 FROM tagging_taggeditem ti
INNER JOIN django_content_type ct ON ti.content_type_id = ct.id
WHERE ct.model = 'location'
AND ti.object_id = myapp_location.id)"""]
)
Это добавляет дополнительное предложение WHERE к SQL:
SELECT [snip] FROM `myapp_location`
WHERE NOT EXISTS(SELECT 1 FROM tagging_taggeditem ti
INNER JOIN django_content_type ct ON ti.content_type_id = ct.id
WHERE ct.model = 'location'
AND ti.object_id = myapp_location.id)
Он присоединяется к таблице django_content_type
, чтобы убедиться, что вы смотрите на
тип контента для вашей модели в случае, если у вас есть более одного типа модели с тегами.
Измените myapp_location.id
в соответствии с именем вашей таблицы. Вероятно, есть способ избежать жесткого кодирования имен таблиц, но вы можете понять это, если это важно для вас.
Отрегулируйте соответственно, если вы не используете MySQL.