Coverage for utilities/templatetags/buttons.py: 34%

97 statements  

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

1from django import template 

2from django.contrib.contenttypes.models import ContentType 

3from django.urls import NoReverseMatch, reverse 

4from django.utils.safestring import mark_safe 

5from django.utils.translation import gettext as _ 

6 

7from core.models import ObjectType 

8from extras.models import Bookmark, ExportTemplate, Subscription 

9from netbox.models.features import NotificationsMixin 

10from utilities.querydict import prepare_cloned_fields 

11from utilities.views import get_action_url 

12 

13__all__ = ( 

14 'action_buttons', 

15 'add_button', 

16 'bookmark_button', 

17 'bulk_delete_button', 

18 'bulk_edit_button', 

19 'clone_button', 

20 'delete_button', 

21 'edit_button', 

22 'export_button', 

23 'import_button', 

24 'subscribe_button', 

25 'sync_button', 

26) 

27 

28register = template.Library() 

29 

30 

31@register.simple_tag(takes_context=True) 

32def action_buttons(context, actions, obj, multi=False, **kwargs): 

33 buttons = [ 

34 action.render(context, obj, **kwargs) 

35 for action in actions if action.multi == multi 

36 ] 

37 return mark_safe(''.join(buttons)) 

38 

39 

40@register.inclusion_tag('buttons/bookmark.html', takes_context=True) 

41def bookmark_button(context, instance): 

42 # Check if this user has already bookmarked the object 

43 content_type = ContentType.objects.get_for_model(instance) 

44 bookmark = Bookmark.objects.filter( 

45 object_type=content_type, 

46 object_id=instance.pk, 

47 user=context['request'].user 

48 ).first() 

49 

50 # Compile form URL & data 

51 if bookmark: 

52 form_url = reverse('extras:bookmark_delete', kwargs={'pk': bookmark.pk}) 

53 form_data = { 

54 'confirm': 'true', 

55 } 

56 else: 

57 form_url = reverse('extras:bookmark_add') 

58 form_data = { 

59 'object_type': content_type.pk, 

60 'object_id': instance.pk, 

61 } 

62 

63 return { 

64 'bookmark': bookmark, 

65 'form_url': form_url, 

66 'form_data': form_data, 

67 'return_url': instance.get_absolute_url(), 

68 } 

69 

70 

71@register.inclusion_tag('buttons/subscribe.html', takes_context=True) 

72def subscribe_button(context, instance): 

73 # Skip for objects which don't support notifications 

74 if not (issubclass(instance.__class__, NotificationsMixin)): 

75 return {} 

76 

77 # Check if this user has already subscribed to the object 

78 content_type = ContentType.objects.get_for_model(instance) 

79 subscription = Subscription.objects.filter( 

80 object_type=content_type, 

81 object_id=instance.pk, 

82 user=context['request'].user 

83 ).first() 

84 

85 # Compile form URL & data 

86 if subscription: 

87 form_url = reverse('extras:subscription_delete', kwargs={'pk': subscription.pk}) 

88 form_data = { 

89 'confirm': 'true', 

90 } 

91 else: 

92 form_url = reverse('extras:subscription_add') 

93 form_data = { 

94 'object_type': content_type.pk, 

95 'object_id': instance.pk, 

96 } 

97 

98 return { 

99 'subscription': subscription, 

100 'form_url': form_url, 

101 'form_data': form_data, 

102 'return_url': instance.get_absolute_url(), 

103 } 

104 

105 

106# 

107# Legacy object buttons 

108# 

109 

110# TODO: Remove in NetBox v4.7 

111@register.inclusion_tag('buttons/clone.html') 

112def clone_button(instance): 

113 # Resolve URL path 

114 try: 

115 url = get_action_url(instance, action='add') 

116 except NoReverseMatch: 

117 return { 

118 'url': None, 

119 } 

120 

121 # Populate cloned field values and return full URL 

122 param_string = prepare_cloned_fields(instance).urlencode() 

123 return { 

124 'url': f'{url}?{param_string}' if param_string else None, 

125 } 

126 

127 

128# TODO: Remove in NetBox v4.7 

129@register.inclusion_tag('buttons/edit.html') 

130def edit_button(instance): 

131 url = get_action_url(instance, action='edit', kwargs={'pk': instance.pk}) 

132 

133 return { 

134 'url': url, 

135 'label': _('Edit'), 

136 } 

137 

138 

139# TODO: Remove in NetBox v4.7 

140@register.inclusion_tag('buttons/delete.html') 

141def delete_button(instance): 

142 url = get_action_url(instance, action='delete', kwargs={'pk': instance.pk}) 

143 

144 return { 

145 'url': url, 

146 'label': _('Delete'), 

147 } 

148 

149 

150# TODO: Remove in NetBox v4.7 

151@register.inclusion_tag('buttons/sync.html') 

152def sync_button(instance): 

153 url = get_action_url(instance, action='sync', kwargs={'pk': instance.pk}) 

154 

155 return { 

156 'label': _('Sync'), 

157 'url': url, 

158 } 

159 

160 

161# 

162# Legacy list buttons 

163# 

164 

165# TODO: Remove in NetBox v4.7 

166@register.inclusion_tag('buttons/add.html') 

167def add_button(model, action='add', return_url=None): 

168 try: 

169 url = get_action_url(model, action=action) 

170 except NoReverseMatch: 

171 url = None 

172 

173 return { 

174 'url': url, 

175 'label': _('Add'), 

176 'return_url': return_url, 

177 } 

178 

179 

180# TODO: Remove in NetBox v4.7 

181@register.inclusion_tag('buttons/import.html') 

182def import_button(model, action='bulk_import'): 

183 try: 

184 url = get_action_url(model, action=action) 

185 except NoReverseMatch: 

186 url = None 

187 

188 return { 

189 'url': url, 

190 'label': _('Import'), 

191 } 

192 

193 

194# TODO: Remove in NetBox v4.7 

195@register.inclusion_tag('buttons/export.html', takes_context=True) 

196def export_button(context, model): 

197 object_type = ObjectType.objects.get_for_model(model) 

198 user = context['request'].user 

199 

200 # Determine if the "all data" export returns CSV or YAML 

201 data_format = 'YAML' if hasattr(object_type.model_class(), 'to_yaml') else 'CSV' 

202 

203 # Retrieve all export templates for this model 

204 export_templates = ExportTemplate.objects.restrict(user, 'view').filter(object_types=object_type) 

205 

206 return { 

207 'label': _('Export'), 

208 'perms': context['perms'], 

209 'object_type': object_type, 

210 'url_params': context['request'].GET.urlencode() if context['request'].GET else '', 

211 'export_templates': export_templates, 

212 'data_format': data_format, 

213 } 

214 

215 

216# TODO: Remove in NetBox v4.7 

217@register.inclusion_tag('buttons/bulk_edit.html', takes_context=True) 

218def bulk_edit_button(context, model, action='bulk_edit', query_params=None): 

219 try: 

220 url = get_action_url(model, action=action) 

221 if query_params: 

222 url = f'{url}?{query_params.urlencode()}' 

223 except NoReverseMatch: 

224 url = None 

225 

226 return { 

227 'label': _('Edit Selected'), 

228 'url': url, 

229 } 

230 

231 

232# TODO: Remove in NetBox v4.7 

233@register.inclusion_tag('buttons/bulk_delete.html', takes_context=True) 

234def bulk_delete_button(context, model, action='bulk_delete', query_params=None): 

235 try: 

236 url = get_action_url(model, action=action) 

237 if query_params: 

238 url = f'{url}?{query_params.urlencode()}' 

239 except NoReverseMatch: 

240 url = None 

241 

242 return { 

243 'label': _('Delete Selected'), 

244 'url': url, 

245 }