MPTT

Поговорим о способе реализации иерархического хранения данных со структурой каталогов. Отличный пример — интернет магазин, где определенный товар находится в определенной ветке дерева категорий. Или иерархия адресов: Когда для определенных городов, есть общий родитель — государство и т.д.

В данном случае, необходимо пройти по дереву с применением рекурсии. Однако в 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

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *