How to build a dynamic menu in Django

  • By PyCat
  • Jan 12, 2017
  • Django Practices

A Django view is responsible for quering the data for a given template and returning the requested HTML. What about if we need to generate dynamic content at the base template of our website? This post demonstrates , how to use a custom template context processor to dynamically generate site navigation menu in the base Django template.

The goal is to buld a custom navigation menu by rendering a list of custom pages ans setting also "active" class to the selected menu. We will follow the Django MVC pattern using also a custom context processor for the pages application.

Database Model

Consider that we have in the database a list of custom pages. We also have in our base template our navigation menu with static links, such us contact, about etch. Our goal is to render this links dynamicaly. Remenber that each page of our site extends the base.html template

An examplary model of the application is as follows:

class Page(models.Model):
    STATUS_CHOICES = (
        ('draft', 'Draft'),
        ('published', 'Published'),
        )
    menu_title = models.CharField(max_length=200)
    page_title = models.CharField(max_length=200)
    slug = models.SlugField(max_length=200,unique_for_date='publish')
    body = RichTextField()
    show_in_menu = models.BooleanField(default=False)
    publish = models.DateTimeField( auto_now=True)
    status = models.CharField(max_length=10,choices=STATUS_CHOICES,default='draft')
    image = models.ImageField(upload_to='page', blank=True, null=True)

    def get_absolute_url(self):
        return reverse('page_detail', args=[self.slug])

 

I assume that model fields are know so I wont proceed to further explanation. I you have questions about model fields you could check on Django Model Field Reference

 

View

In the views.py of the page app we create a view witch will be responsible for quering the data o a specified page and returning the related HTML template. 

from .models import Page

def page_detail(request, slug):
    page = get_object_or_404(Page, slug=slug)
    return render(request, 'page/page_detail.html', {'page': page})

 

Custom template context processor

Our custom template context processor will pass instances of the Page class to the each template. In the base template we create links to the pages based on the slug field. The module for our custom template context processor will be placed in a file called  pages_context.py, as follows (location of the file is aribtrary):

 

from .models import Page

def show_pages_menu(context):
    pages_menu = Page.objects.filter(show_in_menu=True)
    return {'pages_menu': pages_menu}


Using the custom context template processor means that in each template we have access to the Page class though the pages variable. In the example above the function retrieves and returns all the Pages from the database. Of course you can short them as you prefer

The next step is register a template context processor. In the Django configuration module  settings.py you have to set full path to the function:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR,'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                           ...
                          'pages.pages_context.show_pages_menu',
           ],
        },
    },
]

You don’t override the existing settings. Append a path to the custom template context processor at the end of list of tuples.

Navigation menu

Now, in the base template e.g. we can use a list of Pages pages_menu for generate links to the specific pages. Sample navigation menu code might look like this:

Define the URL's

 The next step is to add rules related to the routing of the page links in the Django URL dispatcher located in the django urls file. In our case the urls should look like this:

urls.py

from django.conf.urls import include, url
from django.conf import settings
from django.contrib import admin

from . import views

urlpatterns =[
       url(r'^post/(?P[\w-]+)/$', views.post_detail, name='post_detail'),
       url(r'^$', views.post_list, name='post_list'),
       url(r'^tag/(?P[\w-]+)/$', views.post_list, name='post_list_by_tag'),
       url(r'^search/$', views.post_search, name='post_search'),
]
 

Load in template

And the final step is to load/render the specific menu data in your template. Let's say we have a menu.html to hold the menu markup. Our template coud look like this:

 

{# First render the pages #}
{% for page in pages_menu %}

{# Then check if current page is 'published' #}
{% if page.status == 'published' %}

<li>
  <a href="{{ page.get_absolute_url }}">{{page.menu_title}}</a>
</li>

{% endif %}
{% endfor %}

 

Summary

The custom template context processor is an easy way to transfer variables globally accesible from all the templates. Without the use of this infrastructure we would had have to pass in each view a list of pages as a template variable - witch means to repeat code - and generate a new menu individually witch is not a quite good practice. The above menu implemetation its just one example on how to make use of the custom template context processor. There are lots of "problems" that could be solved using this practice, such as rendering data for a sidebar section etc.

Enjoy your new custom navigation menu!

Comments

======= >>>>>>> master

+30 211 790 2526

Find me

kabardi.cat@gmail.com