Просмотр исходного кода

Closes #17841 Allows Tags to be displayed in specified order (#18930)

Jason Novinger 11 месяцев назад
Родитель
Сommit
6b7d23d684

+ 6 - 0
docs/models/extras/tag.md

@@ -16,6 +16,12 @@ A unique URL-friendly identifier. (This value will be used for filtering.) This
 
 The color to use when displaying the tag in the NetBox UI.
 
+### Weight
+
+A numeric weight employed to influence the ordering of tags. Tags with a lower weight will be listed before those with higher weights. Values must be within the range **0** to **32767**.
+
+!!! info "This field was introduced in NetBox v4.3."
+
 ### Object Types
 
 The assignment of a tag may be limited to a prescribed set of objects. For example, it may be desirable to limit the application of a specific tag to only devices and virtual machines.

+ 2 - 2
netbox/extras/api/serializers_/tags.py

@@ -27,8 +27,8 @@ class TagSerializer(ValidatedModelSerializer):
     class Meta:
         model = Tag
         fields = [
-            'id', 'url', 'display_url', 'display', 'name', 'slug', 'color', 'description', 'object_types',
-            'tagged_items', 'created', 'last_updated',
+            'id', 'url', 'display_url', 'display', 'name', 'slug', 'color', 'description', 'weight',
+            'object_types', 'tagged_items', 'created', 'last_updated',
         ]
         brief_fields = ('id', 'url', 'display', 'name', 'slug', 'color', 'description')
 

+ 1 - 1
netbox/extras/filtersets.py

@@ -450,7 +450,7 @@ class TagFilterSet(ChangeLoggedModelFilterSet):
 
     class Meta:
         model = Tag
-        fields = ('id', 'name', 'slug', 'color', 'description', 'object_types')
+        fields = ('id', 'name', 'slug', 'color', 'weight', 'description', 'object_types')
 
     def search(self, queryset, name, value):
         if not value.strip():

+ 4 - 0
netbox/extras/forms/bulk_edit.py

@@ -275,6 +275,10 @@ class TagBulkEditForm(BulkEditForm):
         max_length=200,
         required=False
     )
+    weight = forms.IntegerField(
+        label=_('Weight'),
+        required=False
+    )
 
     nullable_fields = ('description',)
 

+ 5 - 1
netbox/extras/forms/bulk_import.py

@@ -232,10 +232,14 @@ class EventRuleImportForm(NetBoxModelImportForm):
 
 class TagImportForm(CSVModelForm):
     slug = SlugField()
+    weight = forms.IntegerField(
+        label=_('Weight'),
+        required=False
+    )
 
     class Meta:
         model = Tag
-        fields = ('name', 'slug', 'color', 'description')
+        fields = ('name', 'slug', 'color', 'weight', 'description')
 
 
 class JournalEntryImportForm(NetBoxModelImportForm):

+ 6 - 2
netbox/extras/forms/model_forms.py

@@ -490,15 +490,19 @@ class TagForm(forms.ModelForm):
         queryset=ObjectType.objects.with_feature('tags'),
         required=False
     )
+    weight = forms.IntegerField(
+        label=_('Weight'),
+        required=False
+    )
 
     fieldsets = (
-        FieldSet('name', 'slug', 'color', 'description', 'object_types', name=_('Tag')),
+        FieldSet('name', 'slug', 'color', 'weight', 'description', 'object_types', name=_('Tag')),
     )
 
     class Meta:
         model = Tag
         fields = [
-            'name', 'slug', 'color', 'description', 'object_types',
+            'name', 'slug', 'color', 'weight', 'description', 'object_types',
         ]
 
 

+ 22 - 0
netbox/extras/migrations/0124_alter_tag_options_tag_weight.py

@@ -0,0 +1,22 @@
+# Generated by Django 5.2b1 on 2025-03-17 14:41
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('extras', '0123_remove_staging'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='tag',
+            options={'ordering': ('weight', 'name')},
+        ),
+        migrations.AddField(
+            model_name='tag',
+            name='weight',
+            field=models.PositiveSmallIntegerField(default=0),
+        ),
+    ]

+ 5 - 1
netbox/extras/models/tags.py

@@ -40,13 +40,17 @@ class Tag(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel, TagBase):
         blank=True,
         help_text=_("The object type(s) to which this tag can be applied.")
     )
+    weight = models.PositiveSmallIntegerField(
+        verbose_name=_('weight'),
+        default=0,
+    )
 
     clone_fields = (
         'color', 'description', 'object_types',
     )
 
     class Meta:
-        ordering = ['name']
+        ordering = ('weight', 'name')
         verbose_name = _('tag')
         verbose_name_plural = _('tags')
 

+ 2 - 2
netbox/extras/tables/tables.py

@@ -449,8 +449,8 @@ class TagTable(NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = Tag
         fields = (
-            'pk', 'id', 'name', 'items', 'slug', 'color', 'description', 'object_types', 'created', 'last_updated',
-            'actions',
+            'pk', 'id', 'name', 'items', 'slug', 'color', 'weight', 'description', 'object_types',
+            'created', 'last_updated', 'actions',
         )
         default_columns = ('pk', 'name', 'items', 'slug', 'color', 'description')
 

+ 2 - 1
netbox/extras/tests/test_api.py

@@ -513,6 +513,7 @@ class TagTest(APIViewTestCases.APIViewTestCase):
         {
             'name': 'Tag 4',
             'slug': 'tag-4',
+            'weight': 1000,
         },
         {
             'name': 'Tag 5',
@@ -533,7 +534,7 @@ class TagTest(APIViewTestCases.APIViewTestCase):
         tags = (
             Tag(name='Tag 1', slug='tag-1'),
             Tag(name='Tag 2', slug='tag-2'),
-            Tag(name='Tag 3', slug='tag-3'),
+            Tag(name='Tag 3', slug='tag-3', weight=26),
         )
         Tag.objects.bulk_create(tags)
 

+ 8 - 1
netbox/extras/tests/test_filtersets.py

@@ -1196,7 +1196,7 @@ class TagTestCase(TestCase, ChangeLoggedFilterSetTests):
         tags = (
             Tag(name='Tag 1', slug='tag-1', color='ff0000', description='foobar1'),
             Tag(name='Tag 2', slug='tag-2', color='00ff00', description='foobar2'),
-            Tag(name='Tag 3', slug='tag-3', color='0000ff'),
+            Tag(name='Tag 3', slug='tag-3', color='0000ff', weight=1000),
         )
         Tag.objects.bulk_create(tags)
         tags[0].object_types.add(object_types['site'])
@@ -1249,6 +1249,13 @@ class TagTestCase(TestCase, ChangeLoggedFilterSetTests):
             ['Tag 2', 'Tag 3']
         )
 
+    def test_weight(self):
+        params = {'weight': [1000]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+        params = {'weight': [0]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+
 
 class TaggedItemFilterSetTestCase(TestCase):
     queryset = TaggedItem.objects.all()

+ 34 - 0
netbox/extras/tests/test_models.py

@@ -10,6 +10,40 @@ from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMac
 
 class TagTest(TestCase):
 
+    def test_default_ordering_weight_then_name_is_set(self):
+        Tag.objects.create(name='Tag 1', slug='tag-1', weight=100)
+        Tag.objects.create(name='Tag 2', slug='tag-2')
+        Tag.objects.create(name='Tag 3', slug='tag-3', weight=10)
+        Tag.objects.create(name='Tag 4', slug='tag-4', weight=10)
+
+        tags = Tag.objects.all()
+
+        self.assertEqual(tags[0].slug, 'tag-2')
+        self.assertEqual(tags[1].slug, 'tag-3')
+        self.assertEqual(tags[2].slug, 'tag-4')
+        self.assertEqual(tags[3].slug, 'tag-1')
+
+    def test_tag_related_manager_ordering_weight_then_name(self):
+        tags = [
+            Tag.objects.create(name='Tag 1', slug='tag-1', weight=100),
+            Tag.objects.create(name='Tag 2', slug='tag-2'),
+            Tag.objects.create(name='Tag 3', slug='tag-3', weight=10),
+            Tag.objects.create(name='Tag 4', slug='tag-4', weight=10),
+        ]
+
+        site = Site.objects.create(name='Site 1')
+        for tag in tags:
+            site.tags.add(tag)
+        site.save()
+
+        site = Site.objects.first()
+        tags = site.tags.all()
+
+        self.assertEqual(tags[0].slug, 'tag-2')
+        self.assertEqual(tags[1].slug, 'tag-3')
+        self.assertEqual(tags[2].slug, 'tag-4')
+        self.assertEqual(tags[3].slug, 'tag-1')
+
     def test_create_tag_unicode(self):
         tag = Tag(name='Testing Unicode: 台灣')
         tag.save()

+ 7 - 6
netbox/extras/tests/test_views.py

@@ -441,8 +441,8 @@ class TagTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
 
         tags = (
             Tag(name='Tag 1', slug='tag-1'),
-            Tag(name='Tag 2', slug='tag-2'),
-            Tag(name='Tag 3', slug='tag-3'),
+            Tag(name='Tag 2', slug='tag-2', weight=1),
+            Tag(name='Tag 3', slug='tag-3', weight=32767),
         )
         Tag.objects.bulk_create(tags)
 
@@ -451,13 +451,14 @@ class TagTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
             'slug': 'tag-x',
             'color': 'c0c0c0',
             'comments': 'Some comments',
+            'weight': 11,
         }
 
         cls.csv_data = (
-            "name,slug,color,description",
-            "Tag 4,tag-4,ff0000,Fourth tag",
-            "Tag 5,tag-5,00ff00,Fifth tag",
-            "Tag 6,tag-6,0000ff,Sixth tag",
+            "name,slug,color,description,weight",
+            "Tag 4,tag-4,ff0000,Fourth tag,0",
+            "Tag 5,tag-5,00ff00,Fifth tag,1111",
+            "Tag 6,tag-6,0000ff,Sixth tag,0",
         )
 
         cls.csv_update_data = (

+ 2 - 1
netbox/netbox/models/features.py

@@ -455,7 +455,8 @@ class TagsMixin(models.Model):
     which is a `TaggableManager` instance.
     """
     tags = TaggableManager(
-        through='extras.TaggedItem'
+        through='extras.TaggedItem',
+        ordering=('weight', 'name'),
     )
 
     class Meta:

+ 4 - 0
netbox/templates/extras/tag.html

@@ -28,6 +28,10 @@
                 <span class="color-label" style="background-color: #{{ object.color }}">&nbsp;</span>
             </td>
           </tr>
+          <tr>
+            <th scope="row">{% trans "Weight" %}</th>
+            <td>{{ object.weight }}</td>
+          </tr>
           <tr>
             <th scope="row">{% trans "Tagged Items" %}</th>
             <td>