From 9b3869790d49fa7f9a372dbefeca150520de6728 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 10 May 2018 12:53:11 -0400 Subject: [PATCH] Implemented tags for all primary models --- netbox/circuits/api/serializers.py | 9 +++-- netbox/circuits/forms.py | 7 ++-- netbox/circuits/models.py | 5 +++ netbox/dcim/forms.py | 4 +-- netbox/ipam/api/serializers.py | 23 +++++++++---- netbox/ipam/forms.py | 19 ++++++++--- netbox/ipam/models.py | 9 +++++ netbox/secrets/api/serializers.py | 6 ++-- netbox/secrets/forms.py | 4 ++- netbox/secrets/models.py | 3 ++ netbox/templates/circuits/circuit.html | 4 +++ netbox/templates/circuits/circuit_edit.html | 6 ++++ netbox/templates/circuits/provider.html | 4 +++ netbox/templates/circuits/provider_edit.html | 6 ++++ netbox/templates/dcim/device_edit.html | 6 ++++ netbox/templates/dcim/devicetype_edit.html | 7 +++- netbox/templates/dcim/rack_edit.html | 7 +++- netbox/templates/dcim/site_edit.html | 7 +++- netbox/templates/ipam/aggregate.html | 4 +++ netbox/templates/ipam/aggregate_edit.html | 6 ++++ netbox/templates/ipam/ipaddress.html | 4 +++ netbox/templates/ipam/ipaddress_edit.html | 6 ++++ netbox/templates/ipam/prefix.html | 4 +++ netbox/templates/ipam/prefix_edit.html | 6 ++++ netbox/templates/ipam/vlan.html | 4 +++ netbox/templates/ipam/vlan_edit.html | 6 ++++ netbox/templates/ipam/vrf.html | 4 +++ netbox/templates/ipam/vrf_edit.html | 6 ++++ netbox/templates/secrets/secret.html | 4 +++ netbox/templates/secrets/secret_edit.html | 6 ++++ netbox/templates/tenancy/tenant.html | 4 +++ netbox/templates/tenancy/tenant_edit.html | 6 ++++ netbox/templates/virtualization/cluster.html | 4 +++ .../virtualization/cluster_edit.html | 34 +++++++++++++++++++ .../virtualization/virtualmachine.html | 4 +++ .../virtualization/virtualmachine_edit.html | 6 ++++ netbox/tenancy/api/serializers.py | 9 +++-- netbox/tenancy/forms.py | 4 ++- netbox/tenancy/models.py | 3 ++ netbox/virtualization/api/serializers.py | 13 ++++--- netbox/virtualization/forms.py | 7 ++-- netbox/virtualization/models.py | 5 +++ netbox/virtualization/views.py | 1 + 43 files changed, 262 insertions(+), 34 deletions(-) create mode 100644 netbox/templates/virtualization/cluster_edit.html diff --git a/netbox/circuits/api/serializers.py b/netbox/circuits/api/serializers.py index ded67c934..c42edb5ae 100644 --- a/netbox/circuits/api/serializers.py +++ b/netbox/circuits/api/serializers.py @@ -1,13 +1,14 @@ from __future__ import unicode_literals from rest_framework import serializers +from taggit.models import Tag from circuits.constants import CIRCUIT_STATUS_CHOICES from circuits.models import Provider, Circuit, CircuitTermination, CircuitType from dcim.api.serializers import NestedSiteSerializer, InterfaceSerializer from extras.api.customfields import CustomFieldModelSerializer from tenancy.api.serializers import NestedTenantSerializer -from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer, WritableNestedSerializer +from utilities.api import ChoiceFieldSerializer, TagField, ValidatedModelSerializer, WritableNestedSerializer # @@ -15,11 +16,12 @@ from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer, Writa # class ProviderSerializer(CustomFieldModelSerializer): + tags = TagField(queryset=Tag.objects.all(), required=False, many=True) class Meta: model = Provider fields = [ - 'id', 'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments', + 'id', 'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', ] @@ -60,12 +62,13 @@ class CircuitSerializer(CustomFieldModelSerializer): status = ChoiceFieldSerializer(choices=CIRCUIT_STATUS_CHOICES, required=False) type = NestedCircuitTypeSerializer() tenant = NestedTenantSerializer(required=False, allow_null=True) + tags = TagField(queryset=Tag.objects.all(), required=False, many=True) class Meta: model = Circuit fields = [ 'id', 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', - 'comments', 'custom_fields', 'created', 'last_updated', + 'comments', 'tags', 'custom_fields', 'created', 'last_updated', ] diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py index bfcfa7187..7207e7648 100644 --- a/netbox/circuits/forms.py +++ b/netbox/circuits/forms.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from django import forms from django.db.models import Count +from taggit.forms import TagField from dcim.models import Site, Device, Interface, Rack from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm @@ -22,10 +23,11 @@ from .models import Circuit, CircuitTermination, CircuitType, Provider class ProviderForm(BootstrapMixin, CustomFieldForm): slug = SlugField() comments = CommentField() + tags = TagField(required=False) class Meta: model = Provider - fields = ['name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments'] + fields = ['name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments', 'tags'] widgets = { 'noc_contact': SmallTextarea(attrs={'rows': 5}), 'admin_contact': SmallTextarea(attrs={'rows': 5}), @@ -102,12 +104,13 @@ class CircuitTypeCSVForm(forms.ModelForm): class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldForm): comments = CommentField() + tags = TagField(required=False) class Meta: model = Circuit fields = [ 'cid', 'type', 'provider', 'status', 'install_date', 'commit_rate', 'description', 'tenant_group', 'tenant', - 'comments', + 'comments', 'tags', ] help_texts = { 'cid': "Unique circuit ID", diff --git a/netbox/circuits/models.py b/netbox/circuits/models.py index 4df845bd8..cb79b35a4 100644 --- a/netbox/circuits/models.py +++ b/netbox/circuits/models.py @@ -4,6 +4,7 @@ from django.contrib.contenttypes.fields import GenericRelation from django.db import models from django.urls import reverse from django.utils.encoding import python_2_unicode_compatible +from taggit.managers import TaggableManager from dcim.constants import STATUS_CLASSES from dcim.fields import ASNField @@ -56,6 +57,8 @@ class Provider(CreatedUpdatedModel, CustomFieldModel): object_id_field='obj_id' ) + tags = TaggableManager() + csv_headers = ['name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments'] class Meta: @@ -166,6 +169,8 @@ class Circuit(CreatedUpdatedModel, CustomFieldModel): object_id_field='obj_id' ) + tags = TaggableManager() + csv_headers = [ 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments', ] diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 1d836028e..fe8476d72 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -784,8 +784,8 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm): class Meta: model = Device fields = [ - 'name', 'device_role', 'tags', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'position', 'face', - 'status', 'platform', 'primary_ip4', 'primary_ip6', 'tenant_group', 'tenant', 'comments', + 'name', 'device_role', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'position', 'face', + 'status', 'platform', 'primary_ip4', 'primary_ip6', 'tenant_group', 'tenant', 'comments', 'tags', ] help_texts = { 'device_role': "The function this device serves", diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index 6fb9d3ba4..f7969fbc3 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -5,6 +5,7 @@ from collections import OrderedDict from rest_framework import serializers from rest_framework.reverse import reverse from rest_framework.validators import UniqueTogetherValidator +from taggit.models import Tag from dcim.api.serializers import NestedDeviceSerializer, InterfaceSerializer, NestedSiteSerializer from dcim.models import Interface @@ -14,7 +15,9 @@ from ipam.constants import ( ) from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF from tenancy.api.serializers import NestedTenantSerializer -from utilities.api import ChoiceFieldSerializer, SerializedPKRelatedField, ValidatedModelSerializer, WritableNestedSerializer +from utilities.api import ( + ChoiceFieldSerializer, SerializedPKRelatedField, TagField, ValidatedModelSerializer, WritableNestedSerializer, +) from virtualization.api.serializers import NestedVirtualMachineSerializer @@ -24,12 +27,13 @@ from virtualization.api.serializers import NestedVirtualMachineSerializer class VRFSerializer(CustomFieldModelSerializer): tenant = NestedTenantSerializer(required=False, allow_null=True) + tags = TagField(queryset=Tag.objects.all(), required=False, many=True) class Meta: model = VRF fields = [ - 'id', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'display_name', 'custom_fields', 'created', - 'last_updated', + 'id', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'tags', 'display_name', 'custom_fields', + 'created', 'last_updated', ] @@ -85,11 +89,13 @@ class NestedRIRSerializer(WritableNestedSerializer): class AggregateSerializer(CustomFieldModelSerializer): rir = NestedRIRSerializer() + tags = TagField(queryset=Tag.objects.all(), required=False, many=True) class Meta: model = Aggregate fields = [ - 'id', 'family', 'prefix', 'rir', 'date_added', 'description', 'custom_fields', 'created', 'last_updated', + 'id', 'family', 'prefix', 'rir', 'date_added', 'description', 'tags', 'custom_fields', 'created', + 'last_updated', ] read_only_fields = ['family'] @@ -147,11 +153,12 @@ class VLANSerializer(CustomFieldModelSerializer): tenant = NestedTenantSerializer(required=False, allow_null=True) status = ChoiceFieldSerializer(choices=VLAN_STATUS_CHOICES, required=False) role = NestedRoleSerializer(required=False, allow_null=True) + tags = TagField(queryset=Tag.objects.all(), required=False, many=True) class Meta: model = VLAN fields = [ - 'id', 'site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description', 'display_name', + 'id', 'site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description', 'tags', 'display_name', 'custom_fields', 'created', 'last_updated', ] validators = [] @@ -190,12 +197,13 @@ class PrefixSerializer(CustomFieldModelSerializer): vlan = NestedVLANSerializer(required=False, allow_null=True) status = ChoiceFieldSerializer(choices=PREFIX_STATUS_CHOICES, required=False) role = NestedRoleSerializer(required=False, allow_null=True) + tags = TagField(queryset=Tag.objects.all(), required=False, many=True) class Meta: model = Prefix fields = [ 'id', 'family', 'prefix', 'site', 'vrf', 'tenant', 'vlan', 'status', 'role', 'is_pool', 'description', - 'custom_fields', 'created', 'last_updated', + 'tags', 'custom_fields', 'created', 'last_updated', ] read_only_fields = ['family'] @@ -252,12 +260,13 @@ class IPAddressSerializer(CustomFieldModelSerializer): status = ChoiceFieldSerializer(choices=IPADDRESS_STATUS_CHOICES, required=False) role = ChoiceFieldSerializer(choices=IPADDRESS_ROLE_CHOICES, required=False) interface = IPAddressInterfaceSerializer(required=False, allow_null=True) + tags = TagField(queryset=Tag.objects.all(), required=False, many=True) class Meta: model = IPAddress fields = [ 'id', 'family', 'address', 'vrf', 'tenant', 'status', 'role', 'interface', 'description', 'nat_inside', - 'nat_outside', 'custom_fields', 'created', 'last_updated', + 'nat_outside', 'tags', 'custom_fields', 'created', 'last_updated', ] read_only_fields = ['family'] diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 3353d981f..82ebfe724 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals from django import forms from django.core.exceptions import MultipleObjectsReturned from django.db.models import Count +from taggit.forms import TagField from dcim.models import Site, Rack, Device, Interface from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm @@ -32,10 +33,11 @@ IPADDRESS_MASK_LENGTH_CHOICES = add_blank_choice([(i, i) for i in range(1, 129)] # class VRFForm(BootstrapMixin, TenancyForm, CustomFieldForm): + tags = TagField(required=False) class Meta: model = VRF - fields = ['name', 'rd', 'enforce_unique', 'description', 'tenant_group', 'tenant'] + fields = ['name', 'rd', 'enforce_unique', 'description', 'tenant_group', 'tenant', 'tags'] labels = { 'rd': "RD", } @@ -121,10 +123,11 @@ class RIRFilterForm(BootstrapMixin, forms.Form): # class AggregateForm(BootstrapMixin, CustomFieldForm): + tags = TagField(required=False) class Meta: model = Aggregate - fields = ['prefix', 'rir', 'date_added', 'description'] + fields = ['prefix', 'rir', 'date_added', 'description', 'tags'] help_texts = { 'prefix': "IPv4 or IPv6 network", 'rir': "Regional Internet Registry responsible for this prefix", @@ -228,10 +231,14 @@ class PrefixForm(BootstrapMixin, TenancyForm, CustomFieldForm): api_url='/api/ipam/vlans/?site_id={{site}}&group_id={{vlan_group}}', display_field='display_name' ) ) + tags = TagField(required=False) class Meta: model = Prefix - fields = ['prefix', 'vrf', 'site', 'vlan', 'status', 'role', 'is_pool', 'description', 'tenant_group', 'tenant'] + fields = [ + 'prefix', 'vrf', 'site', 'vlan', 'status', 'role', 'is_pool', 'description', 'tenant_group', 'tenant', + 'tags', + ] def __init__(self, *args, **kwargs): @@ -455,12 +462,13 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm) ) ) primary_for_parent = forms.BooleanField(required=False, label='Make this the primary IP for the device/VM') + tags = TagField(required=False) class Meta: model = IPAddress fields = [ 'address', 'vrf', 'status', 'role', 'description', 'interface', 'primary_for_parent', 'nat_site', - 'nat_rack', 'nat_inside', 'tenant_group', 'tenant', + 'nat_rack', 'nat_inside', 'tenant_group', 'tenant', 'tags', ] def __init__(self, *args, **kwargs): @@ -780,10 +788,11 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldForm): api_url='/api/ipam/vlan-groups/?site_id={{site}}', ) ) + tags = TagField(required=False) class Meta: model = VLAN - fields = ['site', 'group', 'vid', 'name', 'status', 'role', 'description', 'tenant_group', 'tenant'] + fields = ['site', 'group', 'vid', 'name', 'status', 'role', 'description', 'tenant_group', 'tenant', 'tags'] help_texts = { 'site': "Leave blank if this VLAN spans multiple sites", 'group': "VLAN group (optional)", diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index 2f83bb0f2..65a9cce55 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -10,6 +10,7 @@ from django.db.models import Q from django.db.models.expressions import RawSQL from django.urls import reverse from django.utils.encoding import python_2_unicode_compatible +from taggit.managers import TaggableManager from dcim.models import Interface from extras.models import CustomFieldModel @@ -56,6 +57,8 @@ class VRF(CreatedUpdatedModel, CustomFieldModel): object_id_field='obj_id' ) + tags = TaggableManager() + csv_headers = ['name', 'rd', 'tenant', 'enforce_unique', 'description'] class Meta: @@ -155,6 +158,8 @@ class Aggregate(CreatedUpdatedModel, CustomFieldModel): object_id_field='obj_id' ) + tags = TaggableManager() + csv_headers = ['prefix', 'rir', 'date_added', 'description'] class Meta: @@ -325,6 +330,7 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel): ) objects = PrefixQuerySet.as_manager() + tags = TaggableManager() csv_headers = [ 'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan_vid', 'status', 'role', 'is_pool', 'description', @@ -564,6 +570,7 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel): ) objects = IPAddressManager() + tags = TaggableManager() csv_headers = [ 'address', 'vrf', 'tenant', 'status', 'role', 'device', 'virtual_machine', 'interface_name', 'is_primary', @@ -759,6 +766,8 @@ class VLAN(CreatedUpdatedModel, CustomFieldModel): object_id_field='obj_id' ) + tags = TaggableManager() + csv_headers = ['site', 'group_name', 'vid', 'name', 'tenant', 'status', 'role', 'description'] class Meta: diff --git a/netbox/secrets/api/serializers.py b/netbox/secrets/api/serializers.py index aca91920a..0e24281bb 100644 --- a/netbox/secrets/api/serializers.py +++ b/netbox/secrets/api/serializers.py @@ -2,10 +2,11 @@ from __future__ import unicode_literals from rest_framework import serializers from rest_framework.validators import UniqueTogetherValidator +from taggit.models import Tag from dcim.api.serializers import NestedDeviceSerializer from secrets.models import Secret, SecretRole -from utilities.api import ValidatedModelSerializer, WritableNestedSerializer +from utilities.api import TagField, ValidatedModelSerializer, WritableNestedSerializer # @@ -35,10 +36,11 @@ class SecretSerializer(ValidatedModelSerializer): device = NestedDeviceSerializer() role = NestedSecretRoleSerializer() plaintext = serializers.CharField() + tags = TagField(queryset=Tag.objects.all(), required=False, many=True) class Meta: model = Secret - fields = ['id', 'device', 'role', 'name', 'plaintext', 'hash', 'created', 'last_updated'] + fields = ['id', 'device', 'role', 'name', 'plaintext', 'hash', 'tags', 'created', 'last_updated'] validators = [] def validate(self, data): diff --git a/netbox/secrets/forms.py b/netbox/secrets/forms.py index 8f8107805..863d1dfde 100644 --- a/netbox/secrets/forms.py +++ b/netbox/secrets/forms.py @@ -4,6 +4,7 @@ from Crypto.Cipher import PKCS1_OAEP from Crypto.PublicKey import RSA from django import forms from django.db.models import Count +from taggit.forms import TagField from dcim.models import Device from utilities.forms import BootstrapMixin, BulkEditForm, FilterChoiceField, FlexibleModelChoiceField, SlugField @@ -70,10 +71,11 @@ class SecretForm(BootstrapMixin, forms.ModelForm): label='Plaintext (verify)', widget=forms.PasswordInput() ) + tags = TagField(required=False) class Meta: model = Secret - fields = ['role', 'name', 'plaintext', 'plaintext2'] + fields = ['role', 'name', 'plaintext', 'plaintext2', 'tags'] def __init__(self, *args, **kwargs): diff --git a/netbox/secrets/models.py b/netbox/secrets/models.py index e39d46eef..dcb38db70 100644 --- a/netbox/secrets/models.py +++ b/netbox/secrets/models.py @@ -12,6 +12,7 @@ from django.core.exceptions import ValidationError from django.db import models from django.urls import reverse from django.utils.encoding import force_bytes, python_2_unicode_compatible +from taggit.managers import TaggableManager from utilities.models import CreatedUpdatedModel from .exceptions import InvalidKey @@ -336,6 +337,8 @@ class Secret(CreatedUpdatedModel): editable=False ) + tags = TaggableManager() + plaintext = None csv_headers = ['device', 'role', 'name', 'plaintext'] diff --git a/netbox/templates/circuits/circuit.html b/netbox/templates/circuits/circuit.html index 1133f41f3..34d467c89 100644 --- a/netbox/templates/circuits/circuit.html +++ b/netbox/templates/circuits/circuit.html @@ -110,6 +110,10 @@ {% endif %} + + Tags + {{ circuit.tags.all|join:" " }} + {% with circuit.get_custom_fields as custom_fields %} diff --git a/netbox/templates/circuits/circuit_edit.html b/netbox/templates/circuits/circuit_edit.html index 8503e68f6..06ad65241 100644 --- a/netbox/templates/circuits/circuit_edit.html +++ b/netbox/templates/circuits/circuit_edit.html @@ -44,6 +44,12 @@ {% render_field form.comments %} +
+
Tags
+
+ {% render_field form.tags %} +
+
{% endblock %} {% block javascript %} diff --git a/netbox/templates/circuits/provider.html b/netbox/templates/circuits/provider.html index 6dcccfd8d..583d1da4d 100644 --- a/netbox/templates/circuits/provider.html +++ b/netbox/templates/circuits/provider.html @@ -102,6 +102,10 @@ {% endif %} + + Tags + {{ provider.tags.all|join:" " }} + Circuits diff --git a/netbox/templates/circuits/provider_edit.html b/netbox/templates/circuits/provider_edit.html index 4fb3889b1..dfa239e40 100644 --- a/netbox/templates/circuits/provider_edit.html +++ b/netbox/templates/circuits/provider_edit.html @@ -33,4 +33,10 @@ {% render_field form.comments %} +
+
Tags
+
+ {% render_field form.tags %} +
+
{% endblock %} diff --git a/netbox/templates/dcim/device_edit.html b/netbox/templates/dcim/device_edit.html index 1b7a8a9a8..460d96423 100644 --- a/netbox/templates/dcim/device_edit.html +++ b/netbox/templates/dcim/device_edit.html @@ -84,4 +84,10 @@ {% render_field form.comments %} +
+
Tags
+
+ {% render_field form.tags %} +
+
{% endblock %} diff --git a/netbox/templates/dcim/devicetype_edit.html b/netbox/templates/dcim/devicetype_edit.html index 2d7a5b132..e69077ad9 100644 --- a/netbox/templates/dcim/devicetype_edit.html +++ b/netbox/templates/dcim/devicetype_edit.html @@ -12,7 +12,6 @@ {% render_field form.u_height %} {% render_field form.is_full_depth %} {% render_field form.interface_ordering %} - {% render_field form.tags %}
@@ -38,4 +37,10 @@ {% render_field form.comments %}
+
+
Tags
+
+ {% render_field form.tags %} +
+
{% endblock %} diff --git a/netbox/templates/dcim/rack_edit.html b/netbox/templates/dcim/rack_edit.html index 0e50e5b8b..b9526a3ac 100644 --- a/netbox/templates/dcim/rack_edit.html +++ b/netbox/templates/dcim/rack_edit.html @@ -11,7 +11,6 @@ {% render_field form.group %} {% render_field form.role %} {% render_field form.serial %} - {% render_field form.tags %}
@@ -44,4 +43,10 @@ {% render_field form.comments %}
+
+
Tags
+
+ {% render_field form.tags %} +
+
{% endblock %} diff --git a/netbox/templates/dcim/site_edit.html b/netbox/templates/dcim/site_edit.html index 49a3f7241..ad7932642 100644 --- a/netbox/templates/dcim/site_edit.html +++ b/netbox/templates/dcim/site_edit.html @@ -13,7 +13,6 @@ {% render_field form.asn %} {% render_field form.time_zone %} {% render_field form.description %} - {% render_field form.tags %}
@@ -47,4 +46,10 @@ {% render_field form.comments %}
+
+
Tags
+
+ {% render_field form.tags %} +
+
{% endblock %} diff --git a/netbox/templates/ipam/aggregate.html b/netbox/templates/ipam/aggregate.html index 63731755c..de32e9c00 100644 --- a/netbox/templates/ipam/aggregate.html +++ b/netbox/templates/ipam/aggregate.html @@ -81,6 +81,10 @@ {% endif %} + + Tags + {{ aggregate.tags.all|join:" " }} + diff --git a/netbox/templates/ipam/aggregate_edit.html b/netbox/templates/ipam/aggregate_edit.html index be499a509..3cb83ab54 100644 --- a/netbox/templates/ipam/aggregate_edit.html +++ b/netbox/templates/ipam/aggregate_edit.html @@ -19,4 +19,10 @@ {% endif %} +
+
Tags
+
+ {% render_field form.tags %} +
+
{% endblock %} diff --git a/netbox/templates/ipam/ipaddress.html b/netbox/templates/ipam/ipaddress.html index 1509f35cb..c6002eb02 100644 --- a/netbox/templates/ipam/ipaddress.html +++ b/netbox/templates/ipam/ipaddress.html @@ -133,6 +133,10 @@ {% endif %} + + Tags + {{ ipaddress.tags.all|join:" " }} + {% with ipaddress.get_custom_fields as custom_fields %} diff --git a/netbox/templates/ipam/ipaddress_edit.html b/netbox/templates/ipam/ipaddress_edit.html index d0dad69ee..72fc02a1e 100644 --- a/netbox/templates/ipam/ipaddress_edit.html +++ b/netbox/templates/ipam/ipaddress_edit.html @@ -66,6 +66,12 @@ {% render_field form.nat_inside %} +
+
Tags
+
+ {% render_field form.tags %} +
+
{% if form.custom_fields %}
Custom Fields
diff --git a/netbox/templates/ipam/prefix.html b/netbox/templates/ipam/prefix.html index 11c5fc405..466fcc92d 100644 --- a/netbox/templates/ipam/prefix.html +++ b/netbox/templates/ipam/prefix.html @@ -121,6 +121,10 @@ {% endif %} + + Tags + {{ prefix.tags.all|join:" " }} + Utilization {% utilization_graph prefix.get_utilization %} diff --git a/netbox/templates/ipam/prefix_edit.html b/netbox/templates/ipam/prefix_edit.html index 938a75da3..333cf1229 100644 --- a/netbox/templates/ipam/prefix_edit.html +++ b/netbox/templates/ipam/prefix_edit.html @@ -28,6 +28,12 @@ {% render_field form.tenant %}
+
+
Tags
+
+ {% render_field form.tags %} +
+
{% if form.custom_fields %}
Custom Fields
diff --git a/netbox/templates/ipam/vlan.html b/netbox/templates/ipam/vlan.html index 971c3359f..817f0e6b5 100644 --- a/netbox/templates/ipam/vlan.html +++ b/netbox/templates/ipam/vlan.html @@ -80,6 +80,10 @@ N/A {% endif %} + + + Tags + {{ vlan.tags.all|join:" " }}
diff --git a/netbox/templates/ipam/vlan_edit.html b/netbox/templates/ipam/vlan_edit.html index 3bfb7783e..7862d4de9 100644 --- a/netbox/templates/ipam/vlan_edit.html +++ b/netbox/templates/ipam/vlan_edit.html @@ -21,6 +21,12 @@ {% render_field form.tenant %} +
+
Tags
+
+ {% render_field form.tags %} +
+
{% if form.custom_fields %}
Custom Fields
diff --git a/netbox/templates/ipam/vrf.html b/netbox/templates/ipam/vrf.html index e041ce73a..51088a0ec 100644 --- a/netbox/templates/ipam/vrf.html +++ b/netbox/templates/ipam/vrf.html @@ -77,6 +77,10 @@ N/A {% endif %} + + + Tags + {{ vrf.tags.all|join:" " }}
diff --git a/netbox/templates/ipam/vrf_edit.html b/netbox/templates/ipam/vrf_edit.html index 63052129c..95a89a6ca 100644 --- a/netbox/templates/ipam/vrf_edit.html +++ b/netbox/templates/ipam/vrf_edit.html @@ -18,6 +18,12 @@ {% render_field form.tenant %} +
+
Tags
+
+ {% render_field form.tags %} +
+
{% if form.custom_fields %}
Custom Fields
diff --git a/netbox/templates/secrets/secret.html b/netbox/templates/secrets/secret.html index 66c844ebf..e9e333ee7 100644 --- a/netbox/templates/secrets/secret.html +++ b/netbox/templates/secrets/secret.html @@ -55,6 +55,10 @@ {% endif %} + + Tags + {{ secret.tags.all|join:" " }} +
diff --git a/netbox/templates/secrets/secret_edit.html b/netbox/templates/secrets/secret_edit.html index 920409177..87ee3b426 100644 --- a/netbox/templates/secrets/secret_edit.html +++ b/netbox/templates/secrets/secret_edit.html @@ -54,6 +54,12 @@ {% render_field form.plaintext2 %} +
+
Tags
+
+ {% render_field form.tags %} +
+
diff --git a/netbox/templates/tenancy/tenant.html b/netbox/templates/tenancy/tenant.html index d5eb7df98..bf7f5ed67 100644 --- a/netbox/templates/tenancy/tenant.html +++ b/netbox/templates/tenancy/tenant.html @@ -68,6 +68,10 @@ {% endif %} + + Tags + {{ tenant.tags.all|join:" " }} +
{% with tenant.get_custom_fields as custom_fields %} diff --git a/netbox/templates/tenancy/tenant_edit.html b/netbox/templates/tenancy/tenant_edit.html index b2c472a1c..9cc0aa53b 100644 --- a/netbox/templates/tenancy/tenant_edit.html +++ b/netbox/templates/tenancy/tenant_edit.html @@ -26,4 +26,10 @@ {% render_field form.comments %} +
+
Tags
+
+ {% render_field form.tags %} +
+
{% endblock %} diff --git a/netbox/templates/virtualization/cluster.html b/netbox/templates/virtualization/cluster.html index 08251e2fa..05031dff0 100644 --- a/netbox/templates/virtualization/cluster.html +++ b/netbox/templates/virtualization/cluster.html @@ -76,6 +76,10 @@ {% endif %} + + Tags + {{ cluster.tags.all|join:" " }} + Virtual Machines {{ cluster.virtual_machines.count }} diff --git a/netbox/templates/virtualization/cluster_edit.html b/netbox/templates/virtualization/cluster_edit.html new file mode 100644 index 000000000..93fe197ec --- /dev/null +++ b/netbox/templates/virtualization/cluster_edit.html @@ -0,0 +1,34 @@ +{% extends 'utilities/obj_edit.html' %} +{% load form_helpers %} + +{% block form %} +
+
Cluster
+
+ {% render_field form.name %} + {% render_field form.type %} + {% render_field form.group %} + {% render_field form.site %} +
+
+ {% if form.custom_fields %} +
+
Custom Fields
+
+ {% render_custom_fields form %} +
+
+ {% endif %} +
+
Comments
+
+ {% render_field form.comments %} +
+
+
+
Tags
+
+ {% render_field form.tags %} +
+
+{% endblock %} diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html index 944792705..c8d119528 100644 --- a/netbox/templates/virtualization/virtualmachine.html +++ b/netbox/templates/virtualization/virtualmachine.html @@ -121,6 +121,10 @@ {% endif %} + + Tags + {{ vm.tags.all|join:" " }} + {% include 'inc/custom_fields_panel.html' with custom_fields=vm.get_custom_fields %} diff --git a/netbox/templates/virtualization/virtualmachine_edit.html b/netbox/templates/virtualization/virtualmachine_edit.html index 706591ab4..0fa7e07fb 100644 --- a/netbox/templates/virtualization/virtualmachine_edit.html +++ b/netbox/templates/virtualization/virtualmachine_edit.html @@ -54,4 +54,10 @@ {% render_field form.comments %} +
+
Tags
+
+ {% render_field form.tags %} +
+
{% endblock %} diff --git a/netbox/tenancy/api/serializers.py b/netbox/tenancy/api/serializers.py index 3a6e1fb4b..c7b94e7e9 100644 --- a/netbox/tenancy/api/serializers.py +++ b/netbox/tenancy/api/serializers.py @@ -1,10 +1,11 @@ from __future__ import unicode_literals from rest_framework import serializers +from taggit.models import Tag from extras.api.customfields import CustomFieldModelSerializer from tenancy.models import Tenant, TenantGroup -from utilities.api import ValidatedModelSerializer, WritableNestedSerializer +from utilities.api import TagField, ValidatedModelSerializer, WritableNestedSerializer # @@ -32,10 +33,14 @@ class NestedTenantGroupSerializer(WritableNestedSerializer): class TenantSerializer(CustomFieldModelSerializer): group = NestedTenantGroupSerializer(required=False) + tags = TagField(queryset=Tag.objects.all(), required=False, many=True) class Meta: model = Tenant - fields = ['id', 'name', 'slug', 'group', 'description', 'comments', 'custom_fields', 'created', 'last_updated'] + fields = [ + 'id', 'name', 'slug', 'group', 'description', 'comments', 'tags', 'custom_fields', 'created', + 'last_updated', + ] class NestedTenantSerializer(WritableNestedSerializer): diff --git a/netbox/tenancy/forms.py b/netbox/tenancy/forms.py index 4ea6c57ba..123b2bc24 100644 --- a/netbox/tenancy/forms.py +++ b/netbox/tenancy/forms.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from django import forms from django.db.models import Count +from taggit.forms import TagField from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm from utilities.forms import ( @@ -40,10 +41,11 @@ class TenantGroupCSVForm(forms.ModelForm): class TenantForm(BootstrapMixin, CustomFieldForm): slug = SlugField() comments = CommentField() + tags = TagField(required=False) class Meta: model = Tenant - fields = ['name', 'slug', 'group', 'description', 'comments'] + fields = ['name', 'slug', 'group', 'description', 'comments', 'tags'] class TenantCSVForm(forms.ModelForm): diff --git a/netbox/tenancy/models.py b/netbox/tenancy/models.py index 9df714680..f006e512d 100644 --- a/netbox/tenancy/models.py +++ b/netbox/tenancy/models.py @@ -4,6 +4,7 @@ from django.contrib.contenttypes.fields import GenericRelation from django.db import models from django.urls import reverse from django.utils.encoding import python_2_unicode_compatible +from taggit.managers import TaggableManager from extras.models import CustomFieldModel from utilities.models import CreatedUpdatedModel @@ -74,6 +75,8 @@ class Tenant(CreatedUpdatedModel, CustomFieldModel): object_id_field='obj_id' ) + tags = TaggableManager() + csv_headers = ['name', 'slug', 'group', 'description', 'comments'] class Meta: diff --git a/netbox/virtualization/api/serializers.py b/netbox/virtualization/api/serializers.py index 8cee708ba..15ed39abf 100644 --- a/netbox/virtualization/api/serializers.py +++ b/netbox/virtualization/api/serializers.py @@ -1,14 +1,15 @@ from __future__ import unicode_literals from rest_framework import serializers +from taggit.models import Tag from dcim.api.serializers import NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer -from dcim.constants import IFACE_FF_VIRTUAL, IFACE_MODE_CHOICES +from dcim.constants import IFACE_MODE_CHOICES from dcim.models import Interface from extras.api.customfields import CustomFieldModelSerializer from ipam.models import IPAddress, VLAN from tenancy.api.serializers import NestedTenantSerializer -from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer, WritableNestedSerializer +from utilities.api import ChoiceFieldSerializer, TagField, ValidatedModelSerializer, WritableNestedSerializer from virtualization.constants import VM_STATUS_CHOICES from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine @@ -59,10 +60,13 @@ class ClusterSerializer(CustomFieldModelSerializer): type = NestedClusterTypeSerializer() group = NestedClusterGroupSerializer(required=False, allow_null=True) site = NestedSiteSerializer(required=False, allow_null=True) + tags = TagField(queryset=Tag.objects.all(), required=False, many=True) class Meta: model = Cluster - fields = ['id', 'name', 'type', 'group', 'site', 'comments', 'custom_fields', 'created', 'last_updated'] + fields = [ + 'id', 'name', 'type', 'group', 'site', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', + ] class NestedClusterSerializer(WritableNestedSerializer): @@ -95,12 +99,13 @@ class VirtualMachineSerializer(CustomFieldModelSerializer): primary_ip = VirtualMachineIPAddressSerializer(read_only=True) primary_ip4 = VirtualMachineIPAddressSerializer(required=False, allow_null=True) primary_ip6 = VirtualMachineIPAddressSerializer(required=False, allow_null=True) + tags = TagField(queryset=Tag.objects.all(), required=False, many=True) class Meta: model = VirtualMachine fields = [ 'id', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'primary_ip', 'primary_ip4', 'primary_ip6', - 'vcpus', 'memory', 'disk', 'comments', 'custom_fields', 'created', 'last_updated', + 'vcpus', 'memory', 'disk', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', ] diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 4dfea1b42..b973ed5cb 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -4,6 +4,7 @@ from django import forms from django.core.exceptions import ValidationError from django.db.models import Count from mptt.forms import TreeNodeChoiceField +from taggit.forms import TagField from dcim.constants import IFACE_FF_VIRTUAL, IFACE_MODE_ACCESS, IFACE_MODE_TAGGED_ALL from dcim.forms import INTERFACE_MODE_HELP_TEXT @@ -78,10 +79,11 @@ class ClusterGroupCSVForm(forms.ModelForm): class ClusterForm(BootstrapMixin, CustomFieldForm): comments = CommentField(widget=SmallTextarea) + tags = TagField(required=False) class Meta: model = Cluster - fields = ['name', 'type', 'group', 'site', 'comments'] + fields = ['name', 'type', 'group', 'site', 'comments', 'tags'] class ClusterCSVForm(forms.ModelForm): @@ -244,12 +246,13 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm): api_url='/api/virtualization/clusters/?group_id={{cluster_group}}' ) ) + tags = TagField(required=False) class Meta: model = VirtualMachine fields = [ 'name', 'status', 'cluster_group', 'cluster', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', - 'vcpus', 'memory', 'disk', 'comments', + 'vcpus', 'memory', 'disk', 'comments', 'tags', ] def __init__(self, *args, **kwargs): diff --git a/netbox/virtualization/models.py b/netbox/virtualization/models.py index b58cf93e8..e34512410 100644 --- a/netbox/virtualization/models.py +++ b/netbox/virtualization/models.py @@ -6,6 +6,7 @@ from django.core.exceptions import ValidationError from django.db import models from django.urls import reverse from django.utils.encoding import python_2_unicode_compatible +from taggit.managers import TaggableManager from dcim.models import Device from extras.models import CustomFieldModel @@ -124,6 +125,8 @@ class Cluster(CreatedUpdatedModel, CustomFieldModel): object_id_field='obj_id' ) + tags = TaggableManager() + csv_headers = ['name', 'type', 'group', 'site', 'comments'] class Meta: @@ -242,6 +245,8 @@ class VirtualMachine(CreatedUpdatedModel, CustomFieldModel): object_id_field='obj_id' ) + tags = TaggableManager() + csv_headers = [ 'name', 'status', 'role', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments', ] diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index 6de6b86c7..96c57c29b 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -126,6 +126,7 @@ class ClusterView(View): class ClusterCreateView(PermissionRequiredMixin, ObjectEditView): permission_required = 'virtualization.add_cluster' + template_name = 'virtualization/cluster_edit.html' model = Cluster model_form = forms.ClusterForm