Расширить django-taggit

3 ноября 2016 г. 6:02

Потребовалось мне улучшить отображение django-taggit в админке. Делается это путём расширения приложения, которое достаточно хорошо описано в официальной документации. Но тем не менее я решил показать на примере, как можно кастомизировать отображение тегов приложения taggit в админке.

Поставленные задачи:

  1. Добавить колонку "Количество элементов" на страницу списка тегов для того, чтобы удобно было смотреть каких тегов меньше всего. Владея этой информацией, можно, например, добавить ещё статей с этими тегами.
  2. Убрать пустые "Элементы с меткой", которые добавляются по умолчанию на странице редактирования тега.
  3. Отобразить "Смотреть на сайте" как для "Тега" (Метки), так и для "Элемента с меткой".

Решение:

Для того чтобы в админке отображалась ссылка "Смотреть на сайте" для самого тега:

Ссылка "Смотреть на сайте"

и для inline элемента:

Ссылка "Смотреть на сайте" для Inline модели

нужно создать свой Tag и TaggedItem (дефолтный Tag и TaggedItem можно найти в приложении taggit/models.py) и добавить get_absolute_url(). Django, если видит, что существует данный метод, то автоматически добавляет ссылку "Смотреть на сайте". Для того чтобы сохранить полную функциональность TaggedItemBase (я имею в виду метод tags_for(cls, model, instance=None, **extra_filters), который есть в TaggedItemBase), нужно создать свой TaggedItemBase. Полный код приводится ниже:

# models.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models
from django.db.models import permalink
from django.http import Http404

from taggit.managers import TaggableManager
from taggit.models import GenericTaggedItemBase, TagBase, ItemBase
from django.utils.translation import ugettext_lazy as _

class MyTag(TagBase):
    @permalink
    def get_absolute_url(self):
        return 'tag_page', [self.slug, ]

    class Meta:
        verbose_name = _("Tag")
        verbose_name_plural = _("Tags")


class MyTaggedItemBase(ItemBase):
    tag = models.ForeignKey(MyTag, related_name="tag_items", on_delete=models.CASCADE)

    class Meta:
        abstract = True

    @classmethod
    def tags_for(cls, model, instance=None, **extra_filters):
        kwargs = extra_filters or {}
        if instance is not None:
            kwargs.update({
                '%s__content_object' % cls.tag_relname(): instance
            })
            return cls.tag_model().objects.filter(**kwargs)
        kwargs.update({
            '%s__content_object__isnull' % cls.tag_relname(): False
        })
        return cls.tag_model().objects.filter(**kwargs).distinct()


class MyTaggedItem(GenericTaggedItemBase, MyTaggedItemBase):
    def get_absolute_url(self):
        if not hasattr(self.content_object, 'get_absolute_url'):
            raise Http404

        return self.content_object.get_absolute_url()

    class Meta:
        verbose_name = _("Tagged Item")
        verbose_name_plural = _("Tagged Items")
        index_together = [
            ["content_type", "object_id"],
        ]

В классе MyTaggedItem если self.content_object не имеет get_absolute_url(), то просто бросаем исключение Http404.

Замечание
Мы не наследуемся от TaggedItem, так как не будут работать index_together, а также verbose_name в Meta классе. К тому же нам нужно унаследоваться от MyTaggedItemBase.

Затем изменим админку для taggit:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.contrib import admin
from taggit.models import Tag
from taggit.admin import TagAdmin
from spec.models import MyTaggedItem

admin.site.unregister(Tag)


class TaggedItemInline(admin.StackedInline):
    model = MyTaggedItem
    extra = 0  # убирает пустые "Элементы с меткой" (Задача 2)


@admin.register(Tag)
class CustomTagAdmin(TagAdmin):
    inlines = (TaggedItemInline, )
    list_display = ('name', 'slug', 'item_count')

    def item_count(self, obj):
        count = obj.tag_items.count()
        if count:
            return count  # Выводит количество "Элементов с меткой" (Задача 1)

        return '<img alt="False" src="/static/admin/img/icon-no.gif" />'
    item_count.short_description = 'Количество элементов'
    item_count.allow_tags = True

Метод item_count(self, obj) выводит количество "Элементов с меткой", если количество не равно нулю. Иначе, выводит стандартную джанговскую иконку False для boolean полей. Иконка захаркодена, но что поделать. Если знаете способ лучше, пожалуйста, сообщите :) А чтобы иконка отображалась, позволяем использование html-тегов: item_count.allow_tags = True.

Все задачи выполнены!

P.S. Если определён свой TaggedItem и не задан related_name в поле tag, то уже обращение к объектам тега будет примерно так tag.myapp_mytaggeditem_items.all().

Оцените статью

5 из 5 (всего 1 оценка)

Поля, отмеченные звёздочкой ( * ) , являются обязательными.

Спасибо за ваш отзыв!

После нажатия кнопки "Отправить" ваше сообщение будет доставлено мне на почту.

Автор статьи

Права на использование материала, расположенного на этой странице http://vivazzi.ru/it/extend-django-taggit/:

Разрешается копировать материал с указанием её автора и ссылки на оригинал без использования параметра rel="nofollow" в теге <a>. Использование:

Автор статьи: Мальцев Артём
Ссылка на статью: <a href="http://vivazzi.ru/it/extend-django-taggit/">http://vivazzi.ru/it/extend-django-taggit/</a>

Подробнее: Правила использования сайта

Комментариев: 0

Вы можете оставить комментарий как незарегистрированный пользователь. Но, зарегистрировавшись, вы сможете получать оповещения об ответах, а также иметь доступ к своему личному аккаунту для просмотра своих комментариев.

Чтобы оставить комментарий от своего имени войдите или зарегистрируйтесь обычным способом или через социальные сети:

Отправить

На данный момент нет специального поиска, поэтому я предлагаю воспользоваться обычной поисковой системой, например, Google, добавив "vivazzi" после своего запроса.

Попробуйте