Получение непомеченных объектов с помощью django-тегов

Что я ищу, так это QuerySet, содержащий любые объекты без тегов.

Решение, которое я нашел до сих пор, кажется мне слишком сложным:

# Get all tags for model
tags = Location.tags.all().order_by('name')

# Get a list of tagged location id's
tag_list = tags.values_list('name', flat=True)
tag_names = ', '.join(tag_list)
tagged_locations = Location.tagged.with_any(tag_names) \
                                  .values_list('id', flat=True)

untagged_locations = []
for location in Location.objects.all():
    if location.id not in tagged_locations:
        untagged_locations.append(location)

Есть идеи по улучшению? Спасибо!

3 ответа

Решение

В этом посте есть хорошая информация, поэтому я не думаю, что ее следует удалять, но есть гораздо более простое решение.

Я быстро взглянул на исходный код для 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.

Предполагая ваш Location класс использует tagging.fields.TagField полезность.

from tagging.fields import TagField
class Location(models.Model):
    tags = TagField()

Вы можете просто сделать это:

Location.objects.filter(tags='')

Попробуй это:

[location for location in Location.objects.all() if location.tags.count() == 0]
Другие вопросы по тегам