Django 5 для начинающих

Прогресс по курсу:  9/1004

10.2 Создание древовидной модели категорий
2 из 2 шагов пройдено

В этом разделе мы рассмотрим создание древовидной модели категорий для постов с использованием MPTT. Категории будут связаны друг с другом как дочерние-родительские, категории верхнего уровня не будут иметь родительскую категорию.

MPTT - это метод хранения и обработки иерархических данных в базе данных. Библиотека django-mptt позволит создавать и работать с иерархиями. Проще говоря, мы сможем сделать древовидное представление категорий.

Перед началом работы установим django-mptt:

pip install django-mptt

И не забудем добавить его в  INSTALLED_APPS файла settings.py:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'apps.blog.apps.BlogConfig',
    'mptt'
]

Теперь перейдем к созданию самой модели Category:

from mptt.models import MPTTModel, TreeForeignKey

class Category(MPTTModel):
    """
    Модель категорий с вложенностью
    """
    title = models.CharField(max_length=255, verbose_name='Название категории')
    slug = models.SlugField(max_length=255, verbose_name='URL категории', blank=True)
    description = models.TextField(verbose_name='Описание категории', max_length=300)
    parent = TreeForeignKey(
        'self',
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        db_index=True,
        related_name='children',
        verbose_name='Родительская категория'
    )

    class MPTTMeta:
        """
        Сортировка по вложенности
        """
        order_insertion_by = ('title',)

    class Meta:
        """
        Сортировка, название модели в админ панели, таблица в данными
        """
        verbose_name = 'Категория'
        verbose_name_plural = 'Категории'
        db_table = 'app_categories'

    def __str__(self):
        """
        Возвращение заголовка категории
        """
        return self.title

В поле создаётся объект класса TreeForeignKey из django-mptt, позволяющий делать древовидные категории. Аргумент self указывает, что внешний ключ ссылается на ту же модель, в которой он определен, то есть на Category.

Аргумент on_delete=models.CASCADE задает поведение при удалении связанного объекта. В данном случае, если родительская категория удалена, то все дочерние категории также будут удалены.

Аргументы null=True и blank=True позволяют полю parent быть необязательным и не требуют обязательного ввода значения при создании объекта.

Аргумент related_name='children' определяет имя обратной связи между родительской и дочерней категорией. То есть, у каждого объекта модели Category будет доступ к своим дочерним категориям через атрибут children.

Далее нам необходимо включить категории в модель статей, делается это следующим образом:

class Post(models.Model):
    """
    Модель постов для нашего блога
    """

    STATUS_OPTIONS = (
        ('published', 'Опубликовано'),
        ('draft', 'Черновик')
    )

    title = models.CharField(verbose_name='Название записи', max_length=255)
    slug = models.SlugField(verbose_name='URL', max_length=255, blank=True, unique=True)
    description = models.TextField(verbose_name='Краткое описание', max_length=500)
    text = models.TextField(verbose_name='Полный текст записи')
    category = TreeForeignKey('Category', on_delete=models.PROTECT, related_name='posts', verbose_name='Категория') # New
    # Другие поля модели...

Мы должны использовать вместо отношения: ForeignKey импортированное отношение вложенности из модуля MPTT: TreeForeignKey

Регистрируем модель категорий в админке, и добавляем возможность скрывать категории по вложенности с помощью DraggableMPTTAdmin: До этого мы регистрировали уже модель Post.

from django.contrib import admin

from mptt.admin import DraggableMPTTAdmin
from .models import Category, Post


@admin.register(Category)
class CategoryAdmin(DraggableMPTTAdmin):
    """
    Админ-панель модели категорий
    """
    prepopulated_fields = {'slug': ('title',)}


admin.site.register(Post)

Отлично. Теперь нам необходимо провести миграции в базу данных:

python manage.py makemigrations
python manage.py migrate

Теперь давайте проверим работоспособность наших моделей в админ-панели, чтобы в нее войти нам нужно создать супер-пользователя, делается это следующей командой:

python manage.py createsuperuser

Далее переходим в админ-панель по следующему адресу: http://127.0.0.1:8000/admin/, авторизуемся, переходим в раздел категорий и попробуем создать первую категорию:

Далее добавляем вложенную категорию:

Добавим еще несколько категорий:

В списке категорий дочерние категории отображаются чуть сдвинутыми вправо относительно родителя.Также мы можем перемещать категории по иерархии. И раскрывать и скрывать список вложенных категорий.


  • Комментария
Будьте вежливы и соблюдайте наши принципы сообщества. Пожалуйста, не оставляйте решения и подсказки в комментариях, для этого есть отдельный форум.
Оставить комментарий

здесь на против поля категория не помешало бы дописать new

 

Далее нам необходимо включить категории в модель статей, делается это следующим образом:

class Post(models.Model):
    """
    Модель постов для нашего блога
    """

    STATUS_OPTIONS = (
        ('published', 'Опубликовано'),
        ('draft', 'Черновик')
    )

    title = models.CharField(verbose_name='Название записи', max_length=255)
    slug = models.SlugField(verbose_name='URL', max_length=255, blank=True, unique=True)
    description = models.TextField(verbose_name='Краткое описание', max_length=500)
    text = models.TextField(verbose_name='Полный текст записи')
    category = TreeForeignKey('Category', on_delete=models.PROTECT, related_name='posts', verbose_name='Категория')
    # Другие поля модели...

@No_Name, добавил

 почему возвращается заголовок статьи а не заголовок категории 

def __str__(self):
        """
        Возвращение заголовка статьи
        """
        return self.title
Изменен Шамбер Егор

@Шамбер_Егор, Поправил, спасибо. Опечатка.

1) Правильно ли я понял, что order_insertion_by = ('title',) отвечает за то, в каком порядке отображаются вложенные элементы? 

2) Для чего нужно предзаселенные поля prepopulated_fields = {'slug': ('title',)}?

Изменен Кирилл Семенихин

@Кирилл_Семенихин, Да, сортирует в алфавитном порядке по названию категорий.

@Кирилл_Семенихинprepopulated_fields = {'slug': ('title',)}

Этот код заполняет поле slug используя поле title, то есть при вводе title автоматически заполнится slug