Generowanie menu w Django

Ostatnio trochę filozofowałem, czas wrócić do konkretów :).
Kto ma za sobą pierwsze kroki w Django, czyli zrobił już bloga i poznał flatpages, stanie nieuchronnie przed problemem jak zarządzać nawigacją w nieco bardziej rozbudowanym serwisie.

W jednym z najprostszych przypadków poradziłem sobie w ten sposób, że do każdej strony w modelu dodawałem tytuł jaki ma się wyświetlać w menu oraz liczbę, która miała pomóc takie menu ułożyć w pożądanej kolejności (wystarczyło posortować według tych liczb).
Oczywiście szybko się zorientowałem, że takie proste rozwiązanie jest nieeleganckie, chociażby dlatego, jeśli będziemy chcieli połączyć ten nasz prosty serwis z jakąś inną aplikacją, w jakiś większy projekt, to nie możemy tego uczynić, gdyż menu "siedzi" w modelu naszych starych stron i jest z nim mocno powiązane. Można je nieco ulepszyć poprzez utworzenie osobnego modelu menu powiązanego relacją 1 do 1 z innymi modelami.

Ale wtedy szybko zauważymy następne ograniczenie: menu, które możemy w ten sposób wygenerować jest wyłącznie menu "płaskim", czyli jednopoziomowym. Jak napisać model menu wielopoziomowego, o strukturze drzewa?

Niespodziewanie, rozwiązując prosty zdawałoby się problem, napotykamy na ciekawą i dość złożoną teorię informatyczną o modelowaniu struktury drzew.

Jeden z algorytmów "przechodzenia" przez takie drzewo nazywa się MPTT (Modified Preorder Tree Traversal). Django zawiera odpowiednią bibliotekę zbudowaną za zasadzie tego algorytmu. Jest to Django-mptt. Rzeczywiście, wszystkie bardziej rozbudowane CMS-y napisane w Django korzystają z tej biblioteki. Jest ona jednak dość skomplikowana i dla mojego prostego projektu szukałem rozwiązania mniej zamotanego.

Okazało się, że najlepszy przyjaciel developera jakim jest wyszukiwarka internetowa szybko znalazł rozwiązanie. Prosty generator menu został napisany przez Juliana Phalipa i dostępny jest na serwerach google.
Dokumentacja projektu jest dosyć jasna, więc napiszę tylko, że po wgraniu aplikacji do katalogu z naszym projektem należy oczywiście dopisać ją do INSTALLED_APPS w settings.py, wykonać manage.py syncdb i już mamy w panelu administratora nasze menu. Budujemy jego strukturę poprzez wpisanie dla każdej pozycji URLa i tytułu, no i oczywiście "rodzica". Aplikacja ma wygodny "klikany" sposób na przesuwanie pozycji w górę lub w dół.

Żeby zobaczyć menu na naszej stronie musimy w katalogu z naszymi szablonami utworzyć podkatalog "treemenus", a wnim dwa pliki szablonów: menu.html i menu_item.html. U mnie wyglądają one tak:
menu.html:

{% load tree_menu_tags %}
<ul>
   {% for menu_item in menu.root_item.children %}
   {% show_menu_item menu_item %}
   {% endfor %}
</ul>

menu_item.html:

{% load tree_menu_tags %}
{% if menu_item.has_children %}
<li><a class="daddy" href="{{ menu_item.url }}">{{ menu_item.caption }}</a>
       <ul>
          {% for child in menu_item.children %}
          {% show_menu_item child %}
          {% endfor %}
       </ul>
</li>
{% else %}
   <li><a href="{{ menu_item.url }}">{{ menu_item.caption }}</a></li>
{% endif %}

Widać tu jak działa ten algorytm. Jest to klasyczna rekurencja i jest to
najprostsze (ale nie najbardziej wydajne) radzenie sobie z drzewami.

Jedyną rzeczą, która pozostała do zrobienia jest umieszczenie w szablonie strony, na jej początku, znacznika {% load tree_menu_tags %}, a w miejscu, gdzie ma być wyświetlane menu - znacznika {% show_menu "nazwa_menu" %}.

Odpowiedzi

Aktualna strona - current

Skoro tak ładnie można sobie poradzić z menu wielopoziomowym to warto jeszcze zaopatrzyć stronę w dodawanie klasy do znacznika li oznaczającej aktualnie wyświetlaną stronę. Tak aby patrząc na menu użytkownik od razu wiedział gdzie się w ramach drzewa znajduje.

Jak najbardziej. Można

Jak najbardziej. Można zresztą to osiągnąć przez zdefiniowanie dodatkowego znacznika w szablonie Django.