I now have a very basic menu in my dummy application. Now I want to display it to the end user, which is what the 3rd part of the Django tutorial covers. The tutorial starts off with desiging the URLs that the end user will see. Well for the end user what I am creating is essentially a website. So my URLs will be something like:
http://www.example.com/node1/node1.1
etc. Except that node1 and node1.1 will be meaningful titles such as “About us” and “Staff”. These could be structured to contain the following sort of URL:
http://www.example.com/about-us/staff
The system should then bring back the menu expanded to the correct sub menu and with the correct menu item selected. Thus my URLs are quite straightforward in one sense and I will only need one regular expression pattern for now (essentially matching everything after the domain name). But I also need to figure out how to then split the path into it’s component sub-sections. Ideally I would like to end up with a urlpattern that results in a call which looks something like this:
view_menu(request=<HttpRequest object>, subsection1='about-us', subsection2='staff', subsection3=...)
And that there is no limit to the depth of the tree structure. The easier option is to just do this:
view_menu(request=<HttpRequest object>, path='about-us/staff/')
and then work out the subsection details in the view_menu function. I also think that if I store the path with the node in the database then this would be much more efficient (although much more complex to keep up to date). So going with the latter idea, my url pattern will be (essentially matching anything for now!):
(r'^(?P.*)/$', 'contentedweb.cwcms.views.menu')
Now I need to code up the corresponding view for this URL pattern. But in order to keep the code in my public facing views clean and nimble (as these are the ones that are going to be called most often) I need to make a change to the node model to include the path to the node:
class Node(models.Model):
title = models.CharField(max_length=100)
sort_order = models.IntegerField(default=1)
menu = models.ForeignKey('Menu')
parent = models.ForeignKey('self', blank=True, null=True)
is_visible = models.BooleanField(default=True)
path = models.CharField(max_length=1000)
def __unicode__(self):
return self.title
So I have added the path field to the model. For now I will make do with having to populate this field manually, but further down the line this field will be populated and maintained by the model not by the user creating the menu.
Next I need to bring back the relevant part of the menu from the database and display it in HTML indicating which menu item is selected. So I created a basic menu in the admin site and came up with this view (well essentially it’s copied from the tutorial…):
from django.shortcuts import render_to_response
from contentedweb.cwcms.models import Menu, Node
def menu(request, path):
main_menu = Menu.objects.get(id=1)
main_menu_items = Node.objects.filter(menu=1, parent=None)
return render_to_response('cwcms/menu.html', {'main_menu': main_menu,'main_menu_items': main_menu_items,'path': path})
and this template:
<html>
<body>
{% if main_menu %}
<h1>{{ main_menu.title }}</h1>
<ul>
{% for node in main_menu_items %}
<li{% ifequal node.path path %} class="selected"{% endifequal %}><a href="/{{ node.path }}">{{ node.title }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>Menu could not be found</p>
{% endif %}
</body>
</html>
Creating this, admittedly very basic, template was straight forward. There were only two problems I encountered. Firstly, I had tried to use the filter method to return the main menu info:
main_menu = Menu.objects.get(id=1)
But then the template didn’t work because main_menu was not actually a “Menu” object, but a list of “Menu” objects containing one element in the list. Secondly I was trying to use something like this in the templating system:
{% if node.path == path %}
Which threw an error because the templating system uses slightly different control flow statements. The correct form for the above code is:
{% ifequals node.path path %}
I am not sure why the templating system should use a different set of statements in this respect, but anyway, the documentation quickly came to the rescue.
Now I need to cope for the case that the path does not exist and raise a 404 error. So I modified the view to include a try-except construct:
try:
main_menu = Menu.objects.get(id=1)
main_menu_items = Node.objects.filter(menu=1, parent=None)
node = get_object_or_404(Node, path=url_path)
except Node.DoesNotExist:
raise Http404
Which basically checks that the path being looked for actually exists in the database. Adding in the 404 template is also straight forward. All you need to do is create a 404.html template in the root of the templates folder and Django will handle the rest.
Well I now have a very basic menu system in place. Obviously there is lots to do but right now I am concentrating on working myself through the tutorials. Once I have covered all 4 I will start expanding on my little app.