Skip to content

Commit 44e5a63

Browse files
Merge pull request #3755 from netbox-community/3664-configcontext-tags
3664 configcontext tags
2 parents 05938d6 + 19363ad commit 44e5a63

File tree

13 files changed

+97
-9
lines changed

13 files changed

+97
-9
lines changed

netbox/extras/api/serializers.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,18 @@ class ConfigContextSerializer(ValidatedModelSerializer):
173173
required=False,
174174
many=True
175175
)
176+
tags = serializers.SlugRelatedField(
177+
queryset=Tag.objects.all(),
178+
slug_field='slug',
179+
required=False,
180+
many=True
181+
)
176182

177183
class Meta:
178184
model = ConfigContext
179185
fields = [
180186
'id', 'name', 'weight', 'description', 'is_active', 'regions', 'sites', 'roles', 'platforms',
181-
'tenant_groups', 'tenants', 'data',
187+
'tenant_groups', 'tenants', 'tags', 'data',
182188
]
183189

184190

netbox/extras/filters.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from dcim.models import DeviceRole, Platform, Region, Site
66
from tenancy.models import Tenant, TenantGroup
77
from .choices import *
8-
from .constants import *
98
from .models import ConfigContext, CustomField, Graph, ExportTemplate, ObjectChange, Tag
109

1110

@@ -180,6 +179,12 @@ class ConfigContextFilter(django_filters.FilterSet):
180179
to_field_name='slug',
181180
label='Tenant (slug)',
182181
)
182+
tag = django_filters.ModelMultipleChoiceFilter(
183+
field_name='tags__slug',
184+
queryset=Tag.objects.all(),
185+
to_field_name='slug',
186+
label='Tag (slug)',
187+
)
183188

184189
class Meta:
185190
model = ConfigContext

netbox/extras/forms.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
BOOLEAN_WITH_BLANK_CHOICES,
1515
)
1616
from .choices import *
17-
from .constants import *
1817
from .models import ConfigContext, CustomField, CustomFieldValue, ImageAttachment, ObjectChange, Tag
1918

2019

@@ -238,6 +237,14 @@ class Meta:
238237
#
239238

240239
class ConfigContextForm(BootstrapMixin, forms.ModelForm):
240+
tags = forms.ModelMultipleChoiceField(
241+
queryset=Tag.objects.all(),
242+
to_field_name='slug',
243+
required=False,
244+
widget=APISelectMultiple(
245+
api_url="/api/extras/tags/"
246+
)
247+
)
241248
data = JSONField(
242249
label=''
243250
)
@@ -246,7 +253,7 @@ class Meta:
246253
model = ConfigContext
247254
fields = [
248255
'name', 'weight', 'description', 'is_active', 'regions', 'sites', 'roles', 'platforms', 'tenant_groups',
249-
'tenants', 'data',
256+
'tenants', 'tags', 'data',
250257
]
251258
widgets = {
252259
'regions': APISelectMultiple(
@@ -266,7 +273,7 @@ class Meta:
266273
),
267274
'tenants': APISelectMultiple(
268275
api_url="/api/tenancy/tenants/"
269-
)
276+
),
270277
}
271278

272279

@@ -347,6 +354,14 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
347354
value_field="slug",
348355
)
349356
)
357+
tag = FilterChoiceField(
358+
queryset=Tag.objects.all(),
359+
to_field_name='slug',
360+
widget=APISelectMultiple(
361+
api_url="/api/extras/tags/",
362+
value_field="slug",
363+
)
364+
)
350365

351366

352367
#

netbox/extras/middleware.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from django.utils import timezone
1010
from django_prometheus.models import model_deletes, model_inserts, model_updates
1111

12+
from extras.utils import is_taggable
1213
from utilities.querysets import DummyQuerySet
1314
from .choices import ObjectChangeActionChoices
1415
from .models import ObjectChange
@@ -41,7 +42,7 @@ def handle_deleted_object(sender, instance, **kwargs):
4142
copy = deepcopy(instance)
4243

4344
# Preserve tags
44-
if hasattr(instance, 'tags'):
45+
if is_taggable(instance):
4546
copy.tags = DummyQuerySet(instance.tags.all())
4647

4748
# Queue the copy of the object for processing once the request completes
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 2.2.6 on 2019-12-11 09:17
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('extras', '0033_graph_type_to_fk'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='configcontext',
15+
name='tags',
16+
field=models.ManyToManyField(blank=True, related_name='_configcontext_tags_+', to='extras.Tag'),
17+
),
18+
]

netbox/extras/models.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,11 @@ class ConfigContext(models.Model):
682682
related_name='+',
683683
blank=True
684684
)
685+
tags = models.ManyToManyField(
686+
to='extras.Tag',
687+
related_name='+',
688+
blank=True
689+
)
685690
data = JSONField()
686691

687692
objects = ConfigContextQuerySet.as_manager()

netbox/extras/querysets.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,6 @@ def get_for_object(self, obj):
4646
Q(platforms=obj.platform) | Q(platforms=None),
4747
Q(tenant_groups=tenant_group) | Q(tenant_groups=None),
4848
Q(tenants=obj.tenant) | Q(tenants=None),
49+
Q(tags__slug__in=obj.tags.slugs()) | Q(tags=None),
4950
is_active=True,
5051
).order_by('weight', 'name')

netbox/extras/tests/test_api.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,8 @@ def test_create_configcontext(self):
370370
tenantgroup2 = TenantGroup.objects.create(name='Test Tenant Group 2', slug='test-tenant-group-2')
371371
tenant1 = Tenant.objects.create(name='Test Tenant 1', slug='test-tenant-1')
372372
tenant2 = Tenant.objects.create(name='Test Tenant 2', slug='test-tenant-2')
373+
tag1 = Tag.objects.create(name='Test Tag 1', slug='test-tag-1')
374+
tag2 = Tag.objects.create(name='Test Tag 2', slug='test-tag-2')
373375

374376
data = {
375377
'name': 'Test Config Context 4',
@@ -380,6 +382,7 @@ def test_create_configcontext(self):
380382
'platforms': [platform1.pk, platform2.pk],
381383
'tenant_groups': [tenantgroup1.pk, tenantgroup2.pk],
382384
'tenants': [tenant1.pk, tenant2.pk],
385+
'tags': [tag1.slug, tag2.slug],
383386
'data': {'foo': 'XXX'}
384387
}
385388

@@ -402,6 +405,8 @@ def test_create_configcontext(self):
402405
self.assertEqual(tenantgroup2.pk, data['tenant_groups'][1])
403406
self.assertEqual(tenant1.pk, data['tenants'][0])
404407
self.assertEqual(tenant2.pk, data['tenants'][1])
408+
self.assertEqual(tag1.slug, data['tags'][0])
409+
self.assertEqual(tag2.slug, data['tags'][1])
405410
self.assertEqual(configcontext4.data, data['data'])
406411

407412
def test_create_configcontext_bulk(self):

netbox/extras/utils.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from taggit.managers import _TaggableManager
2+
from utilities.querysets import DummyQuerySet
3+
4+
5+
def is_taggable(obj):
6+
"""
7+
Return True if the instance can have Tags assigned to it; False otherwise.
8+
"""
9+
if hasattr(obj, 'tags'):
10+
if issubclass(obj.tags.__class__, _TaggableManager):
11+
return True
12+
# TaggableManager has been replaced with a DummyQuerySet prior to object deletion
13+
if isinstance(obj.tags, DummyQuerySet):
14+
return True
15+
return False

netbox/templates/extras/configcontext.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,20 @@ <h1>{% block title %}{{ configcontext }}{% endblock %}</h1>
162162
{% endif %}
163163
</td>
164164
</tr>
165+
<tr>
166+
<td>Tags</td>
167+
<td>
168+
{% if configcontext.tags.all %}
169+
<ul>
170+
{% for tag in configcontext.tags.all %}
171+
<li><a href="{{ tag.get_absolute_url }}">{{ tag }}</a></li>
172+
{% endfor %}
173+
</ul>
174+
{% else %}
175+
<span class="text-muted">None</span>
176+
{% endif %}
177+
</td>
178+
</tr>
165179
</table>
166180
</div>
167181
</div>

netbox/templates/extras/configcontext_edit.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
{% render_field form.platforms %}
2121
{% render_field form.tenant_groups %}
2222
{% render_field form.tenants %}
23+
{% render_field form.tags %}
2324
</div>
2425
</div>
2526
<div class="panel panel-default">

netbox/utilities/utils.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from django.db.models import Count, OuterRef, Subquery
77

88
from dcim.choices import CableLengthUnitChoices
9+
from extras.utils import is_taggable
910

1011

1112
def csv_format(data):
@@ -103,7 +104,7 @@ def serialize_object(obj, extra=None):
103104
}
104105

105106
# Include any tags
106-
if hasattr(obj, 'tags'):
107+
if is_taggable(obj):
107108
data['tags'] = [tag.name for tag in obj.tags.all()]
108109

109110
# Append any extra data
@@ -201,7 +202,7 @@ def prepare_cloned_fields(instance):
201202
params[field_name] = field_value
202203

203204
# Copy tags
204-
if hasattr(instance, 'tags'):
205+
if is_taggable(instance):
205206
params['tags'] = ','.join([t.name for t in instance.tags.all()])
206207

207208
# Concatenate parameters into a URL query string

netbox/utilities/views.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
from extras.models import CustomField, CustomFieldValue, ExportTemplate
2626
from extras.querysets import CustomFieldQueryset
27+
from extras.utils import is_taggable
2728
from utilities.exceptions import AbortTransaction
2829
from utilities.forms import BootstrapMixin, CSVDataField
2930
from utilities.utils import csv_format, prepare_cloned_fields
@@ -144,7 +145,7 @@ def get(self, request):
144145
table.columns.show('pk')
145146

146147
# Construct queryset for tags list
147-
if hasattr(model, 'tags'):
148+
if is_taggable(model):
148149
tags = model.tags.annotate(count=Count('extras_taggeditem_items')).order_by('name')
149150
else:
150151
tags = None

0 commit comments

Comments
 (0)