这篇文章中有一些很好的信息,所以我觉得不应该删除它,但是有一个非常非常简单的解决方案
我快速浏览了 django-tagging 的源代码。看起来他们使用 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)
上面的代码在我有限的测试用例中对我有用,但如果你的模型中有其他“可标记”对象,它可能会出现问题。原因是它没有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)"""]
)
这会在 SQL 中添加一个额外的 WHERE 子句:
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,请进行相应调整。