Навигация в Джанго
Я только что сделал свой первый маленький веб-приложение в django, и я люблю его. Я собираюсь начать преобразование старого производственного PHP-сайта в django, и в качестве части его шаблона есть панель навигации.
в PHP я проверяю URL каждого параметра nav против текущего URL-адреса в коде шаблона и применяю класс CSS, если они выстраиваются. Это ужасно грязно.
есть ли что-то лучше для django или хороший способ обработки кода в шаблоне?
начать, как я могу получить текущий URL-адрес?
28 ответов:
Я использую наследование шаблонов для настройки навигации. Например:
базы.HTML-код
<html> <head>...</head> <body> ... {% block nav %} <ul id="nav"> <li>{% block nav-home %}<a href="{% url home %}">Home</a>{% endblock %}</li> <li>{% block nav-about %}<a href="{% url about %}">About</a>{% endblock %}</li> <li>{% block nav-contact %}<a href="{% url contact %}">Contact</a>{% endblock %}</li> </ul> {% endblock %} ... </body> </html>
о программе.HTML-код
{% extends "base.html" %} {% block nav-about %}<strong class="nav-active">About</strong>{% endblock %}
вам не нужно, если это сделать, посмотрите на следующий код:
tags.py
@register.simple_tag def active(request, pattern): import re if re.search(pattern, request.path): return 'active' return ''
urls.py
urlpatterns += patterns('', (r'/$', view_home_method, 'home_url_name'), (r'/services/$', view_services_method, 'services_url_name'), (r'/contact/$', view_contact_method, 'contact_url_name'), )
базы.HTML-код
{% load tags %} {% url 'home_url_name' as home %} {% url 'services_url_name' as services %} {% url 'contact_url_name' as contact %} <div id="navigation"> <a class="{% active request home %}" href="{{ home }}">Home</a> <a class="{% active request services %}" href="{{ services }}">Services</a> <a class="{% active request contact %}" href="{{ contact }}">Contact</a> </div>
вот именно. для деталей реализации взгляните на:
gnuvince.wordpress.com
110j.wordpress.com
мне понравилась чистота 110j выше, поэтому я взял большую часть его и рефакторинг, чтобы решить 3 проблемы, которые у меня были с ним:
- регулярное выражение сопоставление url-адреса "home" со всеми другие
- мне нужно несколько URL-адресов сопоставлено с одной вкладкой навигации, так что я нужен более сложный тег, который принимает переменное количество параметров
- исправлены некоторые проблемы с url
вот это:
## tags.py from django import template register = template.Library() @register.tag def active(parser, token): args = token.split_contents() template_tag = args[0] if len(args) < 2: raise template.TemplateSyntaxError, "%r tag requires at least one argument" % template_tag return NavSelectedNode(args[1:]) class NavSelectedNode(template.Node): def __init__(self, patterns): self.patterns = patterns def render(self, context): path = context['request'].path for p in self.patterns: pValue = template.Variable(p).resolve(context) if path == pValue: return "active" # change this if needed for other bootstrap version (compatible with 3.2) return "" ## urls.py urlpatterns += patterns('', url(r'/$', view_home_method, {}, name='home_url_name'), url(r'/services/$', view_services_method, {}, name='services_url_name'), url(r'/contact/$', view_contact_method, {}, name='contact_url_name'), url(r'/contact/$', view_contact2_method, {}, name='contact2_url_name'), ) ## base.html {% load tags %} {% url home_url_name as home %} {% url services_url_name as services %} {% url contact_url_name as contact %} {% url contact2_url_name as contact2 %} <div id="navigation"> <a class="{% active request home %}" href="home">Home</a> <a class="{% active request services %}" href="services">Services</a> <a class="{% active request contact contact2 %}" href="contact">Contact</a> </div>
Я автор книги django-lineage который я написал специально, чтобы решить этот вопрос: D
я стал раздражаться, используя (совершенно приемлемый) метод jpwatts в своих собственных проектах и черпал вдохновение из ответа 110j. Родословная выглядит так:
{% load lineage %} <div id="navigation"> <a class="{% ancestor '/home/' %}" href="/home/">Home</a> <a class="{% ancestor '/services/' %}" href="/services/">Services</a> <a class="{% ancestor '/contact/' %}" href="/contact/">Contact</a> </div>
ancestor
просто заменяется на "активный", если аргумент соответствует началу текущего URL-адреса страницы.переменные Аргументы, и полная
{% url %}
тип обратного резолюции также поддерживается. Я посыпал несколько вариантов конфигурации и немного конкретизировал его и упаковал для всех, чтобы использовать.Если кто-то заинтересован, прочитайте немного больше об этом по адресу:
>> github.com/marcuswhybrow/django-lineage
С Django 1.5:
во всех универсальных представлениях на основе классов (или в любом наследовании представлений на основе классов из ContextMixin), контекстный словарь содержит переменную представления это указывает на экземпляр представления.
так что если вы используете такие представления, вы могли бы добавить что-то likie
breadcrumbs
в поле уровне класса и использовать его в шаблонах.пример кода:
class YourDetailView(DetailView): breadcrumbs = ['detail'] (...)
в шаблоне вы можете использовать его в вот так:
<a href="/detail/" {% if 'detail' in view.breadcrumbs %}class="active"{% endif %}>Detail</a>
если вы хотите дополнительно "выделить" родительские элементы навигации, вам нужно расширить
breadcrumbs
список:class YourDetailView(DetailView): breadcrumbs = ['dashboard', 'list', 'detail'] (...)
... и в вашем шаблоне:
<a href="/dashboard/" {% if 'dashboard' in view.breadcrumbs %}class="active"{% endif %}>Dashboard</a> <a href="/list/" {% if 'list' in view.breadcrumbs %}class="active"{% endif %}>List</a> <a href="/detail/" {% if 'detail' in view.breadcrumbs %}class="active"{% endif %}>Detail</a>
это простое и чистое решение и работает довольно хорошо с вложенным оборудованием.
вы можете применить класс или идентификатор к элементу body страницы, а не к определенному элементу nav.
HTML:
<body class="{{ nav_class }}">
CSS:
body.home #nav_home, body.about #nav_about { */ Current nav styles */ }
я делаю это так:
<a class="tab {% ifequal active_tab "statistics" %}active{% endifequal %}" href="{% url Member.Statistics %}">Statistics</a>
и тогда все, что мне нужно на мой взгляд добавить
{'active_tab': 'statistics'}
в мой контекстный словарь.если вы используете
RequestContext
вы можете получить текущий путь в шаблоне так:{{ request.path }}
а на ваш взгляд:
from django.template import RequestContext def my_view(request): # do something awesome here return template.render(RequestContext(request, context_dict))
Я взял код из nivhab выше и удалил некоторые wierdness и сделал его в чистый templatetag, изменил его так, что /account/edit/ все равно сделает /account/ tab активным.
#current_nav.py from django import template register = template.Library() @register.tag def current_nav(parser, token): import re args = token.split_contents() template_tag = args[0] if len(args) < 2: raise template.TemplateSyntaxError, "%r tag requires at least one argument" % template_tag return NavSelectedNode(args[1]) class NavSelectedNode(template.Node): def __init__(self, url): self.url = url def render(self, context): path = context['request'].path pValue = template.Variable(self.url).resolve(context) if (pValue == '/' or pValue == '') and not (path == '/' or path == ''): return "" if path.startswith(pValue): return ' class="current"' return "" #template.html {% block nav %} {% load current_nav %} {% url home as home_url %} {% url signup as signup_url %} {% url auth_login as auth_login_url %} <ul class="container"> <li><a href="{{ home_url }}"{% current_nav home_url %} title="Home">Home</a></li> <li><a href="{{ auth_login_url }}"{% current_nav auth_login_url %} title="Login">Login</a></li> <li><a href="{{ signup_url }}"{% current_nav signup_url %} title="Signup">Signup</a></li> </ul> {% endblock %}
Это всего лишь вариант решения css, предложенного Toba выше:
включите в базовый шаблон следующее:
<body id="section-{% block section %}home{% endblock %}">
затем в ваших шаблонах, которые расширяют базовое использование:
{% block section %}show{% endblock %}
затем вы можете использовать css для выделения текущей области на основе тега body (например, если у нас есть ссылка с идентификатором nav-home):
#section-home a#nav-home{ font-weight:bold; }
вы могли бы использовать обратная функция с соответствующими параметрами, чтобы получить текущий url.
Спасибо за ваши ответы до сих пор, господа. Я что-то немного другое..
в моем шаблоне:
<li{{ link1_active }}>...link...</li> <li{{ link2_active }}>...link...</li> <li{{ link3_active }}>...link...</li> <li{{ link4_active }}>...link...</li>
как только я выяснил, на какой странице Я нахожусь в логике (обычно в urls.py), я пас
class="selected"
как часть контекста под правильным именем шаблона.например, если я на странице link1, я добавлю
{'link1_active':' class="selected"'}
контекст для шаблона, чтобы зачерпнуть и влить.Это, кажется, работает и это довольно очистить.
Edit: чтобы сохранить HTML из моего контроллера / представления, я немного изменил это:
<li{% if link1_active %} class="selected"{% endif %}>...link...</li> <li{% if link2_active %} class="selected"{% endif %}>...link...</li> ...
это делает шаблон немного менее читаемым, но я согласен, лучше не проталкивать raw HTML из файла url.
у меня есть несколько меню на одной странице, которые создаются динамически с помощью цикла. Сообщения выше, относящиеся к контексту, дали мне быстрое решение. Надеюсь, это кому-то поможет. (Я использую это в дополнение к активному тегу шаблона-мое исправление решает динамическую проблему). Это кажется глупым сравнением, но оно работает. Я решил назвать переменные active_something-единственное в своем роде-уникально, таким образом, она работает с вложенными меню.
вот часть представления (достаточно, чтобы понять что я делаю):
def project_list(request, catslug): "render the category detail page" category = get_object_or_404(Category, slug=catslug, site__id__exact=settings.SITE_ID) context = { 'active_category': category, 'category': category, 'category_list': Category.objects.filter(site__id__exact=settings.SITE_ID), }
и это из шаблона:
<ul> {% for category in category_list %} <li class="tab{% ifequal active_category category %}-active{% endifequal %}"> <a href="{{ category.get_absolute_url }}">{{ category.cat }}</a> </li> {% endfor %} </ul>
мое решение состояло в том, чтобы написать простой контекстный процессор для установки переменной на основе пути запроса:
def navigation(request): """ Custom context processor to set the navigation menu pointer. """ nav_pointer = '' if request.path == '/': nav_pointer = 'main' elif request.path.startswith('/services/'): nav_pointer = 'services' elif request.path.startswith('/other_stuff/'): nav_pointer = 'other_stuff' return {'nav_pointer': nav_pointer}
(Не забудьте добавить свой пользовательский процессор в TEMPLATE_CONTEXT_PROCESSORS in settings.py.)
затем в базовом шаблоне я использую тег ifequal для каждой ссылки, чтобы определить, следует ли добавлять "активный" класс. Конечно, этот подход строго ограничен гибкостью вашей структуры пути, но он работает для моего относительно скромного развертывания.
Я просто хотел поделиться своим незначительным улучшением с постом нивхаба. В моем приложении у меня есть субнавигации, и я не хотел скрывать их, используя только CSS, поэтому мне нужен был какой-то тег "if" для отображения субнавигации для элемента или нет.
from django import template register = template.Library() @register.tag def ifnaviactive(parser, token): nodelist = parser.parse(('endifnaviactive',)) parser.delete_first_token() import re args = token.split_contents() template_tag = args[0] if len(args) < 2: raise template.TemplateSyntaxError, "%r tag requires at least one argument" % template_tag return NavSelectedNode(args[1:], nodelist) class NavSelectedNode(template.Node): def __init__(self, patterns, nodelist): self.patterns = patterns self.nodelist = nodelist def render(self, context): path = context['request'].path for p in self.patterns: pValue = template.Variable(p).resolve(context) if path == pValue: return self.nodelist.render(context) return ""
вы можете использовать это в основном так же, как активный тег:
{% url product_url as product %} {% ifnaviactive request product %} <ul class="subnavi"> <li>Subnavi item for product 1</li> ... </ul> {% endifnaviactive %}
просто еще один ehnancement оригинального решения.
это принимает несколько шаблонов и которые лучше всего также безымянные шаблоны, написанные как относительный URL-адрес, завернутый в'", например:
{% url admin:clients_client_changelist as clients %} {% url admin:clients_town_changelist as towns %} {% url admin:clients_district_changelist as districts %} <li class="{% active "/" %}"><a href="/">Home</a></li> <li class="{% active clients %}"><a href="{{ clients }}">Clients</a></li> {% if request.user.is_superuser %} <li class="{% active towns districts %}"> <a href="#">Settings</a> <ul> <li><a href="{{ towns }}">Towns</a></li> <li><a href="{{ districts }}">Districts</a></li> </ul> </li> {% endif %}
тег выглядит так:
from django import template register = template.Library() @register.tag def active(parser, token): args = token.split_contents() template_tag = args[0] if len(args) < 2: raise template.TemplateSyntaxError, "%r tag requires at least one argument" % template_tag return NavSelectedNode(args[1:]) class NavSelectedNode(template.Node): def __init__(self, urls): self.urls = urls def render(self, context): path = context['request'].path for url in self.urls: if '"' not in url: cpath = template.Variable(url).resolve(context) else: cpath = url.strip('"') if (cpath == '/' or cpath == '') and not (path == '/' or path == ''): return "" if path.startswith(cpath): return 'active' return ""
я использовал jquery, чтобы выделить мои навигационные панели. Это решение просто добавляет класс css "active" к элементу, который соответствует селектору css.
<script type="text/javascript" src="/static/js/jquery.js"></script> <script> $(document).ready(function(){ var path = location.pathname; $('ul.navbar a.nav[href$="' + path + '"]').addClass("active"); }); </script>
небольшое улучшение над @tback'ы ответ, без каких-либо
%if%
теги:# navigation.py from django import template from django.core.urlresolvers import resolve register = template.Library() @register.filter(name="activate_if_active", is_safe=True) def activate_if_active(request, urlname): if resolve(request.get_full_path()).url_name == urlname: return "active" return ''
используйте его в своем шаблоне так:
{% load navigation %} <li class="{{ request|activate_if_active:'url_name' }}"> <a href="{% url 'url_name' %}">My View</a> </li>
и включения
"django.core.context_processors.request"
в своемTEMPLATE_CONTEXT_PROCESSORS
настройка.
слегка изменив ответ Андреаса, похоже, вы можете пройти в названии маршрута от urls.py к тегу шаблона. В моем примере
my_tasks
, а затем в функции тега шаблона используйте обратную функцию, чтобы определить, каким должен быть URL-адрес, тогда вы можете сопоставить это с URL-адресом в объекте запроса (доступно в контексте шаблона)from django import template from django.core.urlresolvers import reverse register = template.Library() @register.tag def active(parser, token): args = token.split_contents() template_tag = args[0] if len(args) < 2: raise template.TemplateSyntaxError, "%r tag requires at least one argument" % template_tag return NavSelectedNode(args[1:]) class NavSelectedNode(template.Node): def __init__(self, name): self.name = name def render(self, context): if context['request'].path == reverse(self.name[1]): return 'active' else: return ''
urls.py
url(r'^tasks/my', my_tasks, name = 'my_tasks' ),
шаблон.HTML-код
<li class="{% active request all_tasks %}"><a href="{% url all_tasks %}">Everyone</a></li>
Я знаю, что опаздываю на вечеринку. Мне не понравилось ни одно из популярных решений, хотя:
The метод блок кажется неправильным: я считаю, что навигация должна быть самодостаточной.
The способ template_tag кажется неправильным: мне не нравится, что я должен сначала получить url-адрес из url-тега. Кроме того, я думаю, что css-класс должен быть определен в шаблоне, а не в теге.
поэтому я написал фильтр, который не имеет недостатков I описанный выше. Он возвращает
True
если url-адрес активен и поэтому может использоваться с{% if %}
:{% load navigation %} <li{% if request|active:"home" %} class="active"{% endif %}><a href="{% url "home" %}">Home</a></li>
код:
@register.filter(name="active") def active(request, url_name): return resolve(request.path_info).url_name == url_name
просто убедитесь, что вы используете
RequestContext
на страницах с навигацией или для включения запроса context_processor в вашемsettings.py
TEMPLATE_CONTEXT_PROCESSORS = ( ... 'django.core.context_processors.request', )
Я видел jpwatts',110j ' s,nivhab ' s & Маркус Whybrowответы, но все они, кажется, не хватает в чем-то: как насчет корневого пути ? Почему он всегда активен ?
поэтому я сделал другой способ, проще, который делает "контроллер" решает сам по себе, и я думаю, что он решает большинство больших проблем.
вот мой пользовательский тег:
## myapp_tags.py @register.simple_tag def nav_css_class(page_class): if not page_class: return "" else: return page_class
затем "контроллер" объявляет классы CSS необходимо (на самом деле, самое главное, что он заявляет о своем присутствии в шаблоне)
## views.py def ping(request): context={} context["nav_ping"] = "active" return render(request, 'myapp/ping.html',context)
и, наконец, я делаю это в моей панели навигации:
<!-- sidebar.html --> {% load myapp_tags %} ... <a class="{% nav_css_class nav_home %}" href="{% url 'index' %}"> Accueil </a> <a class="{% nav_css_class nav_candidats %}" href="{% url 'candidats' %}"> Candidats </a> <a class="{% nav_css_class nav_ping %}" href="{% url 'ping' %}"> Ping </a> <a class="{% nav_css_class nav_stat %}" href="{% url 'statistiques' %}"> Statistiques </a> ...
так что каждая страница имеет свой собственный
nav_css_class
значение для установки, и если оно установлено, шаблон становится активным: нет необходимостиrequest
в контексте шаблона нет синтаксического анализа URL и больше нет проблем с несколькими URL-страницами или корневой страницей.
вот мой пойти на это. В итоге я реализовал класс в своих представлениях, который содержит мою навигационную структуру (плоскую с некоторыми метаданными). Затем я вставляю это в шаблон и визуализирую его.
мое решение-с названием i18n. Она, вероятно, должна быть отведена немного больше, но я не заморачивался с этим на самом деле.
views.py:
from django.utils.translation import get_language, ugettext as _ class Navi(list): items = (_('Events'), _('Users'), ) def __init__(self, cur_path): lang = get_language() first_part = '/' + cur_path.lstrip('/').split('/')[0] def set_status(n): if n['url'] == first_part: n['status'] == 'active' for i in self.items: o = {'name': i, 'url': '/' + slugify(i)} set_status(o) self.append(o) # remember to attach Navi() to your template context! # ie. 'navi': Navi(request.path)
Я определил шаблон логики с помощью включает в себя, как это. Базовый шаблон:
{% include "includes/navigation.html" with items=navi %}
фактическое включить (включает / навигация.html):
<ul class="nav"> {% for item in items %} <li class="{{ item.status }}"> <a href="{{ item.url }}">{{ item.name }}</a> </li> {% endfor %} </ul>
надеюсь кто-то найдет это полезным! Я думаю, было бы довольно легко расширить эту идею для поддержки вложенных иерархий и т. д.
создать шаблон включения " интрасеть / nav_item.html":
{% load url from future %} {% url view as view_url %} <li class="nav-item{% ifequal view_url request.path %} current{% endifequal %}"> <a href="{{ view_url }}">{{ title }}</a> </li>
и включить его в элемент nav:
<ul> {% include "intranet/nav_item.html" with view='intranet.views.home' title='Home' %} {% include "intranet/nav_item.html" with view='crm.views.clients' title='Clients' %} </ul>
и вам нужно добавить это в настройки:
from django.conf import global_settings TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + ( 'django.core.context_processors.request', )
вот довольно простое решение,https://github.com/hellysmile/django-activeurl
от этого и
{% url 'some_urlpattern_name' as url %} <a href="{{url}}"{% if request.path == url %} class="active"{% endif %}>Link</a>
повторите для каждой ссылки.
Я также использовал jQuery, чтобы выделить его и найти его более элегантным, чем загромождение шаблона несемантическими тегами шаблона Django.
приведенный ниже код работает с вложенными выпадающими списками в bootstrap 3 (выделяет как родительский, так и дочерний
<li>
элемент.// DOM Ready $(function() { // Highlight current page in nav bar $('.nav, .navbar-nav li').each(function() { // Count the number of links to the current page in the <li> var matched_links = $(this).find('a[href]').filter(function() { return $(this).attr('href') == window.location.pathname; }).length; // If there's at least one, mark the <li> as active if (matched_links) $(this).addClass('active'); }); });
это также довольно легко добавить
click
событиеreturn false
(или поменять до#
) для текущей страницы, без изменения шаблона/HTML-разметку:var matched_links = $(this).find('a[href]').filter(function() { var matched = $(this).attr('href') == window.location.pathname; if (matched) $(this).click(function() { return false; }); return matched; }).length;
Я использую комбинацию этого mixin для представлений на основе классов:
class SetActiveViewMixin(object): def get_context_data(self, **kwargs): context = super(SetActiveViewMixin, self).get_context_data(**kwargs) context['active_nav_menu'] = { self.request.resolver_match.view_name: ' class="pure-menu-selected"' } return context
С этим в шаблоне:
<ul> <li{{active_nav_menu.node_explorer }}><a href="{% url 'node_explorer' '' %}">Explore</a></li> <li{{active_nav_menu.node_create }}><a href="{% url 'node_create' path %}">Create</a></li> <li{{active_nav_menu.node_edit }}><a href="{% url 'node_edit' path %}">Edit</a></li> <li{{active_nav_menu.node_delete }}><a href="{% url 'node_delete' path %}">Delete</a></li> </ul>
мой немного похож на другой подход JS, представленный ранее.. только без jQuery...
скажем, у нас есть в базе.html следующее:
<div class="pure-u-1 pure-menu pure-menu-open pure-menu-horizontal header" > <ul class=""> <li id="home"><a href="{% url 'article:index' %}">Home</a></li> <li id="news"><a href="{% url 'article:index' %}">News</a></li> <li id="analysis"><a href="{% url 'article:index' %}">Analysis</a></li> <li id="opinion"><a href="{% url 'article:index' %}">Opinion</a></li> <li id="data"><a href="{% url 'article:index' %}">Data</a></li> <li id="events"><a href="{% url 'article:index' %}">Events</a></li> <li id="forum"><a href="{% url 'article:index' %}">Forum</a></li> <li id="subscribe"><a href="{% url 'article:index' %}">Subscribe</a></li> </ul> <script type="text/javascript"> (function(){ loc=/\w+/.exec(window.location.pathname)[0]; el=document.getElementById(loc).className='pure-menu-selected'; })(); </script> </div>
Я просто сделал свою иерархию, чтобы следовать определенному шаблону URL... после адреса хоста... у меня есть моя основная категория, например, дом, новости, анализ и т. д. и регулярное выражение просто вытаскивает первое слово из местоположения
вдохновленный этим решение, Я начал использовать этот подход:
**Placed in templates as base.html** {% block tab_menu %} <ul class="tab-menu"> <li class="{% if active_tab == 'tab1' %} active{% endif %}"><a href="#">Tab 1</a></li> <li class="{% if active_tab == 'tab2' %} active{% endif %}"><a href="#">Tab 2</a></li> <li class="{% if active_tab == 'tab3' %} active{% endif %}"><a href="#">Tab 3</a></li> </ul> {% endblock tab_menu %} **Placed in your page template** {% extends "base.html" %} {% block tab_menu %} {% with active_tab="tab1" %} {{ block.super }} {% endwith %} {% endblock tab_menu %}