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
« 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 _
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
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)
28register = template.Library()
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))
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()
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 }
63 return {
64 'bookmark': bookmark,
65 'form_url': form_url,
66 'form_data': form_data,
67 'return_url': instance.get_absolute_url(),
68 }
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 {}
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()
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 }
98 return {
99 'subscription': subscription,
100 'form_url': form_url,
101 'form_data': form_data,
102 'return_url': instance.get_absolute_url(),
103 }
106#
107# Legacy object buttons
108#
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 }
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 }
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})
133 return {
134 'url': url,
135 'label': _('Edit'),
136 }
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})
144 return {
145 'url': url,
146 'label': _('Delete'),
147 }
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})
155 return {
156 'label': _('Sync'),
157 'url': url,
158 }
161#
162# Legacy list buttons
163#
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
173 return {
174 'url': url,
175 'label': _('Add'),
176 'return_url': return_url,
177 }
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
188 return {
189 'url': url,
190 'label': _('Import'),
191 }
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
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'
203 # Retrieve all export templates for this model
204 export_templates = ExportTemplate.objects.restrict(user, 'view').filter(object_types=object_type)
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 }
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
226 return {
227 'label': _('Edit Selected'),
228 'url': url,
229 }
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
242 return {
243 'label': _('Delete Selected'),
244 'url': url,
245 }