
Поговорим о способе реализации иерархического хранения данных со структурой каталогов. Отличный пример — интернет магазин, где определенный товар находится в определенной ветке дерева категорий. Или иерархия адресов: Когда для определенных городов, есть общий родитель — государство и т.д.
В данном случае, необходимо пройти по дереву с применением рекурсии. Однако в Django уже есть готовое решение django-mptt.
MPTT(Modified Preorder Tree Traversal) / Nested Sets — один из способов обхода деревьев категорий. В двух словах — метод обхода иерархических деревьев, когда в БД есть структура: id, parent_id, name. А нам необходим способ рекурсивно пройти по нему извлечь ветки для потомком особо не нагружая базу данных
Install
pip install django-mptt
pip install django-mptt-admin
settings.py
INSTALLED_APPS = [
...
'mptt',
]
# default is 10 pixels
MPTT_ADMIN_LEVEL_INDENT = 20
Models & Admin
models.py
# -*- coding: utf-8 -*-
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
#MPTT
from mptt.models import MPTTModel, TreeForeignKey
# Модель которая хранит дерево категорий
@python_2_unicode_compatible
class Category(MPTTModel):
class Meta():
db_table = 'category'
verbose_name_plural = "Категории"
verbose_name = "Категория"
ordering = ('tree_id', 'level')
name = models.CharField(max_length=50, unique=True)
parent = TreeForeignKey('self', null=True, blank=True, related_name='children', verbose_name=u'Родительская категория', db_index=True)
def __str__(self):
return self.name
class MPTTMeta:
order_insertion_by = ['name']
# Модель для обьектов, которые распределяем по дереву
@python_2_unicode_compatible
class Node(models.Model):
class Meta():
verbose_name_plural = "Устройства"
verbose_name = "Устройство"
category = TreeForeignKey(Category, null=True, blank=True, related_name='cat')
ip = models.CharField(max_length=300, verbose_name=u'IPv4', blank=True)
hostname = models.CharField(max_length=300)
description = models.TextField(blank=True)
def __str__(self):
return self.hostname
admin.py
from django.contrib import admin
# Register your models here.
# MPTT
from mptt.admin import MPTTModelAdmin
# Импортим модели листьев и дерева
from nodes.models import Node, Category
class NodeAdmin(admin.ModelAdmin):
# Порядок полей
fields = ['category', 'ip', 'hostname', 'description']
# Фильтр
list_filter = ('category', )
# Поиск
search_fields = ['hostname']
class CategotyAdmin(admin.ModelAdmin):
fields = ['name', 'parent']
# Регестрируем модели
admin.site.register(Node, NodeAdmin)
admin.site.register(Category, CategotyAdmin)
URLs->View->Template
urls.py
from django.conf.urls import url, include
from django.contrib import admin
from nodes import views
app_name = 'nodes'
urlpatterns = [
url(r'category/(?P<category_id>\d+)', views.index, name="index"),
url(r'^$', views.index, name="index"),
]
views.py
from nodes.models import Node, Category
def index(request, category_id=None):
if category_id:
category_name = Category.objects.get(id=category_id)
nodes = Node.objects.filter(category_id=category_id)
# получим queryset, в который будет входить сама категория и все вложенные в нее
branch_category = category_name.get_descendants(include_self=True)
# Выберем все nodes, которые входят в выбранную категорию, либо в любую вложенную в нее.
category_nodes = Node.objects.filter(category__in=branch_category).distinct()
data = {
'category': Category.objects.all(),
'category_name': category_name,
'nodes': nodes,
'category_nodes': category_nodes,
}
return render(request, 'nodes/index.html', data )
# Если category_id=None возвращаем просто дерево категорий
else:
data = {
'category': Category.objects.all(),
}
return render(request, 'nodes/index.html', data )
index.html
{% load mptt_tags %}
<html>
<head>
<title></title>
</head>
<body>
{{hi}}
<ul class="category-menu">
{% recursetree category %}
<li>
<a href="/nodes/category/{{ node.id }}">{{ node.name }}</a>
<!-- Дочернии элементы -->
{% if not node.is_leaf_node %}
<ul class="children">
{{ children }}
</ul>
{% endif %}
</li>
{% endrecursetree %}
</ul>
<hr>
<!-- category_name - содержит ноды для дочерних от выбраного элемента -->
Категория: {{category_name}} <br>
{% for node in category_nodes %}
{{node}} <br>
{% endfor %}
</body>
</html>
Материалы
- http://zabaykin.ru/?p=138
- https://www.youtube.com/watch?v=FSZVSQ_Nstc
- https://django-mptt.github.io/django-mptt/admin.html