{"id":15559874,"url":"https://github.com/agateblue/django-navutils","last_synced_at":"2025-10-07T11:30:39.944Z","repository":{"id":32169648,"uuid":"35742900","full_name":"agateblue/django-navutils","owner":"agateblue","description":"A lightweight package for handling menus and breadcrumbs in your django project","archived":false,"fork":false,"pushed_at":"2022-05-06T07:19:37.000Z","size":105,"stargazers_count":40,"open_issues_count":7,"forks_count":12,"subscribers_count":3,"default_branch":"develop","last_synced_at":"2025-01-19T03:36:00.694Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"robinsloan/slinky","license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/agateblue.png","metadata":{"files":{"readme":"README.rst","changelog":"CHANGES.rst","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-05-16T22:40:05.000Z","updated_at":"2024-05-14T13:47:33.000Z","dependencies_parsed_at":"2022-09-04T02:20:26.197Z","dependency_job_id":null,"html_url":"https://github.com/agateblue/django-navutils","commit_stats":null,"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agateblue%2Fdjango-navutils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agateblue%2Fdjango-navutils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agateblue%2Fdjango-navutils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agateblue%2Fdjango-navutils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/agateblue","download_url":"https://codeload.github.com/agateblue/django-navutils/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235621552,"owners_count":19019519,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-02T15:59:02.117Z","updated_at":"2025-10-07T11:30:34.652Z","avatar_url":"https://github.com/agateblue.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"Django-navutils\n~~~~~~~~~~~~~~~\n\n**Note**: this package is still in beta. It has been successfully used in a few projects of my own. However, API may be subject to backward incompatible changes until the first major version is released.\n\nDjango-navutils is a lightweight package for handling menu and breadcrumbs inside your django project.\n\n.. contents::\n    :local:\n    :depth: 2\n\nFeatures\n========\n\n- No database involved (unless you want it): menus and breadcrumbs are plain old python code\n- Highly customizable\n- Conditionnal menu items display: you want to show a menu link to authenticated users only ? Anonymous ? Staff members ? A custom criteria ? You're covered !\n- i18n-friendly: you can rely on usual django translation mechanisms\n- Unlimited menus\n- Semi-automatic current menu node detection\n\nRequirements\n============\n\n- Python \u003e= 2.7 or \u003e= 3.3\n- Django \u003e= 1.7\n\nThe menu system may be integrated in any project, but the breadcrumbs part requires\nthat you use class-based views.\n\nInstall\n=======\n\nPackage is available on pip and can be installed via ``pip install django-navutils``.\n\nYou'll also have to add ``navutils`` to your ``settings.INSTALLED_APPS``\n\nAlso add the following to ``settings.CONTEXT_PROCESSORS``:\n\n.. code:: python\n\n    CONTEXT_PROCESSORS = (\n        # ...\n        'navutils.context_processors.menus',\n    )\n\nUsage\n=====\n\nMenus\n*****\n\nNavutils represents menus using ``Menu`` and ``Node`` instances, each menu being a collection of\nnode instances representing a menu link. Nodes may have children, which are also ``Node`` instances.\n\nLet's see a minimal example.\n\n``yourapp/menu.py``:\n\n.. code:: python\n\n    from navutils import menu\n\n    main_menu = menu.Menu('main')\n    menu.register(main_menu)\n\n    # will be shown to everybody\n    blog = menu.Node(id='blog', label='Blog', pattern_name='blog:index')\n    main_menu.register(blog)\n\n    # nodes created with a pattern_name argument will use django reverse\n    assert blog.get_url() == '/blog'\n\n    # if you want to disable reversion, use the url argument\n    django = menu.Node(id='django',\n                       label='Django project',\n                       url='http://djangoproject.com',\n                       link_attrs={'target': '_blank'})\n\n    # Each node instance can accept an arbitrary number of children\n    blog.add(\n        menu.Node(id='last_entries',\n                  label='Last entries',\n                  pattern_name='blog:last_entries')\n    )\n    blog.add(\n        menu.Node(id='archives', label='Archives', pattern_name='blog:archives')\n    )\n\n    # will be shown to anonymous users only\n    login = menu.AnonymousNode(id='login',\n                               label='Login',\n                               pattern_name='accounts_login',\n                               link_attrs={'class': 'big-button'})\n    main_menu.register(login)\n\n    # will be shown to authenticated users only\n    logout = menu.AuthenticatedNode(id='logout',\n                                    label='Logout',\n                                    pattern_name='accounts_logout')\n    main_menu.register(logout)\n\n\n``yourapp/templates/index.html``::\n\n    {% load navutils_tags %}\n    {% render_menu menu=menus.main user=request.user %}\n\nFor an anonymous user, this would output something like:\n\n.. code:: html\n\n    \u003cnav class=\"main-menu\"\u003e\n        \u003cul\u003e\n            \u003cli class=\"has-children menu-item\"\u003e\n                \u003ca href=\"/blog\"\u003eBlog\u003ca\u003e\n                \u003cul class=\"sub-menu\"\u003e\n                    \u003cli class=\"menu-item\"\u003e\n                        \u003ca href=\"/blog/latest\"\u003eLatest entries\u003c/a\u003e\n                    \u003c/li\u003e\n                    \u003cli class=\"menu-item\"\u003e\n                        \u003ca href=\"/blog/archives\"\u003eArchives\u003c/a\u003e\n                    \u003c/li\u003e\n                \u003c/ul\u003e\n            \u003c/li\u003e\n            \u003cli class=\"menu-item\"\u003e\n                \u003ca href=\"http://djangoproject.com\" target=\"_blank\"\u003eDjango project\u003c/a\u003e\n            \u003c/li\u003e\n            \u003cli class=\"menu-item\"\u003e\n                \u003ca href=\"/login\" class=\"big-button\"\u003eLogin\u003c/a\u003e\n            \u003c/li\u003e\n        \u003c/ul\u003e\n    \u003c/nav\u003e\n\n\nYou can also directly set children nodes on parent instanciation with the ``children`` argument:\n\n.. code:: python\n\n    user = menu.Node(\n        id='user',\n        label='Greetings',\n        pattern_name='user:dashboard',\n        children=[\n            menu.Node(id='logout', label='Logout', pattern_name='user:logout'),\n\n            # you can nest children indefinitely\n            menu.Node(\n                id='settings',\n                label='Settings',\n                pattern_name='user:settings',\n                children = [\n                    menu.Node(id='newsletter',\n                              label='Newsletter',\n                              pattern_name='user:settings:newsletter')\n                ],\n            ),\n        ]\n    )\n\nNodes can be customized in many ways:\n\n.. code:: python\n\n    heavily_customized_node = menu.Node(\n        'customized',\n        'My custom menu',  # Supports arbitrary template values as well\n                           # like {{ request.user }}\n        url='#',  # Supports arbitrary template values as well\n                  # like {{ request.user }}\n\n        # a custom CSS class that will be applied to the node on rendering\n        css_class='custom-class',\n\n        # the \u003ca\u003e title attribute\n        title='click me!',\n\n        # a path to a custom template for rendering the node\n        # it's also possible to globally specify a custom template by naming\n        # your template '\u003cyourapp\u003e/templates/navutils/node.html'\n        template='myapp/menu/mynode.html',\n\n        # extra context you can use in your node template\n        context={'foo': 'bar'},\n\n        # a dict of attributes that will be applied as HTML attributes on the \u003cli\u003e\n        attrs = {'style': 'background-color: white;'}\n\n        # a dict of attributes that will be applied as HTML attributes on the \u003ca\u003e\n        link_attrs = {'target': '_blank', 'data-something': 'fancy-stuff'}\n    )\n\nCurrent node\n------------\n\nYou'll probably want to highlight the current node in some way. Navutils provide\na view mixin you an inherit from in order to achieve this.\n\nAssuming the following menu:\n\n.. code:: python\n\n    from navutils import menu\n\n    main_menu = menu.Menu(id='main')\n    menu.register(main_menu)\n\n    login = menu.Node(id='login', label='Login', pattern_name='account_login')\n    main_menu.register(login)\n\n\nYou can bind a view to a menu node with the following code:\n\n.. code:: python\n\n    from navutils import MenuMixin\n\n    class Login(MenuMixin, TemplateView):\n        current_menu_item = 'login'\n\n\nUnder the hood, the mixin will pass the value to the context and a `current` class will be added\n to the login node if the view is displayed. Note that you can achieve the same result\n with django function-based views, as long as you manually pass the node identifier in the context,\n under the `current_menu_item` key.\n\nNode reference\n--------------\n\nNavutils provide a few node subclasses that address common use cases.\n\nNode\n++++\n\nThe base Node type, will be displayed to anybody.\n\nAnonymousNode\n+++++++++++++\n\nDisplayed to anonymous users only.\n\nAuthenticatedNode\n+++++++++++++++++\n\nDisplayd to authenticated users only.\n\nStaffNode\n+++++++++\n\nDisplayed to staff users/superusers only.\n\nPermissionNode\n++++++++++++++\n\nDisplayed to users that have the given permission. Usage:\n\n.. code:: python\n\n    vip_node = menu.PermissionNode('vip',\n                                   label='VIP Area',\n                                   pattern_name='vip:index',\n                                   permission='access_vip_area')\n\nAllPermissionsNode\n++++++++++++++++++\n\nDisplayed to users that match a list of permission. Usage:\n\n.. code:: python\n\n    permissions = ['myapp.access_vip_area', 'myapp.drink_champagne']\n    champagne_node = menu.AllPermissionsNode('champagne',\n                                             label='Champagne!',\n                                             pattern_name='vip:champagne',\n                                             permissions=permissions)\n\nAnyPermissionsNode\n++++++++++++++++++\n\nDisplayed to users that match any given permission. Usage:\n\n.. code:: python\n\n    permissions = ['myapp.can_party', 'myapp.can_have_fun']\n    have_a_good_time = menu.AnyPermissionsNode('good-time',\n                                               label='Have a good time',\n                                               pattern_name='good_time',\n                                               permissions=permissions)\n\n\nPassTestNode\n++++++++++++\n\nDisplayed to users that match a custom test. Usage:\n\n.. code:: python\n\n    def can_drink_alcohol(user, context):\n        return user.age \u003e= 21 or user.looks_mature_for_his_age\n\n    drink_alcohol = menu.PassTestNode('drink',\n                                      label='Have a beer',\n                                      pattern_name='beer',\n                                      test=can_drink_alcohol)\n\nIf it's not enough, you can also override the default templates:\n\n- ``navutils/menu.html`` : the menu wrapper that loop through the nodes\n- ``navutils/node.html`` : called for displaying each node instance\n\nAnd of course, you're free to create your own sub-classes.\n\nBreadcrumbs\n***********\n\nBreadcrumbs are set up into views, and therefore can only be used with class-based views.\n\nFirst of all, you'll probably want to define a base mixin for all your views:\n\n.. code:: python\n\n    from navutils import BreadcrumbsMixin, Breadcrumb\n\n    class BaseMixin(BreadcrumbsMixin):\n        def get_breadcrumbs(self):\n            breadcrumbs = super(BaseMixin, self).get_breadcrumbs()\n            breadcrumbs.append(Breadcrumb('Home', url='/'))\n            return breadcrumbs\n\nThen, you can inherit from this view everywhere:\n\n.. code:: python\n\n    # breadcrumbs = Home \u003e Blog\n    class BlogView(BaseMixin):\n        title = 'Blog'\n\n\n    # breadcrumbs = Home \u003e Logout\n    class LogoutView(BaseMixin):\n        title = 'Logout'\n\n\nBy default, the last element of the breadcrumb is deduced from the ``title`` attribute of the view.\nHowever, for a complex hierarchy, you are free to override the ``get_breadcrumbs`` method:\n\n.. code:: python\n\n    # you can trigger url reversing via pattern_name, as for menu nodes\n    class BlogMixin(BaseMixin)\n        def get_breadcrumbs(self):\n            breadcrumbs = super(BlogMixin, self).get_breadcrumbs()\n            breadcrumbs.append(Breadcrumb('Blog', pattern_name='blog:index'))\n            return breadcrumbs\n\n\n    # breadcrumbs = Home \u003e Blog \u003e Last entries\n    class BlogIndex(BlogMixin):\n        title = 'Last entries'\n\n\n    # for dynamic titles, just override the get_title method\n    # breadcrumbs = Home \u003e Blog \u003e My category name\n    class CategoryDetail(BlogMixin, DetailView):\n\n        model = Category\n\n        def get_title(self):\n            # assuming your Category model has a title field\n            return self.object.title\n\n\nThe last step is to render the breadcrumbs in your template. The provided mixin takes\ncare with passing data in the context, so all you need is::\n\n    {% load navutils_tags %}\n\n    {% render_breadcrumbs breadcrumbs %}\n\nThe breadcrumbs part of navutils is bundled with two templates, feel free to override them:\n\n- ``navutils/breadcrumbs.html``: the breadcrumbs wrapper\n- ``navutils/crumb.html``: used to render each crumb\n\nThat's it !\n\nChangelog\n=========\n\nSee `CHANGES.rst\n\u003cCHANGES.rst\u003e`_.\n\nLicense\n=======\n\nProject is licensed under BSD license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagateblue%2Fdjango-navutils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fagateblue%2Fdjango-navutils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagateblue%2Fdjango-navutils/lists"}