Navigation dans django

104

Je viens de faire ma première petite application web dans django et j'adore ça. Je suis sur le point de commencer à convertir un ancien site PHP de production en django et dans le cadre de son modèle, il y a une barre de navigation.

En PHP, je vérifie l'URL de chaque option de navigation par rapport à l'URL actuelle, dans le code du modèle et j'applique une classe CSS si elles s'alignent. C'est horriblement désordonné.

Y a-t-il quelque chose de mieux pour django ou un bon moyen de gérer le code dans le modèle?

Pour commencer, comment procéder pour obtenir l'URL actuelle?

Oli
la source
J'ai créé github.com/orokusaki/django-active-menu pour cela - il prend en charge les structures d'URL imbriquées et repose sur la configuration plutôt que sur la convention (aussi diabolique que cela puisse paraître ), afin que vous puissiez définir la hiérarchie de votre site comme vous le souhaitez. Vous utilisez juste <a href="{% url "view:name" %}" {% active_class "view:name" %}>. Vous pouvez éventuellement l'utiliser pour générer uniquement la " active"valeur (en la passant Falsecomme deuxième argument à la balise) à ajouter à un attribut de classe existant, mais pour la plupart des liens de navigation, cet exemple est ce que j'utilise.
orokusaki
Cette question semble être liée à celle-ci stackoverflow.com/a/9801473/5739875
Evgeny Bobkin
Peut-être que cette grille aide: djangopackages.org/grids/g/navigation
guettli

Réponses:

74

J'utilise l'héritage de modèle pour personnaliser la navigation. Par exemple:

base.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>

about.html

{% extends "base.html" %}

{% block nav-about %}<strong class="nav-active">About</strong>{% endblock %}
jpwatts
la source
J'aime beaucoup cette idée, en particulier pour sa flexibilité, mais elle s'accompagne d'un compromis moins sec. J'ai cependant commencé à l'utiliser dans un site.
lâche anonyme le
23
Je ne suis pas enthousiasmé par cette approche car il n'est pas rare que plusieurs sections de site soient gérées par le même sous-modèle. Vous finissez donc par mettre des variables personnalisées dans les vues et des conditions dans des modèles, ou réorganiser les sous-modèles pour qu'ils soient tous uniques ... tout simplement pour détecter la section actuelle du site. L'approche des balises de modèle finit par être plus propre à la fin.
shacker
J'ai regardé quelques autres solutions, et il semble qu'elles soient toutes un peu piratées. Celui-ci, au moins, est assez simple et simple à mettre en œuvre / supprimer.
mlissner
J'ai refactoré le <ul id="nav">....</ul>dans un fichier différent, disons tabs.html. Alors maintenant, base.html contenu {%block nav%}{%include "tabs.html"%}{%endblock%}, puis la mise en évidence de l'onglet actif a cessé de fonctionner (dans about.html ci-dessus). Est-ce que je manque quelque chose?
None-da
@Maddy Vous avez suffisamment d'indirection pour que je ne sois pas absolument certain de garder ça droit dans ma tête, mais je pense que la réponse a à voir avec le fonctionnement du includetag. Consultez la note incluse dans la documentation: docs.djangoproject.com/en/dev/ref/templates/builtins/#include Dans votre cas, au moment où vous essayez de remplacer le modèle de base dans about.html, je pense que vous a déjà un bloc HTML rendu, plutôt qu'un bloc de modèle Django en attente de traitement.
jpwatts le
117

Vous n'avez pas besoin d'un if pour le faire, jetez un œil au code suivant:

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'),
)

base.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>

c'est tout. pour plus de détails sur l'implémentation, consultez:
gnuvince.wordpress.com
110j.wordpress.com

pradyunsg
la source
2
Les propriétés de href n'ont pas de crochets de modèle django {{,}}. Par exemple, <a class="{% active request home %}" href="home"> Home </a> doit être, <a class = "{% active request home%}" href = "{{home} } "> Accueil </a> le fichier tags.py aura également besoin de quelques inclusions. Sinon, excellente solution!
bsk le
2
+1 Ceci est plus faiblement couplé à partir des applications. En tant que débutant, j'ai compris que les balises avaient besoin de leur propre application, vous ne pouvez pas simplement la vider dans un fichier global tags.py. J'ai créé une nouvelle application appelée tags et tout s'est bien passé. docs.djangoproject.com/en/dev/howto/custom-template-tags
Keyo
3
@Keyo, créez un répertoire templatetags dans votre projet et ajoutez votre projet aux applications installées. Cela fera également l'affaire. Sinon, comme vous l'avez dit, créez votre site principal en tant qu'application au sein de votre projet.
Josh Smeaton
5
N'oubliez pas d'ajouter django.core.context_processors.requestà votre TEMPLATE_CONTEXT_PROCESSORSinsettings.py
amigcamel
1
Ceci n'est pas valide pour les états qui peuvent être imbriqués, par exemple mysite.com(comme home) et mysite.com/blog, comme le chemin apparaîtra comme /et /blog/(respectivement) donnant une correspondance pour le premier à chaque fois. Si vous ne l'utilisez pas /comme atterrissage, cela peut convenir sinon j'utilise simplement return 'active' if pattern == request.path else ''(je n'ai pas encore vu de problèmes avec cela, mais je viens de configurer en utilisant cela).
nerdwaller
33

J'ai aimé la propreté du 110j ci-dessus, alors j'en ai pris la majeure partie et je l'ai refactorisé pour résoudre les 3 problèmes que j'avais avec:

  1. l'expression régulière correspondait à l'url "home" avec toutes les autres
  2. J'avais besoin de plusieurs URL mappées sur un onglet de navigation , donc j'avais besoin d'une balise plus complexe qui prend une quantité variable de paramètres
  3. correction de quelques problèmes d'URL

C'est ici:

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>
Nivhab
la source
C'est peut-être mieux répondre avec Marcus one, mais comment ça marche avec le "home"? c'est toujours actif? Comment le rendre actif uniquement sur un appel d'URL root (www.toto.com/ et www.toto.com/index)? Les deux réponses ne résulteront pas ce problème ...
DestyNova
20

Je suis l'auteur de django-lineage que j'ai écrit spécifiquement pour résoudre cette question: D

Je suis devenu ennuyé en utilisant la méthode jpwatts (parfaitement acceptable) dans mes propres projets et je me suis inspiré de la réponse de 110j. La lignée ressemble à ceci:

{% 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 est simplement remplacé par "active" si l'argument correspond au début de l'URL de la page courante.

Les arguments variables et {% url %}la résolution inverse de type complet sont également pris en charge. J'ai saupoudré quelques options de configuration, je l'ai un peu étoffé et je l'ai emballé pour que tout le monde puisse l'utiliser.

Si quelqu'un est intéressé, lisez un peu plus à ce sujet sur:

>> github.com/marcuswhybrow/django-lineage

Marcus Whybrow
la source
1
chemins de codage en dur dans le modèle :(
CpILL
10

Depuis Django 1.5 :

Dans toutes les vues génériques basées sur des classes (ou toute vue basée sur des classes héritant de ContextMixin), le dictionnaire de contexte contient une variable de vue qui pointe vers l'instance de View.

Donc, si vous utilisez de telles vues, vous pouvez ajouter quelque chose de similaire en breadcrumbstant que champ de niveau de classe et l'utiliser dans vos modèles.

Exemple de code de vue:

class YourDetailView(DetailView):
     breadcrumbs = ['detail']
     (...)

Dans votre modèle, vous pouvez l'utiliser de cette manière:

<a href="/detail/" {% if 'detail' in view.breadcrumbs %}class="active"{% endif %}>Detail</a>

Si vous souhaitez également "mettre en évidence" les éléments de navigation parents, vous devez étendre la breadcrumbsliste:

class YourDetailView(DetailView):
     breadcrumbs = ['dashboard', 'list', 'detail']
     (...)

... et dans votre modèle:

<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>

C'est une solution simple et propre qui fonctionne plutôt bien avec la navigation imbriquée.

Konrad Hałas
la source
Dans cet exemple, les trois éléments de navigation ne le seraient-ils pas .active?
Oli le
Oui, mais c'est généralement ce que vous souhaitez obtenir avec la navigation à plusieurs niveaux. Vous pouvez bien sûr mettre un élément breadcrumbssi vous le souhaitez. Mais vous avez raison - mon exemple n'est pas le meilleur.
Konrad Hałas
@Oli exemple amélioré.
Konrad Hałas
9

Vous pouvez appliquer une classe ou un identifiant à l'élément body de la page, plutôt qu'à un élément de navigation spécifique.

HTML:

<body class="{{ nav_class }}">

CSS:

body.home #nav_home,
body.about #nav_about { */ Current nav styles */ }
Michael Warkentin
la source
8

Je le fais comme ça:

<a class="tab {% ifequal active_tab "statistics" %}active{% endifequal %}" href="{% url Member.Statistics %}">Statistics</a>

et ensuite tout ce que j'ai à faire est à mon avis d'ajouter {'active_tab': 'statistics'}à mon dictionnaire contextuel.

Si vous utilisez, RequestContextvous pouvez obtenir le chemin actuel dans votre modèle sous la forme:

{{ request.path }}

Et à votre avis:

from django.template import RequestContext

def my_view(request):
    # do something awesome here
    return template.render(RequestContext(request, context_dict))
muhuk
la source
Merci de partager cette information. J'ai utilisé cette méthode, mais j'avais également une page plate dans ma barre de navigation, donc pour détecter cela et la mettre en évidence correctement, j'ai utilisé {% ifequal flatpage.url '/ about /'%}. Je n'aime pas la détection codée en dur de l'URL, mais cela fonctionne pour un piratage ponctuel.
Matt Garrison
Le problème avec cette solution est que vous avez des "statistiques" codées en dur dans le code. Cela va à l'encontre de l'objectif d'utiliser la balise url pour obtenir l'url de la page.
justin le
7

J'ai pris le code de nivhab ci-dessus et j'ai supprimé certaines bizarreries et en ai fait un templatetag propre, je l'ai modifié pour que / account / edit / rende toujours / account / tab actif.

#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 %}
Andreas
la source
6

Ceci est juste une variante de la solution css proposée par Toba ci-dessus:

Incluez les éléments suivants dans votre modèle de base:

<body id="section-{% block section %}home{% endblock %}">

Puis dans vos modèles qui étendent l'utilisation de base:

{% block section %}show{% endblock %}

Vous pouvez ensuite utiliser css pour mettre en évidence la zone actuelle en fonction de la balise body (par exemple si nous avons un lien avec un identifiant de nav-home):

#section-home a#nav-home{
 font-weight:bold;
}
dtt101
la source
3

Vous pouvez utiliser la fonction inverse avec les paramètres appropriés pour obtenir l'url actuelle.

Corey
la source
3

Merci pour vos réponses jusqu'à présent, messieurs. J'ai encore opté pour quelque chose de légèrement différent.

Dans mon modèle:

<li{{ link1_active }}>...link...</li>
<li{{ link2_active }}>...link...</li>
<li{{ link3_active }}>...link...</li>
<li{{ link4_active }}>...link...</li>

Une fois que j'ai déterminé sur quelle page je me trouve dans la logique (généralement dans urls.py), je passe class="selected"dans le contexte sous le bon nom au modèle.

Par exemple, si je suis sur la page link1, j'ajouterai {'link1_active':' class="selected"'}au contexte pour que le modèle récupère et injecte.

Cela semble fonctionner et c'est assez propre.

Edit: pour garder HTML hors de mon contrôleur / vue, j'ai un peu modifié ceci:

<li{% if link1_active %} class="selected"{% endif %}>...link...</li>
<li{% if link2_active %} class="selected"{% endif %}>...link...</li>
...

Cela rend le modèle un peu moins lisible, mais je suis d'accord, il vaut mieux ne pas pousser le HTML brut à partir du fichier urls.

Oli
la source
2
Vous devriez vraiment éviter de manipuler du HTML brut dans votre vue, ce qui est requis par cette technique. Avez-vous pensé à écrire une balise de modèle personnalisée?
Justin Voss
Vous avez raison. J'ai modifié pour arrêter de passer par le HTML. Je passe juste par True maintenant. Je n'ai pas encore écrit de balises de modèle, mais oui, cela pourrait être un bon point de départ.
Oli
2

J'ai plusieurs menus sur la même page qui sont créés dynamiquement via une boucle. Les messages ci-dessus relatifs au contexte m'ont donné une solution rapide. J'espère que cela aide quelqu'un. (J'utilise ceci en plus de la balise de modèle active - mon correctif résout le problème dynamique). Cela semble être une comparaison idiote, mais cela fonctionne. J'ai choisi de nommer les variables active_something-unique et something-unique, de cette façon cela fonctionne avec des menus imbriqués.

Voici une partie de la vue (assez pour comprendre ce que je fais):

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),

    }

Et cela provient du modèle:

<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>
Thomas Schreiber
la source
2

Ma solution était d'écrire un processeur de contexte simple pour définir une variable en fonction du chemin de la requête:

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}

(N'oubliez pas d'ajouter votre processeur personnalisé à TEMPLATE_CONTEXT_PROCESSORS dans settings.py.)

Ensuite, dans le modèle de base, j'utilise une balise ifequal par lien pour déterminer s'il faut ajouter la classe "active". Certes, cette approche est strictement limitée à la flexibilité de la structure de votre chemin, mais cela fonctionne pour mon déploiement relativement modeste.


la source
Je pense qu'il est vraiment logique de les avoir dans le contexte global, afin que vous puissiez référencer la section du site de différentes manières (en utilisant différents modèles pour différentes sections du site par exemple. +1.
shacker
2

Je voulais juste partager ma petite amélioration apportée à la publication de nivhab. Dans mon application, j'ai des sous-navigations et je ne voulais pas les cacher en utilisant uniquement du CSS, donc j'avais besoin d'une sorte de balise "if" pour afficher la sous-navigation d'un élément ou non.

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 ""

Vous pouvez l'utiliser fondamentalement de la même manière que la balise active:

{% url product_url as product %}

{% ifnaviactive request product %}
    <ul class="subnavi">
        <li>Subnavi item for product 1</li>
        ...
    </ul>
{% endifnaviactive %}
Nino
la source
2

Juste une autre évolution de la solution originale.

Cela accepte plusieurs modèles et ce qui est mieux également les modèles sans nom écrits sous forme d'URL relative encapsulée dans '"', comme suit:

{% 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 %}

La balise va comme ceci:

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 ""
Xaralis
la source
2

J'ai utilisé jquery pour mettre en évidence mes navbars. Cette solution ajoute simplement la classe css "active" à l'élément qui correspond au sélecteur 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>
matts1
la source
2

Une petite amélioration par rapport à la réponse de @tback , sans aucun %if%tag:

# 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 ''

Utilisez-le dans votre modèle comme ça:

{% load navigation %}
<li class="{{ request|activate_if_active:'url_name' }}">
  <a href="{% url 'url_name' %}">My View</a>
</li>

Et incluez "django.core.context_processors.request"dans votre TEMPLATE_CONTEXT_PROCESSORScadre.

MadeOfAir
la source
2

J'ai trouvé que le mieux est d'utiliser une balise d'inclusion:

templates/fnf/nav_item.html

<li class="nav-item">
    <a class="nav-link {% if is_active %}active{% endif %}" href="{% url url_name %}">{{ link_name }}</a>
</li>

C'est juste mon élément de base de navigation bootstrap que je souhaite rendre.

Il obtient la valeur href et éventuellement la valeur link_name. is_activeest calculé en fonction de la demande en cours.

templatetags/nav.py

from django import template

register = template.Library()


@register.inclusion_tag('fnf/nav_item.html', takes_context=True)
def nav_item(context, url_name, link_name=None):
    return {
        'url_name': url_name,
        'link_name': link_name or url_name.title(),
        'is_active': context.request.resolver_match.url_name == url_name,
    }

Ensuite, utilisez-le dans un nav: templates/fnf/nav.html

{% load nav %}
<nav class="navbar navbar-expand-lg navbar-light bg-light">
        <ul class="navbar-nav mr-auto">
                {% nav_item 'dashboard' %}
            </ul>
Tjorriemorrie
la source
Juste une lecture superficielle, mais cela ne limite-t-il pas les choses aux correspondances exactes sur l'URL? J'utilise couramment des conseils de navigation comme celui-ci pour les pages profondes. Par exemple, l'élément À propos de la navigation serait mis en surbrillance si vous étiez sur /about/company-history/ou /about/what-we-do/
Oli
1
Oui, mais is_activepeut être remplacé et d'autres clés ajoutées au dictionnaire renvoyées. En outre, le chèque peut être context.request.resolver_match.url_name.startswith(x)ou quoi que ce soit d'autre. En outre, vous pouvez avoir du code avant l'instruction return afin d'établir les valeurs dict. En outre, vous pouvez utiliser différents modèles, c'est-à-dire un pour top_level_nav.htmlavec une logique différente, etc.
Tjorriemorrie
Solution propre et simple ... sympa!
mmw
1

En modifiant légèrement la réponse d'Andreas, il semble que vous puissiez passer le nom de la route de urls.py à la balise de modèle. Dans mon exemple my_tasks, puis dans la fonction de balise de modèle, utilisez la fonction inverse pour déterminer ce que devrait être l'URL, puis vous pouvez la faire correspondre à l'URL dans l'objet de requête (disponible dans le contexte du modèle)

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' ),

template.html

<li class="{% active request all_tasks %}"><a href="{% url all_tasks %}">Everyone</a></li>
errkk
la source
Peut-être, une approche plus simple: turnkeylinux.org/blog/django-navbar
jgsogo
1

Je sais que je suis en retard à la fête. Cependant, je n'ai aimé aucune des solutions populaires:

La méthode de blocage semble erronée: je pense que la navigation devrait être autonome.

La méthode template_tag semble incorrecte: je n'aime pas le fait que je doive d'abord obtenir l'url de la balise url. De plus, je pense que la classe css devrait être définie dans le modèle, pas dans la balise.

J'ai donc écrit un filtre qui ne présente pas les inconvénients que j'ai décrits ci-dessus. Il retourne Truesi une url est active et peut donc être utilisée avec {% if %}:

{% load navigation %}
<li{% if request|active:"home" %} class="active"{% endif %}><a href="{% url "home" %}">Home</a></li>

Le code:

@register.filter(name="active")
def active(request, url_name):
    return resolve(request.path_info).url_name == url_name

Assurez-vous simplement de l'utiliser RequestContextsur les pages avec navigation ou d'activer la requête context_processor dans votresettings.py

TEMPLATE_CONTEXT_PROCESSORS = (
    ...
    'django.core.context_processors.request',
)
tback
la source
1

J'ai vu les réponses de jpwatts , 110j , nivhab et Marcus Whybrow , mais elles semblent toutes manquer de quelque chose: qu'en est-il du chemin racine? Pourquoi est-ce toujours actif?

J'ai donc fait un autre moyen, plus facile, qui fait que le "contrôleur" décide par lui-même et je pense que cela résout la plupart des gros problèmes.

Voici mon tag personnalisé:

## myapp_tags.py

@register.simple_tag
def nav_css_class(page_class):
    if not page_class:
        return ""
    else:
        return page_class

Ensuite, le "contrôleur" déclare les classes CSS nécessaires (en fait, le plus important est qu'il déclare sa présence au modèle)

## views.py

def ping(request):
    context={}
    context["nav_ping"] = "active"
    return render(request, 'myapp/ping.html',context)

Et enfin, je le rende dans ma barre de navigation:

<!-- 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>
...

Ainsi, chaque page a sa propre nav_css_classvaleur à définir, et si elle est définie, le modèle est rendu actif: pas besoin de requestcontexte de modèle, pas de parcage d'URL et plus de problèmes de pages multi-URL ou de page racine.

DestyNova
la source
1

Inspiré par cette solution , j'ai commencé à utiliser cette approche:

**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 %}
Evgeny Bobkin
la source
0

Voici mon essai. J'ai fini par implémenter une classe dans mes vues qui contient ma structure de navigation (plate avec quelques métadonnées). Je l'injecte ensuite dans le modèle et le rend.

Ma solution concerne i18n. Cela devrait probablement être un peu plus abstrait, mais cela ne me dérange pas vraiment.

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)

J'ai défini la logique du modèle en utilisant des inclusions comme celle-ci. Modèle de base:

{% include "includes/navigation.html" with items=navi %}

Inclure réel (includes / navigation.html):

 <ul class="nav">
     {% for item in items %}
         <li class="{{ item.status }}">
             <a href="{{ item.url }}">{{ item.name }}</a>
         </li>
     {% endfor %}
 </ul>

J'espère que quelqu'un trouvera cela utile! Je suppose qu'il serait assez facile d'étendre cette idée pour prendre en charge les hiérarchies imbriquées, etc.

Juho Vepsäläinen
la source
0

Créez un modèle d'inclusion "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>

Et incluez-le dans l'élément 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>

Et vous devez ajouter ceci aux paramètres:

from django.conf import global_settings
TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
    'django.core.context_processors.request',
)
Par
la source
0

voici une solution assez simple, https://github.com/hellysmile/django-activeurl

Віктор Ковтун
la source
1
Veuillez noter que vous devriez publier les points utiles d'une réponse ici, sur ce site, ou votre message risque d'être supprimé comme "Pas une réponse" . Vous pouvez toujours inclure le lien si vous le souhaitez, mais uniquement en tant que «référence». La réponse doit être autonome sans avoir besoin du lien.
Andrew Barber
0

de cette question SO

{% url 'some_urlpattern_name' as url %}
<a href="{{url}}"{% if request.path == url %} class="active"{% endif %}>Link</a>

Répétez au besoin pour chaque lien.

suhailvs
la source
Cela ne fonctionne que pour les correspondances directes. La plupart des systèmes de navigation marquent l'élément de navigation comme actif si une page descendante est également active. C'est-à-dire que si /blog/posts/2021/04/12c'était l'URL, l'élément / blog / nav serait actif.
Oli
@Oli oui cela ne fonctionnera pas parfois. par exemple dans la navigation par exemple stackoverflow Questions, Tags, Users, Badges, Unanswered, Ask Question. cela ne fonctionnera pas pour Questions, mais pour tous les autres navs, cela fonctionnera bien.
suhailvs
0

J'ai également utilisé jQuery pour le mettre en évidence et le trouver plus élégant que d'encombrer le modèle avec des balises de modèle Django non sémantiques.

Le code ci-dessous fonctionne avec les listes déroulantes imbriquées dans bootstrap 3 (met en évidence à la fois le parent et l' <li>élément enfant .

// 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');
    });
});

Il est également assez facile d'ajouter un clickévénement à return false(ou de modifier l' hrefattribut en #) pour la page actuelle, sans changer le balisage template / 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;
user193130
la source
0

J'utilise une combinaison de ce mixin pour les vues basées sur les classes:

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

avec ceci dans le modèle:

<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>
Brian Faherty
la source
0

La mienne est un peu similaire à une autre approche JS soumise précédemment .. juste sans jQuery ...

Disons que nous avons dans base.html ce qui suit:

<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>

Je viens de faire en sorte que ma hiérarchie suive un certain modèle d'URL ... après l'adresse de l'hôte ... j'ai ma catégorie principale, par exemple, maison, actualités, analyse, etc. et l'expression régulière tire juste le premier mot de l'emplacement

Logik Sounds
la source