59 트리구조 메뉴 만들기- treebeard

59.1 django-treebeard 패키지 설치 및 셋팅파일 설정

패키지는 아래 명령어로 설치합니다.

1
pip install django-treebeard

패키지 설치가 완료되었으면 셋팅파일에 추가합니다.

1
2
3
4
5
6
7
8
DJANGO_BASE_APP = [
    ...
    'django.contrib.sitemaps',
    'markdownx',
    'treebeard',
]
MY_APP =['myapp.blog','myapp.coding']
INSTALLED_APPS = DJANGO_BASE_APP + MY_APP

59.2 모델 수정하기 - models.py

MP_Node 를 import하고 상속 받습니다.
node_order_by 기준을 regist_dt로 정했습니다.

get_prev_sibling와 get_next_sibling는
현재 글의 같은 카테고리에서
이전 글과 다음 글을 가져오는 기능입니다.
더 많은 함수는 아래 링크에서 확인하시면 됩니다.

https://django-treebeard.readthedocs.io/en/latest/api.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#myapp/coding/model.py
from django.db import models
from django.urls import reverse
from markdownx.models import MarkdownxField
from markdownx.utils import markdownify
from treebeard.mp_tree import MP_Node

IMG_SIZE_CHOICES = {(30,'30%'),(40, '40%'),(50, '50%'),(70, '70%'),(100, '100%')}

class PyCoding(MP_Node):
    id        = models.AutoField(primary_key=True)
    tags      = models.CharField(max_length=200)
    title     = models.CharField(max_length=100)
    sub_title       = models.CharField(max_length=100, blank=True, null=True)
    img_url         = models.CharField(max_length=200, blank=True, null=True)
    img_size        = models.IntegerField(choices=IMG_SIZE_CHOICES, null=False, blank=False)
    #content_body   = models.TextField(null=False, blank=False)
    content_body = MarkdownxField()
    update_dt = models.DateTimeField(auto_now=True)
    regist_dt = models.DateTimeField(auto_now_add=False)

    node_order_by = ['regist_dt']


    def get_absolute_url(self):
        if not self.is_root():
            return reverse('coding:coding_detail', kwargs={'pk':self.pk} )

    def get_previous(self): 
        if not self.is_root():            
            return self.get_prev_sibling() 

    def get_next(self): 
        if not self.is_root():            
            return self.get_next_sibling()

    def get_is_root(self):
        return self.is_root()

    def __str__(self):        
        return self.title

    class Meta:
        db_table = 'py_coding'

59.3 관리자 추가하기 - admin.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#myapp/coding/admin.py
from django.contrib import admin
from markdownx.admin import MarkdownxModelAdmin
from markdownx.widgets import AdminMarkdownxWidget
from markdownx.models import MarkdownxField

from .models  import PyCoding
from treebeard.forms import movenodeform_factory
from treebeard.admin import TreeAdmin


class CategoryAdmin(TreeAdmin):
    form = movenodeform_factory(PyCoding)

admin.site.register(PyCoding, CategoryAdmin)

59.4 뷰 수정하기 - views.py

common_views.py의 menuMixin 클래스를
상속 받아 context[]coding_menu]로 담았습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#myapp/common/views.py
from django.utils.functional import cached_property
from myapp.blog.models import PyBlog, PyBlogDetail
from myapp.coding.models import PyCoding
import logging as log

class menuMixin(object):
    @cached_property
    def getMenuList(self):
        return {"blog_menu":PyBlog.objects.all(),
                "coding_menu": PyCoding.objects.all()}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#myapp/coding/views.py
from django.shortcuts import render
from django.views import generic
from .models import PyCoding

#sidebar
from myapp.common.common_views import menuMixin

class codingDetail(menuMixin, generic.DetailView):
    model = PyCoding

    def get_context_data(self, **kwargs):
        context   = super(codingDetail, self).get_context_data(**kwargs)            
        queryset = PyCoding.objects.get(id=self.kwargs['pk'])       
        context['pageInfo'] = queryset      
        context['blog_menu'] = self.getMenuList['blog_menu']
        context['coding_menu'] = self.getMenuList['coding_menu']        
        return context      

59.5 좌측메뉴 수정하기 - sidebar.html

if list.is_root로 여부로 부모와 자식을 구분합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    <li id="menu_coding" class="nav-item" >
        <a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapseUtilities"
            aria-expanded="true" aria-controls="collapseUtilities">
            <i class="fas fa-fw fa-wrench"></i>
            <span>Python 코딩</span>
        </a>
        <div id="collapseUtilities" class="collapse" aria-labelledby="headingUtilities"
            data-parent="#accordionSidebar">
            <div class="bg-white py-2 collapse-inner rounded">                
                  {% for list in coding_menu %} 
                      {% if list.is_root %}
                        <h6 class="collapse-header">{{list.title}} </h6>
                      {% endif%}
                      {% if not list.is_root %}                                        
                        <a id="col-item-coding{{list.id}}" class="collapse-item pl-4" href=" {{ list.get_absolute_url }}" >{{list.title}}</a>
                      {% endif%}
                {% endfor %}
            </div>
        </div>
    </li>

59.6 상세페이지 수정하기 - pycoding_detail.html

우측 서브메뉴와 하단 이전,다음글 부분입니다.

model에 정의한
get_absolute_url, get_previous, get_next 함수를 이용하여 구현 하였습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
  {% if pageInfo.get_previous or pageInfo.get_next  %}
    <div class="col-lg-4 mb-4">
        <div id="right_menu" class="col-xl-3 col-md-6 mb-4">
            <div class="card border-left-info shadow h-100 py-2">
                <div class="card-body">                                        
                    <div class="row no-gutters align-items-center">
                        <div class="col">
                            {% if pageInfo.get_previous %}
                                <div> 
                                    <span class="h6 mb-1 font-weight-bold anchor_link">
                                        <a href="{{pageInfo.get_previous.get_absolute_url}}">{{pageInfo.get_previous}}</a>
                                    </span>
                                </div>
                            {% endif %}
                            <hr class="d-none d-md-block">

                            <div> <span class="h6 mb-1 font-weight-bold anchor_link"><a href="#{{pageInfo.title}}">{{pageInfo.title}}</a></span></div>                            
                            <div> 
                                <span class="h6 mb-1 font-weight-bold anchor_link active">
                                    <a href="#{{pageInfo.sub_title}}">{{pageInfo.sub_title}}</a>
                                </span>
                            </div>
                            <hr class="d-none d-md-block">
                            {% if pageInfo.get_next %}
                                <div> 
                                    <span class="h6 mb-1 font-weight-bold anchor_link">
                                        <a href="{{pageInfo.get_next.get_absolute_url}}">{{pageInfo.get_next}}</a>
                                    </span>
                                </div>
                            {% endif %}
                        </div><!--<div class="col mr-2">-->                        
                    </div>
                </div>
            </div>
        </div>
     </div>
     {% endif %}
</div>
<div class="row">
     <div class="col-lg-8 mb-4">
        <div class="card shadow mb-4">
             {% if pageInfo.get_previous %}
            <div  class="card-body"><span class="font-weight-bold text-gray-800">이전글</span> : <span class=" text-primary font-weight-bold"> <a href="{{pageInfo.get_previous.get_absolute_url}}">{{pageInfo.get_prev_sibling}}</a></span></div>
            {% endif %}
            {% if pageInfo.get_next %}
            <div  class="card-body"><span class="font-weight-bold text-gray-800">다음글</span> : <span class=" text-primary font-weight-bold"><a href="{{pageInfo.get_next.get_absolute_url}}">{{pageInfo.get_next_sibling}}</a> </span></div>
            {% endif %}
        </div>
     </div>
</div>

59.7 브라우저에서 확인하기

계층 구조로 메뉴를 완성했습니다.^__^