Получение непомеченных объектов с помощью django-тегов - PullRequest
1 голос
/ 30 ноября 2009

Я ищу 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 ]

3 голосов
/ 30 ноября 2009

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

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

0 голосов
/ 01 декабря 2009

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

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

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

Location.objects.filter(tags='')
0 голосов
/ 30 ноября 2009

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

[location for location in Location.objects.all() if location.tags.count() == 0]
...