Pull to refresh

Django. Меню из разных моделей

Здравствуйте! Не так давно понадобилось связать в одно меню несколько разных, несвязанных друг с другом моделей и спустя некоторое время, родилось вот такое решение.

Итак, требования были такими:

  • Минимальное связывание или полное отсутствие оного
  • Доступ из админки
  • Удобная и гибкая настройка верстки
  • Возможность древовидной структуры


Итак, приступим…


Для деревьев я использовал mptt, для рисования в админке использовал часть feinCMS. Написал два простых класса Menu и MenuItem.

Файл models.py

from django.utils.translation import ugettext_lazy as _
from django.db import models
import mptt

CHOICES_VISIBLE = [
    ('1', _('Yes')),
    ('0', _('No')),
]

class Menu(models.Model):
    name = models.CharField(
        max_length = 80,
        verbose_name = _('name'),
    )
    position = models.CharField(
        max_length = 80,
        verbose_name = _('position'),
    )
    visible = models.CharField(
        max_length = 2,
        verbose_name = _('visible'),
        choices = CHOICES_VISIBLE,
        default = CHOICES_VISIBLE[0][0],
    )
    
    class Meta:
        verbose_name = _('menu')
        verbose_name_plural = _('menu')
    
    def __unicode__(self):
        return self.name


В этом коде нет ничего особенного или сложного, поэтому без комментариев и пояснений. И класс для пунктов меню.

class MenuItem(models.Model):
    menu = models.ForeignKey(
        Menu,
        verbose_name = _('menu'),
    )
    parent = models.ForeignKey(
        'self',
        verbose_name = _('parent'),
        related_name = u'child',
        blank = True,
        null = True,
    )
    name = models.CharField(
        max_length = 150,
        verbose_name = _('name'),
    )
    uri = models.SlugField(
        verbose_name = _('URL'),
        help_text = _('If you do not want to connect this item to your models, you can specify a URL explicitly'),
        blank = True,
        null = True,
    )
    visible = models.CharField(
        max_length = 1,
        verbose_name = _('visible'),
        choices = CHOICES_VISIBLE,
        default = CHOICES_VISIBLE[0][0],
    )
    css_class = models.CharField(
        max_length = 50,
        verbose_name = _('CSS class'),
        help_text = u'',
        blank = True,
        null = True,
    )
    
    class Meta:
        verbose_name = _('menu item')
        verbose_name_plural = _('menu items')
    
    def get_absolute_url(self):
        return '/%s' % self.uri
    
    def __unicode__(self):
        return self.name

mptt.register(MenuItem,)


Добавляем в наш settings.py свойство THEMENU, в который мы будем добавлять те модели, которые соединены с нашим приложением отношением OneToOneField. Указываем названия моделей, а НЕ приложений. Все модели указываются в нижнем регистре.

Файл settings.py

THEMENU = ['our_model', 'our_model2']


Дальше, я написал простой шаблонный тег включения и фильтр и вся работа, по сути, заключена в них. Создаем папки templatetags и templates в папке с нашим приложением и создаем файлы __init__.py и get_menu.py в папке templatetags и файл menu.html в папке templates.

Файл get_menu.py

from django import template
from django.conf import settings
from themenu.models import MenuItem

register = template.Library()

@register.filter
def getattribute(obj, option):
    current = {}
    for item in settings.THEMENU:
        try:
            current = getattr(obj, item)
            if option == 'menu_url':
                obj = obj.get_absolute_url()
            elif option == 'menu_name':
                obj = obj.name
            elif option == 'menu_css':
                obj = obj.css_class
            elif option == 'get_url':
                obj = current.get_absolute_url()
            else:
                obj = getattr(current, option)
        except:
            pass
    return obj

# Сам тег просто извлекает значения из базы. Координирует всю логику фильтр getattribute
@register.inclusion_tag('menu.html')
def get_menu(position):
    menu = MenuItem.objects.filter(
        menu__position = position,
        menu__visible = 1,
        visible = 1,
    ).order_by('tree_id')
    if menu:
        return {'menu': menu}
    else:
        msg = _('Did not match any of the menu item!')
        raise template.TemplateSyntaxError(msg)


Итак, что делает фильтр. Есть зарезервированные опции, такие как, menu_name, menu_css, menu_url, которые берут значения из пункта меню и есть get_url, который берет get_absolute_url() из Вашей модели, а также, Вы можете указать в фильтр любое значение, которое соответствует Вашему полю в Вашей модели и фильр всё выполнит за Вас.

Вот так подключаем в свой шаблон:

{% load get_menu %}

<ul>
{% get_menu 'position' %}
</ul>


Передаем позицию нужного меню и получаем результат.

Спасибо за внимание!

Ссылка на github
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.