mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-25 08:46:10 -06:00
Initial work on #11541
This commit is contained in:
parent
69b818ed33
commit
9b189231c3
@ -15,3 +15,11 @@ A unique URL-friendly identifier. (This value will be used for filtering.) This
|
||||
### Color
|
||||
|
||||
The color to use when displaying the tag in the NetBox UI.
|
||||
|
||||
### Object Types
|
||||
|
||||
!!! info "This feature was introduced in NetBox v3.6."
|
||||
|
||||
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.
|
||||
|
||||
If no object types are specified, the tag will be assignable to any type of object.
|
||||
|
@ -196,12 +196,18 @@ class SavedFilterSerializer(ValidatedModelSerializer):
|
||||
|
||||
class TagSerializer(ValidatedModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='extras-api:tag-detail')
|
||||
object_types = ContentTypeField(
|
||||
queryset=ContentType.objects.filter(FeatureQuery('custom_fields').get_query()),
|
||||
many=True,
|
||||
required=False
|
||||
)
|
||||
tagged_items = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Tag
|
||||
fields = [
|
||||
'id', 'url', 'display', 'name', 'slug', 'color', 'description', 'tagged_items', 'created', 'last_updated',
|
||||
'id', 'url', 'display', 'name', 'slug', 'color', 'description', 'object_types', 'tagged_items', 'created',
|
||||
'last_updated',
|
||||
]
|
||||
|
||||
|
||||
|
@ -258,10 +258,14 @@ class TagFilterSet(ChangeLoggedModelFilterSet):
|
||||
content_type_id = MultiValueNumberFilter(
|
||||
method='_content_type_id'
|
||||
)
|
||||
object_type_id = MultiValueNumberFilter(
|
||||
field_name='object_types__id'
|
||||
)
|
||||
object_types = ContentTypeFilter()
|
||||
|
||||
class Meta:
|
||||
model = Tag
|
||||
fields = ['id', 'name', 'slug', 'color', 'description']
|
||||
fields = ['id', 'name', 'slug', 'color', 'description', 'object_types']
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
|
@ -245,6 +245,11 @@ class TagFilterForm(SavedFiltersMixin, FilterForm):
|
||||
required=False,
|
||||
label=_('Tagged object type')
|
||||
)
|
||||
object_type_id = ContentTypeMultipleChoiceField(
|
||||
queryset=ContentType.objects.filter(FeatureQuery('tags').get_query()),
|
||||
required=False,
|
||||
label=_('Allowed object type')
|
||||
)
|
||||
|
||||
|
||||
class ConfigContextFilterForm(SavedFiltersMixin, FilterForm):
|
||||
|
@ -204,15 +204,20 @@ class WebhookForm(BootstrapMixin, forms.ModelForm):
|
||||
|
||||
class TagForm(BootstrapMixin, forms.ModelForm):
|
||||
slug = SlugField()
|
||||
object_types = ContentTypeMultipleChoiceField(
|
||||
queryset=ContentType.objects.all(),
|
||||
limit_choices_to=FeatureQuery('tags'),
|
||||
required=False
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Tag', ('name', 'slug', 'color', 'description')),
|
||||
('Tag', ('name', 'slug', 'color', 'description', 'object_types')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Tag
|
||||
fields = [
|
||||
'name', 'slug', 'color', 'description'
|
||||
'name', 'slug', 'color', 'description', 'object_types',
|
||||
]
|
||||
|
||||
|
||||
|
20
netbox/extras/migrations/0094_tag_object_types.py
Normal file
20
netbox/extras/migrations/0094_tag_object_types.py
Normal file
@ -0,0 +1,20 @@
|
||||
# Generated by Django 4.1.9 on 2023-06-22 19:33
|
||||
|
||||
from django.db import migrations, models
|
||||
import extras.utils
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('extras', '0093_configrevision_ordering'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='tag',
|
||||
name='object_types',
|
||||
field=models.ManyToManyField(blank=True, limit_choices_to=extras.utils.FeatureQuery('tags'), related_name='+', to='contenttypes.contenttype'),
|
||||
),
|
||||
]
|
@ -1,9 +1,13 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils.text import slugify
|
||||
from django.utils.translation import gettext as _
|
||||
from taggit.models import TagBase, GenericTaggedItemBase
|
||||
|
||||
from extras.utils import FeatureQuery
|
||||
from netbox.models import ChangeLoggedModel
|
||||
from netbox.models.features import CloningMixin, ExportTemplatesMixin
|
||||
from utilities.choices import ColorChoices
|
||||
@ -30,9 +34,16 @@ class Tag(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel, TagBase):
|
||||
max_length=200,
|
||||
blank=True,
|
||||
)
|
||||
object_types = models.ManyToManyField(
|
||||
to=ContentType,
|
||||
related_name='+',
|
||||
limit_choices_to=FeatureQuery('tags'),
|
||||
blank=True,
|
||||
help_text=_("The object type(s) to which this this tag can be applied.")
|
||||
)
|
||||
|
||||
clone_fields = (
|
||||
'color', 'description',
|
||||
'color', 'description', 'object_types',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -10,8 +10,9 @@ from extras.validators import CustomValidator
|
||||
from netbox.config import get_config
|
||||
from netbox.context import current_request, webhooks_queue
|
||||
from netbox.signals import post_clean
|
||||
from utilities.exceptions import AbortRequest
|
||||
from .choices import ObjectChangeActionChoices
|
||||
from .models import ConfigRevision, CustomField, ObjectChange
|
||||
from .models import ConfigRevision, CustomField, ObjectChange, TaggedItem
|
||||
from .webhooks import enqueue_object, get_snapshots, serialize_for_webhook
|
||||
|
||||
#
|
||||
@ -207,3 +208,21 @@ def update_config(sender, instance, **kwargs):
|
||||
Update the cached NetBox configuration when a new ConfigRevision is created.
|
||||
"""
|
||||
instance.activate()
|
||||
|
||||
|
||||
#
|
||||
# Tags
|
||||
#
|
||||
|
||||
@receiver(m2m_changed, sender=TaggedItem)
|
||||
def validate_assigned_tags(sender, instance, action, model, pk_set, **kwargs):
|
||||
"""
|
||||
Validate that any Tags being assigned to the instance are not restricted to non-applicable object types.
|
||||
"""
|
||||
if action != 'pre_add':
|
||||
return
|
||||
ct = ContentType.objects.get_for_model(instance)
|
||||
# Retrieve any applied Tags that are restricted to certain object_types
|
||||
for tag in model.objects.filter(pk__in=pk_set, object_types__isnull=False).prefetch_related('object_types'):
|
||||
if ct not in tag.object_types.all():
|
||||
raise AbortRequest(f"Tag {tag} cannot be assigned to {ct.model} objects.")
|
||||
|
@ -210,10 +210,14 @@ class TagTable(NetBoxTable):
|
||||
linkify=True
|
||||
)
|
||||
color = columns.ColorColumn()
|
||||
object_types = columns.ContentTypesColumn()
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = Tag
|
||||
fields = ('pk', 'id', 'name', 'items', 'slug', 'color', 'description', 'created', 'last_updated', 'actions')
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'items', 'slug', 'color', 'description', 'object_types', 'created', 'last_updated',
|
||||
'actions',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'items', 'slug', 'color', 'description')
|
||||
|
||||
|
||||
|
@ -43,9 +43,23 @@
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
<div class="card">
|
||||
<h5 class="card-header">
|
||||
Tagged Item Types
|
||||
</h5>
|
||||
<h5 class="card-header">Allowed Object Types</h5>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover attr-table">
|
||||
{% for ct in object.object_types.all %}
|
||||
<tr>
|
||||
<td>{{ ct }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td class="text-muted">Any</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h5 class="card-header">Tagged Item Types</h5>
|
||||
<div class="card-body">
|
||||
<table class="table table-hover panel-body attr-table">
|
||||
{% for object_type in object_types %}
|
||||
|
Loading…
Reference in New Issue
Block a user