Навигация в django
Я только что сделал свой первый маленький webapp в 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" со всеми другие!--5-->
- мне нужно несколько 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>
Я автор книги Джанго-линии который я написал специально, чтобы решить этот вопрос: 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-адреса страницы.
переменные Аргументы и full {% url %}
тип обратного резолюции также поддерживается. Я посыпал несколько вариантов конфигурации и немного развел его и упаковал для всех, чтобы использовать.
Если кто-то заинтересован, прочитайте немного больше об этом по адресу:
>> github.com/marcuswhybrow/django-lineage
С Джанго 1.5:
во всех универсальных представлениях на основе классов (или в любом наследующем представлении на основе классов из ContextMixin), контекстный словарь содержит переменную представления это указывает на экземпляр View.
поэтому, если вы используете такие представления, вы можете добавить что-то вроде 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, предложенного Тоба выше:
включите в базовый шаблон следующее:
<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 из файла urls.
у меня есть несколько меню на одной странице, которые создаются динамически с помощью цикла. Сообщения выше, относящиеся к контексту, дали мне быстрое исправление. Надеюсь, это кому-то поможет. (Я использую это в дополнение к активному тегу шаблона-мое исправление решает динамическую проблему). Это кажется глупым сравнением, но оно работает. Я решил назвать переменные active_something-unique и something-unique, таким образом, он работает с вложенными меню.
вот часть представления (достаточно, чтобы понять что я делаю):
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 в 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>
Я знаю, что опаздываю на вечеринку. Мне не понравилось ни одно из популярных решений, хотя:
на метод блок кажется неправильным: я думаю, что навигация должна быть автономной.
на метод 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>
надеюсь кто-то найдет это полезным! Я думаю, было бы довольно легко расширить эту идею для поддержки вложенных иерархий и т. д.
создайте шаблон include " intranet / 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;
Я использую комбинацию этого миксина для представлений на основе классов:
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>
Mine немного похож на другой подход 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 %}