DRY menu Custom Template Tag

  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. CheckboxMultiSelect with interable checkboxes by pyramids16 1 year, 5 months ago
  2. template tag for highlighting currently active page by adunar 5 years, 5 months ago
  3. better paginator template tag by amitu 5 years, 6 months ago
  4. FieldsetForm by Ciantic 7 years ago
  5. Tags & filters for rendering search results by exogen 6 years 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.

#

(Forgotten your password?)