Django: Расширяем тег url

Django 2008-03-26 Dovbush Pavel

Одним из первых встретившихся мне недоразумений в Djnogo стало неудобство ссылок на другие страницы.

Есть тег url, который принемает view и параметры. Если у отображения нет модели, то это то, что надо. Но использовать его для объекта имеющего свой view по-моему не-DRY. Пусть мы немного перемешаем MVC, но объект будет занать свой view-по-умолчанию.

Мысль прописывать полный путь вручную используя операцию форматирования (%) мне показалась неприемлимой. После непродолжительных поисков была найдена функция reverse.

Первым делом, я насоздавал у всех моделей методов url которые возвращают полный адрес до отображения объекта, используя функцию reverse. Ну и использовалось в шаблонах это ввиде {{ object.url }}. Вроде ничего.

Потом обнаружил @permalink и get_absolute_url. (Вот что значит не прочитать сначала всю документацию.) Уже лучше - reverse убран в декоратор. Но теперь придется писать {{ object.get_absolute_url }}. Длииинно... Да и получается, что в шаблонах появляется два типа урлов - один на модельные отображения, другой на безмодельные.

Для унификации этих двух типов я перекрыл стандартный тег url. Смысл работы такой: если ему передан однин параметр, и этот параметр разрешается в контексте в объект, класс которого наследован от django.db.models.Model и у него есть атрибут get_absolute_url, то тег возвращает результат его вызова. Иначе вызывается оригинальный обработчик тега url.

Вот что получилось:

from django.template.defaulttags import url as url_orig

@register.tag
def url(parser, token):
	bits=token.contents.split(' ')
	if len(bits)==2:
		filter_obj=parser.compile_filter(bits[1])
		return URLNode(filter_obj, url_orig(parser, token))
	return url_orig(parser, token)

class URLNode(template.Node):
	def __init__(self, filter_obj, origURLNode):
		self.filter_obj=filter_obj
		self.origURLNode=origURLNode
	def render(self, context):
		obj=self.filter_obj.resolve(context)
		from django.db import models
		if isinstance(obj,django.dbmodels.Model) and hasattr(obj,'get_absolute_url'):
			return obj.get_absolute_url()
		return self.origURLNode.render(context)

Использование:

<a href="{% url article %}">одна из статей</a> и <a href="{% url main_page_view %}">Главная</a>

Такой подход обеспечит большую универсальность. Например в меню:

# settings.py
SITE_MENU=(
	(Page.objects.get(slug='main'), u'Главная', ),
	('some_index_page_view', u'Какой-то индекс', ),
	(Page.objects.get(slug='about'), u'About', ),
	# ...
)
<!-- menu.htm -->
{% for item,title in menu_items %}
<a href="{% url item %}">{{ title }}</a>
{% endfor %}

Тег url используется как для объектов так и для отображений. Пример может и неочень (у меня title берется из другого места), просто идея.

По-моему намного удобнее.

Или я не заметил слона в зоопарке Django?