Здравствуйте! Не так давно понадобилось связать в одно меню несколько разных, несвязанных друг с другом моделей и спустя некоторое время, родилось вот такое решение.
Итак, требования были такими:
Итак, приступим…
Для деревьев я использовал mptt, для рисования в админке использовал часть feinCMS. Написал два простых класса Menu и MenuItem.
Файл models.py
В этом коде нет ничего особенного или сложного, поэтому без комментариев и пояснений. И класс для пунктов меню.
Добавляем в наш settings.py свойство THEMENU, в который мы будем добавлять те модели, которые соединены с нашим приложением отношением OneToOneField. Указываем названия моделей, а НЕ приложений. Все модели указываются в нижнем регистре.
Файл settings.py
Дальше, я написал простой шаблонный тег включения и фильтр и вся работа, по сути, заключена в них. Создаем папки templatetags и templates в папке с нашим приложением и создаем файлы __init__.py и get_menu.py в папке templatetags и файл menu.html в папке templates.
Файл get_menu.py
Итак, что делает фильтр. Есть зарезервированные опции, такие как, menu_name, menu_css, menu_url, которые берут значения из пункта меню и есть get_url, который берет get_absolute_url() из Вашей модели, а также, Вы можете указать в фильтр любое значение, которое соответствует Вашему полю в Вашей модели и фильр всё выполнит за Вас.
Вот так подключаем в свой шаблон:
Передаем позицию нужного меню и получаем результат.
Спасибо за внимание!
Ссылка на github
Итак, требования были такими:
- Минимальное связывание или полное отсутствие оного
- Доступ из админки
- Удобная и гибкая настройка верстки
- Возможность древовидной структуры
Итак, приступим…
Для деревьев я использовал 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