Coverage for extras/templatetags/custom_links.py: 21%

43 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-10 18:35 +0000

1from django import template 

2from django.utils.html import escape 

3from django.utils.safestring import mark_safe 

4 

5from core.models import ObjectType 

6from extras.models import CustomLink 

7from netbox.choices import ButtonColorChoices 

8from utilities.request import get_safe_request_context 

9 

10register = template.Library() 

11 

12LINK_BUTTON = '<a href="{}"{} class="btn btn-sm btn-{}">{}</a>\n' 

13 

14GROUP_BUTTON = """ 

15<div class="dropdown"> 

16 <button 

17 class="btn btn-sm btn-{} dropdown-toggle" 

18 type="button" 

19 data-bs-toggle="dropdown" 

20 aria-expanded="false"> 

21 {} 

22 </button> 

23 <ul class="dropdown-menu dropdown-menu-end"> 

24 {} 

25 </ul> 

26</div> 

27""" 

28 

29GROUP_LINK = '<li><a class="dropdown-item" href="{}"{}>{}</a></li>\n' 

30 

31 

32@register.simple_tag(takes_context=True) 

33def custom_links(context, obj): 

34 """ 

35 Render all applicable links for the given object. 

36 """ 

37 user = context['request'].user 

38 object_type = ObjectType.objects.get_for_model(obj) 

39 custom_links = CustomLink.objects.restrict(user, 'view').filter(object_types=object_type, enabled=True) 

40 if not custom_links: 

41 return '' 

42 

43 # Pass select context data when rendering the CustomLink 

44 link_context = { 

45 'object': obj, 

46 'debug': context.get('debug', False), # django.template.context_processors.debug 

47 'request': get_safe_request_context(context['request']), # Sanitized subset of the request 

48 'user': context['user'], # django.contrib.auth.context_processors.auth 

49 'perms': context['perms'], # django.contrib.auth.context_processors.auth 

50 } 

51 template_code = '' 

52 group_names = {} 

53 

54 for cl in custom_links: 

55 

56 # Organize custom links by group 

57 if cl.group_name and cl.group_name in group_names: 

58 group_names[cl.group_name].append(cl) 

59 elif cl.group_name: 

60 group_names[cl.group_name] = [cl] 

61 

62 # Add non-grouped links 

63 else: 

64 button_class = 'outline-secondary' if cl.button_class == ButtonColorChoices.DEFAULT else cl.button_class 

65 try: 

66 if rendered := cl.render(link_context): 

67 template_code += LINK_BUTTON.format( 

68 rendered['link'], rendered['link_target'], button_class, rendered['text'] 

69 ) 

70 except Exception as e: 

71 template_code += f'<a class="btn btn-sm btn-outline-secondary" disabled="disabled" ' \ 

72 f'title="{escape(e)}"><i class="mdi mdi-alert"></i> {escape(cl.name)}</a>\n' 

73 

74 # Add grouped links to template 

75 for group, links in group_names.items(): 

76 

77 links_rendered = [] 

78 

79 for cl in links: 

80 try: 

81 if rendered := cl.render(link_context): 

82 links_rendered.append( 

83 GROUP_LINK.format(rendered['link'], rendered['link_target'], rendered['text']) 

84 ) 

85 except Exception as e: 

86 links_rendered.append( 

87 f'<li><a class="dropdown-item" disabled="disabled" title="{escape(e)}"><span class="text-muted">' 

88 f'<i class="mdi mdi-alert"></i> {escape(cl.name)}</span></a></li>' 

89 ) 

90 

91 if links_rendered: 

92 template_code += GROUP_BUTTON.format( 

93 links[0].button_class, escape(group), ''.join(links_rendered) 

94 ) 

95 

96 return mark_safe(template_code)