Giter Site home page Giter Site logo

westerveltco / django-simple-nav Goto Github PK

View Code? Open in Web Editor NEW
10.0 2.0 0.0 149 KB

A simple, flexible, and extensible navigation menu for Django

Home Page: https://django-simple-nav.westervelt.dev

License: MIT License

Just 4.56% Python 65.79% HTML 29.65%

django-simple-nav's Introduction

django-simple-nav

PyPI PyPI - Python Version Django Version

django-simple-nav is a Python/Django application designed to simplify the integration of navigation and menu bars in your Django projects. With a straightforward API and customizable options, you can easily add and manage navigational elements in your web applications. It is designed to be simple to start with, but flexible enough to handle complex navigation structures while maintaining that same simplicity.

Requirements

  • Python 3.8, 3.9, 3.10, 3.11, 3.12
  • Django 3.2, 4.2, 5.0

Getting Started

  1. Install the package from PyPI.

    python -m pip install django-simple-nav
  2. Add django_simple_nav to INSTALLED_APPS.

    After installation, add django_simple_nav to your INSTALLED_APPS in your Django settings:

    INSTALLED_APPS = [
        ...,
        "django_simple_nav",
        ...,
    ]

Usage

  1. Create a navigation definition.

    Define your navigation structure in a Python file. This file can be located anywhere in your Django project, provided it's importable. You can also split the navigations across multiple files if desired.

    A good starting point is to create a single nav.py or navigation.py file in your Django project's main configuration directory (where your settings.py file is located).

    django-simple-nav provides three classes to help you define your navigation structure:

    • Nav: The main container for a navigation structure. It has two required attributes:
      • template_name: The name of the template to render the navigation structure.
      • items: A list of NavItem or NavGroup objects that represent the navigation structure.
    • NavGroup: A container for a group of NavItem or NavGroup objects. It has two required and three optional attributes:
      • title: The title of the group.
      • items: A list of NavItem or NavGroupobjects that represent the structure of the group.
      • url (optional): The URL of the group. If not provided, the group will not be a link but just a container for the items.
      • permissions (optional): A list of permissions that control the visibility of the group. These permissions can be User attributes (e.g. is_authenticated, is_staff, is_superuser) or Django permissions (e.g. myapp.django_perm).
      • extra_context (optional): A dictionary of additional context to pass to the template when rendering the navigation.
    • NavItem: A single navigation item. It has two required and three optional attributes:
      • title: The title of the item.
      • url: The URL of the item. This can be a URL string (e.g. https://example.com/about/ or /about/) or a Django URL name (e.g. about-view).
      • permissions (optional): A list of permissions that control the visibility of the item. These permissions can be User attributes (e.g. is_authenticated, is_staff, is_superuser) or Django permissions (e.g. myapp.django_perm).
      • extra_context (optional): A dictionary of additional context to pass to the template when rendering the navigation.

    Here's an example configuration:

    # config/nav.py
    from django_simple_nav.nav import Nav
    from django_simple_nav.nav import NavGroup
    from django_simple_nav.nav import NavItem
    
    
    class MainNav(Nav):
        template_name = "main_nav.html"
        items = [
            NavItem(title="Relative URL", url="/relative-url"),
            NavItem(title="Absolute URL", url="https://example.com/absolute-url"),
            NavItem(title="Internal Django URL by Name", url="fake-view"),
            NavGroup(
                title="Group",
                url="/group",
                items=[
                    NavItem(title="Relative URL", url="/relative-url"),
                    NavItem(title="Absolute URL", url="https://example.com/absolute-url"),
                    NavItem(title="Internal Django URL by Name", url="fake-view"),
                ],
            ),
            NavGroup(
                title="Container Group",
                items=[
                    NavItem(title="Item", url="#"),
                ],
            ),
            NavItem(
                title="is_authenticated Item", url="#", permissions=["is_authenticated"]
            ),
            NavItem(title="is_staff Item", url="#", permissions=["is_staff"]),
            NavItem(title="is_superuser Item", url="#", permissions=["is_superuser"]),
            NavItem(
                title="myapp.django_perm Item", url="#", permissions=["myapp.django_perm"]
            ),
            NavGroup(
                title="Group with Extra Context",
                items=[
                    NavItem(
                        title="Item with Extra Context",
                        url="#",
                        extra_context={"foo": "bar"},
                    ),
                ],
                extra_context={"baz": "qux"},
            ),
        ]
  2. Create a template for the navigation.

    Create a template to render the navigation structure. This is just a standard Django template so you can use any Django template features you like.

    The template will be passed an items variable in the context representing the structure of the navigation, containing the NavItem and NavGroup objects defined in your navigation.

    Any items with permissions attached will automatically filtered out before rendering the template based on the request user's permissions, so you don't need to worry about that in your template.

    Items with extra context will have that context passed to the template when rendering the navigation, which you can access either directly or through the item.extra_context attribute.

    For example, given the above example MainNav, you could create a main_nav.html template:

    <!-- main_nav.html -->
    <ul>
      {% for item in items %}
        <li>
          <a href="{{ item.url }}"{% if item.active %} class="active"{% endif %}{% if item.baz %} data-baz="{{ item.baz }}"{% endif %}>
            {{ item.title }}
          </a>
          {% if item.items %}
            <ul>
              {% for subitem in item.items %}
                <li>
                  <a href="{{ subitem.url }}"{% if subitem.active %} class="active"{% endif %}{% if item.extra_context.foo %} data-foo="{{ item.extra_context.foo }}"{% endif %}>
                    {{ subitem.title }}
                  </a>
                </li>
              {% endfor %}
            </ul>
          {% endif %}
        </li>
      {% endfor %}
    </ul>
  3. Integrate navigation in templates.:

    Use the django_simple_nav template tag in your Django templates where you want to display the navigation.

    For example:

    <!-- base.html -->
    {% load django_simple_nav %}
    
    {% block navigation %}
    <nav>
      {% django_simple_nav "path.to.MainNav" %}
    </nav>
    {% endblock navigation %}

    The template tag can either take a string representing the import path to your navigation definition or an instance of your navigation class:

    # example_app/views.py
    from config.nav import MainNav
    
    
    def example_view(request):
        return render(request, "example_app/example_template.html", {"nav": MainNav()})
    <!-- example_app/example_template.html -->
    {% extends "base.html" %}
    {% load django_simple_nav %}
    
    {% block navigation %}
    <nav>
        {% django_simple_nav nav %}
    </nav>
    {% endblock navigation %}

    Additionally, the template tag can take a second argument to specify the template to use for rendering the navigation. This is useful if you want to use the same navigation structure in multiple places but render it differently.

    <!-- base.html -->
    {% load django_simple_nav %}
    
    <footer>
      {% django_simple_nav "path.to.MainNav" "footer_nav.html" %}
    </footer>

After configuring your navigation, you can use it across your Django project by calling the django_simple_nav template tag in your templates. This tag dynamically renders navigation based on your defined structure, ensuring a consistent and flexible navigation experience throughout your application.

Examples

The example directory contains a simple Django project that demonstrates how to use django-simple-nav. The example project includes a navigation definitions for a few different scenarios as well as some popular CSS frameworks.

You can run the example project by following these steps. These steps assume you have git and python installed on your system and are using a Unix-like shell. If you are using Windows, you may need to adjust the commands accordingly.

  1. Clone the repository.

    git clone https://github.com/westerveltco/django-simple-nav
    cd django-simple-nav
  2. Create a new virtual environment, activate it, and install django-simple-nav.

    python -m venv venv
    source venv/bin/activate
    python -m pip install .
  3. Run the example project.

    python example/demo.py
  4. Open your browser to http://localhost:8000 to see the examples in action.

Documentation

Please refer to the documentation for more information.

License

django-simple-nav is licensed under the MIT license. See the LICENSE file for more information.

django-simple-nav's People

Contributors

joshuadavidthomas avatar pre-commit-ci[bot] avatar jefftriplett avatar dependabot[bot] avatar

Stargazers

dmm avatar  avatar  avatar  avatar Ebrahim Hasan Alshaibani avatar  avatar Anton Alekseev avatar Louis L avatar Luis Norambuena avatar Tobi DEGNON avatar

Watchers

Dan Mann avatar  avatar

django-simple-nav's Issues

`get_context_data` fails if no user is attached to request

  File "/home/josh/projects/django-simple-nav/.nox/demo/lib/python3.12/site-packages/django_simple_nav/nav.py", line 30, in render
    context = self.get_context_data(request)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/josh/projects/django-simple-nav/.nox/demo/lib/python3.12/site-packages/django_simple_nav/nav.py", line 25, in get_context_data
    if check_item_permissions(item, request.user)  # type: ignore[arg-type]
                                    ^^^^^^^^^^^^
AttributeError: 'WSGIRequest' object has no attribute 'user'

Active item not matching for named urls

If you specify a NavGroup or NavItem with a url that is a named Django url, the active property is not applied correctly.

from django_simple_nav.nav import Nav
from django_simple_nav.nav import NavItem

class ClubsTabs(Nav):
    template_name = "navigation/tabs.html"
    items = [
        NavItem(title="Overview", url="clubs_dashboard"),
    ]

image

("Overview" should be underlined.)

Unable to nest `django_simple_nav` templatetags

Need to adjust either the templatetag or the render method of the Nav to pass in the request always.

  File "/home/josh/.pyenv/versions/nav-3.12/lib/python3.12/site-packages/django/template/base.py", line 961, in render_annotated
    return self.render(context)
           ^^^^^^^^^^^^^^^^^^^^
  File "/home/josh/projects/django-simple-nav/src/django_simple_nav/templatetags/django_simple_nav.py", line 48, in render
    return nav.render(context["request"], self.template_name)
                      ~~~~~~~^^^^^^^^^^^
  File "/home/josh/.pyenv/versions/nav-3.12/lib/python3.12/site-packages/django/template/context.py", line 83, in __getitem__
    raise KeyError(key)
KeyError: 'request'

Refactor `NavGroup` and `NavItem` to add a `render` method

When adding the type hints to #69 I realized that I don't want the get_items method to return a list of RenderedNavItem and have someone have to deal with the overhead of an additional extra class. Or add additional typing to return a list of RenderedNavItem or NavGroup | NavItem. That's just sloppy and unnecessary.

Instead, the NavGroup and NavItem should have a render method with any associated helper methods to go along with that. It would bring them more in line with the Nav as well and its render method.

I never really liked the solution I came up with regarding the rendering of the items, but the extra RenderedNavItem class seemed the most simple solution at the time. After sitting with it for a while, this way seems cleaner and somehow more simple. Not sure how I didn't see it the first go around.

  • Add render methods to both NavGroup and NavItem
    • Instead of an extra RenderedNavItem class, it could instead be a mixin that both of them inherit to cut down on duplication. Though the number of helper methods responsible for rendering is not that many, so that maybe overkill and just some small duplication would be better. ๐Ÿคทโ€โ™‚๏ธ Table that for now until PR review.
  • Remove RenderedNavItem
  • Change Nav.get_items method to call the render method for each item. Additionally, adjust the type hint to list[NavGroup | NavItem]

Simplify README

Since the docs have finally been setup, the README should be pared back to just a brief intro to the package. All the details currently there should be moved to the docs for further in depth reading.

Beef up the documentation

The README as it stands right now is a great start, but it'd be nice to actually document all the bits of the package.

Allow setting template content directly on `Nav`?

Curious about this, but as I create the demo with a bunch of examples, a thought occurred to me: should a nav's template be allowed to be set on the nav itself?

Example:

from django_simple_nav.nav import Nav
from django_simple_nav.nav import NavItem

class ExampleListNav(Nav):
    items = [
        NavItem(title="Tailwind CSS", url="/tailwind/"),
    ]
    template = """\
<ul>
  {% for item in items %}
    <li>
      <a href="{{ item.url }}"
         class="{% if item.active %}text-indigo-500 hover:text-indigo-300{% else %}hover:text-gray-400{% endif %}">
        {{ item.title }}
      </a>
    </li>
  {% endfor %}
</ul>"""

Add extra context to `NavGroup` and `NavItem`

One thing I missed when translating this from an internal library to external is I was setting stuff like icons and hx-boost attributes in the nav templates.

SidebarGroup(
    title="Admin",
    items=[
        SidebarItem(
            title="Admin",
            icon="cog-6-tooth",
            url="/admin/",
            is_staff=True,
            boost=False,
        ),
    ],
),

Maybe an extra-context hanging off the nav dataclasses for passing anything else that needs to be passed to the template..

Add `get_context_data` to `Nav`

The Nav should have a get_context_data method to allow for a little bit more flexibility users to override the context. Move the items rendering into that method.

Add README to `example` directory

For someone clicking around the GitHub UI trying to get a sense of the library, may be useful to have a README within the example directory that copies the installation instructions from the base README, along with some more details regarding what all features the examples are showing off.

We could then just reference the README within the documentation, and have it also be there as well.

Add a tutorial to docs

  • Start with the Django polls tutorial
  • Add a nav without package
    • polls list
    • add new poll (depending on is_staff perm)
    • Admin (depending on is_staff perm)
    • Log out/log in (depending on is_authenticated perm)
  • Refactor to use package
  • List alternatives -- include, custom inclusion template tag, context processor, other packages (django-simple-menu, others on django package's navigation grid)

Get `AttributeError: 'SafeString' object has no attribute 'resolve'` randomly

In clicking around on the demo in #42, this error sometimes pops up. It's random and inconsistent.

  File "/home/josh/projects/django-simple-nav/.nox/demo/lib/python3.12/site-packages/django/template/base.py", line 1000, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/josh/projects/django-simple-nav/.nox/demo/lib/python3.12/site-packages/django/template/base.py", line 961, in render_annotated
    return self.render(context)
           ^^^^^^^^^^^^^^^^^^^^
  File "/home/josh/projects/django-simple-nav/.nox/demo/lib/python3.12/site-packages/django_simple_nav/templatetags/django_simple_nav.py", line 32, in render
    self.nav = self.nav.resolve(context)
               ^^^^^^^^^^^^^^^^
AttributeError: 'SafeString' object has no attribute 'resolve'
2024-02-23 16:21:17,484 django.server ERROR    "GET /tailwind/ HTTP/1.1" 500 145

Create an example template

{% for item in items %}
  {% if item.items %}
    {% include 'tests/test_nav.html' with items=item.items %}
  {% endif %}
  <a href="{{ item.url }}">{{ item.title }}</a>
{% endfor %}

Add more CSS frameworks to examples

Within the example directory, there are a handful of examples using popular CSS frameworks.

At the moment, these include:

  • Tailwind CSS
  • Bootstrap v4 and v5
  • PicoCSS

The more examples the better! If there is a popular framework we are missing, we would love to have it included.

I would first comment here with the name of the framework, a link to either the repository or main website for the framework, and the example you would like to include.

The example should be open source, as we don't want to violate any license that may be attached.

For example, the Tailwind CSS example comes from one of the public templates offered in their Tailwind UI package, shown here. It gives a good example of how to use django-simple-nav within the context of the Tailwind UI templates, and if someone has a license to that product, they can then take it and apply it to one of the ones behind the paywall.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.