Login

DRY menu Custom Template Tag

Author:
sergzach
Posted:
March 22, 2012
Language:
Python
Version:
1.1
Score:
0 (after 0 ratings)

This is the description of a custom template tag to create DRY menu. It solves the problem of markup duplication in templates of your site. The menu always has one active option and one or several inactive options.

HOW TO USE

Define a structure of your menu in a parent template:

{% defmenu "menu1" %}

{% active %}<span class='active'>__text__</span>{% endactive %}
{% inactive %}<a href='__url__'>__text__</a>{% endinactive %}

{% opt "opt1" "/opt1/" %}Go to opt1{% endopt %}
{% opt "opt2" "/opt2/" %}Go to opt2{% endopt %}
{% opt "opt3" "/opt3/" %}Go to opt3{% endopt %}

{% enddefmenu %}

The menu has it's name (first parameter of the tag 'defmenu'.

First parameter of a tag 'opt' is menu option's name. 'text' inside of 'active'/'inactive' will be substituted by inner text of a tag 'opt' (Go to opt...), 'url' indide of 'active'/'inactive' will be substituted by second parameter of a tag 'opt'

To generate menu with one selected option in child template do:

{% menu "menu1" "opt1" %}

Here: "menu1" is a name of menu that was defined by 'defmenu' tag, "opt1" is selected option.

Result of the applying 'menu' is the next:

<span class='active'> Go to opt1</span> <a href='"/opt2/"'>Go to opt2</a> <a href='"/opt3/"'>Go to opt3</a>
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
from django import template
from django.template.base import TextNode, VariableNode, TemplateSyntaxError

from copy import copy

register = template.Library()

@register.tag( name = "menu" )
def do_menu( parser, token ):	
	tag_name, menu_name, opt_name = token.split_contents()
	return MenuNode( menu_name, opt_name )

class MenuNode( template.Node ):
	def __init__( self, menu_name, opt_name ):
		self.menu_name = menu_name
		self.opt_name = opt_name

	def _replaceUrlText( self, opt, active ):

		active_ = copy( active )

		for i in range( 0, len( active ) ):
			node = active_[ i ]

			if isinstance( node, VariableNode ):
				if node.filter_expression.token == u'text':
					active_[ i ] = TextNode( opt[ 1 ][ 0 ].s )
				elif node.filter_expression.token == u'url':
					active_[ i ] = TextNode( opt[ 0 ] )
		return active_
		

	def render( self, context ):
		menu = context[ '_menus' ][ self.menu_name ]

		active = menu[ '_defmenu_active' ]
		inactive = menu[ '_defmenu_inactive' ]
		opts = menu[ '_defmenu_opts' ]
		opt = opts[ self.opt_name ]

		result_list = []

		
		for opt_name in opts:
			opt = opts[ opt_name ]

			if self.opt_name == opt_name:								
				result_list.append( ( self._replaceUrlText( opt, active ) ).render( context ) )
			else:
				result_list.append( ( self._replaceUrlText( opt, inactive ) ).render( context ) )

		result_list.reverse()

		return " ".join( result_list )			

@register.tag( name = 'defmenu' )
def do_defmenu( parser, token ):
	nodelist = parser.parse( ( 'enddefmenu', ) )
	parser.delete_first_token()

	tag_name, menu_name = token.split_contents()

	return DefMenuNode( nodelist, menu_name )

class DefMenuNode( template.Node ):
	def __init__( self, nodelist, menu_name ):
		self.nodelist = nodelist
		self.menu_name = menu_name

	def render( self, context ):
		output = self.nodelist.render( context ) # applye children here

		if '_menus' not in context:
			context[ '_menus' ] = {}

		menu_name = self.menu_name
		context[ '_menus' ][ menu_name ] = {}
		menu = context[ '_menus' ][ menu_name ]

		try:
			menu[ '_defmenu_active' ] = context[ '_defmenu_active' ]
		except KeyError:
			raise TemplateSyntaxError( "An {% active %} block is not found inside the {% defmenu %} block." )
		try:
			menu[ '_defmenu_inactive' ] = context[ '_defmenu_inactive' ]
		except KeyError:
			raise TemplateSyntaxError( "An {% inactive %} block is not found inside the {% defmenu %} block." )
		try:
			menu[ '_defmenu_opts' ] = context[ '_defmenu_opts' ]
		except KeyError:
			raise TemplateSyntaxError( "The {% defmenu %} block must have at least one {% opt %} block inside." )

		del context[ '_defmenu_active' ]
		del context[ '_defmenu_inactive' ]
		del context[ '_defmenu_opts' ]

		return ""

@register.tag( name = 'active' )
def do_active( parser, token ):
	nodelist = parser.parse( ( 'endactive', ) )
	parser.delete_first_token()
	return ActiveNode( nodelist )

class ActiveNode( template.Node ):
	def __init__( self, nodelist ):
		self.nodelist = nodelist

	def render( self, context ):
		context[ '_defmenu_active' ] = self.nodelist

@register.tag( name = 'inactive' )
def do_inactive( parser, token ):
	nodelist = parser.parse( ( 'endinactive', ) )
	parser.delete_first_token()
	return InactiveNode( nodelist )

class InactiveNode( template.Node ):
	def __init__( self, nodelist ):
		self.nodelist = nodelist

	def render( self, context ):
		context[ '_defmenu_inactive' ] = self.nodelist


@register.tag( name = 'opt' )
def do_opt( parser, token ):
	nodelist = parser.parse( ( 'endopt', ) )
	parser.delete_first_token()

	tag_name, name, url = token.split_contents()

	return OptNode( nodelist, name, url )

class OptNode( template.Node ):
	def __init__( self, nodelist, name, url ):
		self.nodelist = nodelist
		self.name = name
		self.url = url

	def render( self, context ):
		if '_defmenu_opts' not in context:
			context['_defmenu_opts' ] = {}
		context[ '_defmenu_opts' ].update( { self.name: ( self.url, self.nodelist ) } )

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 2 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 2 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 9 months, 2 weeks ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 10 months, 1 week ago
  5. Help text hyperlinks by sa2812 11 months ago

Comments

sergzach (on March 26, 2012):

Thank you! When I created a snippet #2722 I did not take into account the option with of inclusion tag.

I think your solution is better.

My decision have only one plus (your have several ones). In case of #2722 inheritance is possible. Sometimes it is more convenient and conceptual than inclusion.

And one more plus in #2722. In your case if you want to mek whole fragment different (not only class attribute) you should write several times very similar code for each option. But I would like DRY menu.

#

Please login first before commenting.