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
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 2 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 6 months ago
Comments
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.