Пользователь
0,0
рейтинг
21 ноября 2013 в 22:09

Разработка → Навигация в шаблонах Django

Наверно в каждом проекте есть система навигации — пользователи кликают по ссылкам, менюшкам и нам(разработчикам\дизайнерам\верстальщикам) надо как-то «подсвечивать» страницу\ссылку на которой сейчас находится пользователь.

Предоставляю не тривиальное решение очень тривиальной задачи при разработки навигации в Django проектах.

Для не терпеливых можно сразу взглянуть на проект на github.

Самое тривиальное решение


В каждой вьюхе в контексте передаем некую переменную, пусть будет «location» и потом беспощадно хардкодим:

<ul>
  <li {% if location=='random_page' %} class="active" {% endif %}>
    <a href="{% url 'random_page' %}">
      {% trans 'random page' %}
    </a>
  </li>
</ul>

Получается — к каждой ссылки уникальное значение «location», что порождает очень много лишнего кода, когда ссылок\вьюх становится много практически не возможно запомнить какой «location» к какой ссылке, а так же верстальщик может быть крайне не доволен необходимостью вникать в логику питоновских вьюх и какое значение от куда попадает в контекст.

Можно передавать «location» такой же как именная ссылка данной вьюхи и навигация станет слегка наглядней.

Реализация через простой «template filter»


собственно фильтр:

from django import template


register = template.Library()


def active(url, request):
    if url == request.get_full_path():
        return True
    else:
        return False


register.filter('active', active)

дальше в шаблоне:

{% load active %}

<ul>
  {% url 'random_page' as random_page %}
  <li {% if random_page|active:request %} class="active" {% endif %}>
    <a href="{{ random_page }}">
      {% trans 'random page' %}
    </a>
  </li>
</ul>

Передавать переменные с вьюх уже не нужно, можно заменить сравнение на .startswith() или вовсе на регулярки и добавить поддержу меню, например светить ссылку на "/menu/" когда пользователь находится на странице "/menu/submenu/". Но рутина никуда не делась…

Мои велосипеды — django-activeurl


Рутинные действия напрочь отсутствуют, а именно:

{% load activeurl %}

{% activeurl %}
  <ul>
    <li>
      <a href="{% url 'random_page' %}">
        {% trans 'random page' %}
      </a>
    </li>
  </ul>
{% endactiveurl %}

немного магии…

<ul>
  <li class="active">
    <a href="/random_page/">
      random page
    </a>
  </li>
</ul>

Установка как и у всех питоновских батареек:

pip install django-activeurl

Все нюансы описаны тут.

Теперь, немного о том как всё это работает — из переданного html с помощью lxml строится дерево, в нем идет поиск HTML элементов которые надо подсветить(по умолчанию «li»), потом в этом тэге берутся все ссылки и их параметр «href» сравнивается с «request.get_full_path()».

Реализованные фичи:


Поменьше всем рутинных задач при разработке!
@hellysmile
карма
17,0
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (11)

  • +2
    ИМХО, корректнее реализовывать данную задачу следующим образом:

    {% url 'menu_1' as menu_url_1 %}
    {% url 'menu_2' as menu_url_2 %}
    {% ifequal request.path menu_url_1 %}menu 1 is active{% endifequal %}
    {% ifequal request.path menu_url_2 %}menu 2 is active{% endifequal %}
    

    Разумеется не забываем подключить django.core.context_processors.request
    • +1
      На дворе django 1.6, а вы до сих пор пользуетесь ifequal? Серьезно?

      Заодно вот такой велосипед:
      _urls = (
          (reverse_lazy('app1.index'), u'App 1'),
          (reverse_lazy('app2.index'), u'App 2'),
          (reverse_lazy('app3.index'), u'App 3'),
      )
      
      @register.inclusion_tag('menu.html', takes_context=True)
      def navigation_menu(context):
          request = context['request']
      
          urls = []
          full_path = request.get_full_path()
          for href, name in _urls:
              is_current = full_path.startswith(href)
              urls.append([href, name, is_current])
      
          return {
              'urls': urls,
          }
      

      • 0
        На дворе django 1.6, а вы до сих пор пользуетесь ifequal? Серьезно?

        Я знаю про {% if a == b %}, но вот момент когда ifequal признан устаревшим, честно говоря, я пропустил, тем более что в dev версии доков об этом ни слова (разве что упомянуто об эквивалентности этих записей и всё). Так что дело тут просто в привычке, хотя да, через if запись будет получаться на несколько символов короче.
  • –1
    Я делаю так:
    templatetags/navigation.py
    import re
    from django import template
    
    register = template.Library()
    
    @register.simple_tag
    def active(request, pattern):
        if re.search(pattern, request.path):
            return 'active'
        return ''
    


    template.html
    {% load navigation %}
    ... class="first {% active request "^/url1/" %}" ...
    ... class="{% active request "^/url2/" %}" ...
    
  • +3
    Хм. Парсить хтмл, регулярками находить нужный пункт навигации ради того чтобы его подсветить… Вам не кажется что это оверкил?! Я правда вот так сразу не предложу простого и универсального решения. Ваш вариант я пожалуй применял бы только в случае когда менюшки автоматически генерируются.

    Когдато давно в своем проектике я решил эту проблему на уровне наследования в шаблонах. В главном шаблоне
    ...
    <li class="{%block about%}{%endblock%}"><a href="/">О проекте</a></li>
    <li class="{%block search%}{%endblock%}"><a href="/search">Поиск</a></li>
    <li class="{%block contacts%}{%endblock%}"><a href="/contact">Обратная связь</a></li>
    ...
    

    а в шаблонах отдельных страниц просто доблял
    {%block contacts%} active{%endblock%}

    Тупое решение в лоб, но для небольшого проекта в самый раз. Сейчас я бы поискал более изящное решение.
  • 0
    Неужели никто не использует django-treenav?
    • 0
      Ну не всем же нужно древовидное меню и django-mptt:

      Requirements

      django-mptt >= 0.5.2
  • 0
    Более сложный вариант. Пользуюсь им уже года два

    import re
    from django import template
    register = template.Library()
    
    @register.simple_tag
    def active_tag(request, patterns, out='selected'):
        """
        usage:
            <a class="url{% active_tag request "default /home/" %}" href="#">url item 1</a>
            -> <a class="url seleced">url item 1</a>
    
            <a class="url{% active_tag request "/posts/ /allposts/" "custom-css" %}" href="#">url item 2</a>
            -> <a class="url custom-css">url item 2</a>
            
            {% active_tag request '^/account/-/(\w+)/$ ^/account/-/(\w+)/edit/$ ^/account/-/(\w+)/password/$' 'class="selected"'%}
            
            {% active_tag request obj.get_absolute_url %}
        """
        if "default" in patterns.split() and request.path == '/':
            return " %s" % out
        else:
            return " %s" % out if len([p for p in patterns.split() 
                                if re.search(p, request.path) ]) else ''
    
  • 0
    0) таки тянет на оверкилл
    1) а как поступать со вложенной навигацией и вообще случаями менее очевидными, чем request.url === page.url?
    2) свой велосипед — хорошо, но не стоило бы начать с обзора существующих? в том числе и эту статью…
  • 0
    Я пользуюсь menus из django-cms
    docs.django-cms.org/en/2.4.3/getting_started/navigation.html
  • 0
    А почему все так любят привязываться к конкретному URL, типа {% active request "^/url1/" %}, забывая про reverse()? Хардкодинг URL — зло.

    Вот и мой двухколёсный друг:
    @register.filter
    def is_current_page(request, param):
        params = param.split(',')
        name = params[0]
        args, kwargs, as_var = parse_args_and_kwargs(params[1:])
        # Do not mix args and kwargs in reverse() - it is forbidden!
        if args:
            return request.path == reverse(name, args=args)
        elif kwargs:
            return request.path == reverse(name, kwargs=kwargs)
        else:
            return request.path == reverse(name)
    


    В шаблоне:
    {% if request|is_current_page:'shop/product,id=1' %} active {% endif %}
    


    Как уже говорили выше, парсить собственноручно написанный шаблон (не какой-то там чужой сайт!) — это точно оверкилл. Напишите шаблон так, чтоб не парсить.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.