1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Implemented tags for all primary models

This commit is contained in:
Jeremy Stretch
2018-05-10 12:53:11 -04:00
parent b0dafcf50f
commit 9b3869790d
43 changed files with 262 additions and 34 deletions

View File

@ -1,13 +1,14 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from rest_framework import serializers from rest_framework import serializers
from taggit.models import Tag
from circuits.constants import CIRCUIT_STATUS_CHOICES from circuits.constants import CIRCUIT_STATUS_CHOICES
from circuits.models import Provider, Circuit, CircuitTermination, CircuitType from circuits.models import Provider, Circuit, CircuitTermination, CircuitType
from dcim.api.serializers import NestedSiteSerializer, InterfaceSerializer from dcim.api.serializers import NestedSiteSerializer, InterfaceSerializer
from extras.api.customfields import CustomFieldModelSerializer from extras.api.customfields import CustomFieldModelSerializer
from tenancy.api.serializers import NestedTenantSerializer 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): class ProviderSerializer(CustomFieldModelSerializer):
tags = TagField(queryset=Tag.objects.all(), required=False, many=True)
class Meta: class Meta:
model = Provider model = Provider
fields = [ 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', 'custom_fields', 'created', 'last_updated',
] ]
@ -60,12 +62,13 @@ class CircuitSerializer(CustomFieldModelSerializer):
status = ChoiceFieldSerializer(choices=CIRCUIT_STATUS_CHOICES, required=False) status = ChoiceFieldSerializer(choices=CIRCUIT_STATUS_CHOICES, required=False)
type = NestedCircuitTypeSerializer() type = NestedCircuitTypeSerializer()
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True)
tags = TagField(queryset=Tag.objects.all(), required=False, many=True)
class Meta: class Meta:
model = Circuit model = Circuit
fields = [ fields = [
'id', 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'id', 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description',
'comments', 'custom_fields', 'created', 'last_updated', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
] ]

View File

@ -2,6 +2,7 @@ from __future__ import unicode_literals
from django import forms from django import forms
from django.db.models import Count from django.db.models import Count
from taggit.forms import TagField
from dcim.models import Site, Device, Interface, Rack from dcim.models import Site, Device, Interface, Rack
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
@ -22,10 +23,11 @@ from .models import Circuit, CircuitTermination, CircuitType, Provider
class ProviderForm(BootstrapMixin, CustomFieldForm): class ProviderForm(BootstrapMixin, CustomFieldForm):
slug = SlugField() slug = SlugField()
comments = CommentField() comments = CommentField()
tags = TagField(required=False)
class Meta: class Meta:
model = Provider 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 = { widgets = {
'noc_contact': SmallTextarea(attrs={'rows': 5}), 'noc_contact': SmallTextarea(attrs={'rows': 5}),
'admin_contact': SmallTextarea(attrs={'rows': 5}), 'admin_contact': SmallTextarea(attrs={'rows': 5}),
@ -102,12 +104,13 @@ class CircuitTypeCSVForm(forms.ModelForm):
class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldForm): class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldForm):
comments = CommentField() comments = CommentField()
tags = TagField(required=False)
class Meta: class Meta:
model = Circuit model = Circuit
fields = [ fields = [
'cid', 'type', 'provider', 'status', 'install_date', 'commit_rate', 'description', 'tenant_group', 'tenant', 'cid', 'type', 'provider', 'status', 'install_date', 'commit_rate', 'description', 'tenant_group', 'tenant',
'comments', 'comments', 'tags',
] ]
help_texts = { help_texts = {
'cid': "Unique circuit ID", 'cid': "Unique circuit ID",

View File

@ -4,6 +4,7 @@ from django.contrib.contenttypes.fields import GenericRelation
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
from taggit.managers import TaggableManager
from dcim.constants import STATUS_CLASSES from dcim.constants import STATUS_CLASSES
from dcim.fields import ASNField from dcim.fields import ASNField
@ -56,6 +57,8 @@ class Provider(CreatedUpdatedModel, CustomFieldModel):
object_id_field='obj_id' object_id_field='obj_id'
) )
tags = TaggableManager()
csv_headers = ['name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments'] csv_headers = ['name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments']
class Meta: class Meta:
@ -166,6 +169,8 @@ class Circuit(CreatedUpdatedModel, CustomFieldModel):
object_id_field='obj_id' object_id_field='obj_id'
) )
tags = TaggableManager()
csv_headers = [ csv_headers = [
'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments', 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments',
] ]

View File

@ -784,8 +784,8 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm):
class Meta: class Meta:
model = Device model = Device
fields = [ fields = [
'name', 'device_role', 'tags', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'position', 'face', 'name', 'device_role', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'position', 'face',
'status', 'platform', 'primary_ip4', 'primary_ip6', 'tenant_group', 'tenant', 'comments', 'status', 'platform', 'primary_ip4', 'primary_ip6', 'tenant_group', 'tenant', 'comments', 'tags',
] ]
help_texts = { help_texts = {
'device_role': "The function this device serves", 'device_role': "The function this device serves",

View File

@ -5,6 +5,7 @@ from collections import OrderedDict
from rest_framework import serializers from rest_framework import serializers
from rest_framework.reverse import reverse from rest_framework.reverse import reverse
from rest_framework.validators import UniqueTogetherValidator from rest_framework.validators import UniqueTogetherValidator
from taggit.models import Tag
from dcim.api.serializers import NestedDeviceSerializer, InterfaceSerializer, NestedSiteSerializer from dcim.api.serializers import NestedDeviceSerializer, InterfaceSerializer, NestedSiteSerializer
from dcim.models import Interface 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 ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
from tenancy.api.serializers import NestedTenantSerializer 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 from virtualization.api.serializers import NestedVirtualMachineSerializer
@ -24,12 +27,13 @@ from virtualization.api.serializers import NestedVirtualMachineSerializer
class VRFSerializer(CustomFieldModelSerializer): class VRFSerializer(CustomFieldModelSerializer):
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True)
tags = TagField(queryset=Tag.objects.all(), required=False, many=True)
class Meta: class Meta:
model = VRF model = VRF
fields = [ fields = [
'id', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'display_name', 'custom_fields', 'created', 'id', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'tags', 'display_name', 'custom_fields',
'last_updated', 'created', 'last_updated',
] ]
@ -85,11 +89,13 @@ class NestedRIRSerializer(WritableNestedSerializer):
class AggregateSerializer(CustomFieldModelSerializer): class AggregateSerializer(CustomFieldModelSerializer):
rir = NestedRIRSerializer() rir = NestedRIRSerializer()
tags = TagField(queryset=Tag.objects.all(), required=False, many=True)
class Meta: class Meta:
model = Aggregate model = Aggregate
fields = [ 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'] read_only_fields = ['family']
@ -147,11 +153,12 @@ class VLANSerializer(CustomFieldModelSerializer):
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True)
status = ChoiceFieldSerializer(choices=VLAN_STATUS_CHOICES, required=False) status = ChoiceFieldSerializer(choices=VLAN_STATUS_CHOICES, required=False)
role = NestedRoleSerializer(required=False, allow_null=True) role = NestedRoleSerializer(required=False, allow_null=True)
tags = TagField(queryset=Tag.objects.all(), required=False, many=True)
class Meta: class Meta:
model = VLAN model = VLAN
fields = [ 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', 'custom_fields', 'created', 'last_updated',
] ]
validators = [] validators = []
@ -190,12 +197,13 @@ class PrefixSerializer(CustomFieldModelSerializer):
vlan = NestedVLANSerializer(required=False, allow_null=True) vlan = NestedVLANSerializer(required=False, allow_null=True)
status = ChoiceFieldSerializer(choices=PREFIX_STATUS_CHOICES, required=False) status = ChoiceFieldSerializer(choices=PREFIX_STATUS_CHOICES, required=False)
role = NestedRoleSerializer(required=False, allow_null=True) role = NestedRoleSerializer(required=False, allow_null=True)
tags = TagField(queryset=Tag.objects.all(), required=False, many=True)
class Meta: class Meta:
model = Prefix model = Prefix
fields = [ fields = [
'id', 'family', 'prefix', 'site', 'vrf', 'tenant', 'vlan', 'status', 'role', 'is_pool', 'description', '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'] read_only_fields = ['family']
@ -252,12 +260,13 @@ class IPAddressSerializer(CustomFieldModelSerializer):
status = ChoiceFieldSerializer(choices=IPADDRESS_STATUS_CHOICES, required=False) status = ChoiceFieldSerializer(choices=IPADDRESS_STATUS_CHOICES, required=False)
role = ChoiceFieldSerializer(choices=IPADDRESS_ROLE_CHOICES, required=False) role = ChoiceFieldSerializer(choices=IPADDRESS_ROLE_CHOICES, required=False)
interface = IPAddressInterfaceSerializer(required=False, allow_null=True) interface = IPAddressInterfaceSerializer(required=False, allow_null=True)
tags = TagField(queryset=Tag.objects.all(), required=False, many=True)
class Meta: class Meta:
model = IPAddress model = IPAddress
fields = [ fields = [
'id', 'family', 'address', 'vrf', 'tenant', 'status', 'role', 'interface', 'description', 'nat_inside', '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'] read_only_fields = ['family']

View File

@ -3,6 +3,7 @@ from __future__ import unicode_literals
from django import forms from django import forms
from django.core.exceptions import MultipleObjectsReturned from django.core.exceptions import MultipleObjectsReturned
from django.db.models import Count from django.db.models import Count
from taggit.forms import TagField
from dcim.models import Site, Rack, Device, Interface from dcim.models import Site, Rack, Device, Interface
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm 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): class VRFForm(BootstrapMixin, TenancyForm, CustomFieldForm):
tags = TagField(required=False)
class Meta: class Meta:
model = VRF model = VRF
fields = ['name', 'rd', 'enforce_unique', 'description', 'tenant_group', 'tenant'] fields = ['name', 'rd', 'enforce_unique', 'description', 'tenant_group', 'tenant', 'tags']
labels = { labels = {
'rd': "RD", 'rd': "RD",
} }
@ -121,10 +123,11 @@ class RIRFilterForm(BootstrapMixin, forms.Form):
# #
class AggregateForm(BootstrapMixin, CustomFieldForm): class AggregateForm(BootstrapMixin, CustomFieldForm):
tags = TagField(required=False)
class Meta: class Meta:
model = Aggregate model = Aggregate
fields = ['prefix', 'rir', 'date_added', 'description'] fields = ['prefix', 'rir', 'date_added', 'description', 'tags']
help_texts = { help_texts = {
'prefix': "IPv4 or IPv6 network", 'prefix': "IPv4 or IPv6 network",
'rir': "Regional Internet Registry responsible for this prefix", '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' api_url='/api/ipam/vlans/?site_id={{site}}&group_id={{vlan_group}}', display_field='display_name'
) )
) )
tags = TagField(required=False)
class Meta: class Meta:
model = Prefix 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): 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') primary_for_parent = forms.BooleanField(required=False, label='Make this the primary IP for the device/VM')
tags = TagField(required=False)
class Meta: class Meta:
model = IPAddress model = IPAddress
fields = [ fields = [
'address', 'vrf', 'status', 'role', 'description', 'interface', 'primary_for_parent', 'nat_site', '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): def __init__(self, *args, **kwargs):
@ -780,10 +788,11 @@ class VLANForm(BootstrapMixin, TenancyForm, CustomFieldForm):
api_url='/api/ipam/vlan-groups/?site_id={{site}}', api_url='/api/ipam/vlan-groups/?site_id={{site}}',
) )
) )
tags = TagField(required=False)
class Meta: class Meta:
model = VLAN 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 = { help_texts = {
'site': "Leave blank if this VLAN spans multiple sites", 'site': "Leave blank if this VLAN spans multiple sites",
'group': "VLAN group (optional)", 'group': "VLAN group (optional)",

View File

@ -10,6 +10,7 @@ from django.db.models import Q
from django.db.models.expressions import RawSQL from django.db.models.expressions import RawSQL
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
from taggit.managers import TaggableManager
from dcim.models import Interface from dcim.models import Interface
from extras.models import CustomFieldModel from extras.models import CustomFieldModel
@ -56,6 +57,8 @@ class VRF(CreatedUpdatedModel, CustomFieldModel):
object_id_field='obj_id' object_id_field='obj_id'
) )
tags = TaggableManager()
csv_headers = ['name', 'rd', 'tenant', 'enforce_unique', 'description'] csv_headers = ['name', 'rd', 'tenant', 'enforce_unique', 'description']
class Meta: class Meta:
@ -155,6 +158,8 @@ class Aggregate(CreatedUpdatedModel, CustomFieldModel):
object_id_field='obj_id' object_id_field='obj_id'
) )
tags = TaggableManager()
csv_headers = ['prefix', 'rir', 'date_added', 'description'] csv_headers = ['prefix', 'rir', 'date_added', 'description']
class Meta: class Meta:
@ -325,6 +330,7 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
) )
objects = PrefixQuerySet.as_manager() objects = PrefixQuerySet.as_manager()
tags = TaggableManager()
csv_headers = [ csv_headers = [
'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan_vid', 'status', 'role', 'is_pool', 'description', 'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan_vid', 'status', 'role', 'is_pool', 'description',
@ -564,6 +570,7 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel):
) )
objects = IPAddressManager() objects = IPAddressManager()
tags = TaggableManager()
csv_headers = [ csv_headers = [
'address', 'vrf', 'tenant', 'status', 'role', 'device', 'virtual_machine', 'interface_name', 'is_primary', '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' object_id_field='obj_id'
) )
tags = TaggableManager()
csv_headers = ['site', 'group_name', 'vid', 'name', 'tenant', 'status', 'role', 'description'] csv_headers = ['site', 'group_name', 'vid', 'name', 'tenant', 'status', 'role', 'description']
class Meta: class Meta:

View File

@ -2,10 +2,11 @@ from __future__ import unicode_literals
from rest_framework import serializers from rest_framework import serializers
from rest_framework.validators import UniqueTogetherValidator from rest_framework.validators import UniqueTogetherValidator
from taggit.models import Tag
from dcim.api.serializers import NestedDeviceSerializer from dcim.api.serializers import NestedDeviceSerializer
from secrets.models import Secret, SecretRole 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() device = NestedDeviceSerializer()
role = NestedSecretRoleSerializer() role = NestedSecretRoleSerializer()
plaintext = serializers.CharField() plaintext = serializers.CharField()
tags = TagField(queryset=Tag.objects.all(), required=False, many=True)
class Meta: class Meta:
model = Secret model = Secret
fields = ['id', 'device', 'role', 'name', 'plaintext', 'hash', 'created', 'last_updated'] fields = ['id', 'device', 'role', 'name', 'plaintext', 'hash', 'tags', 'created', 'last_updated']
validators = [] validators = []
def validate(self, data): def validate(self, data):

View File

@ -4,6 +4,7 @@ from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
from django import forms from django import forms
from django.db.models import Count from django.db.models import Count
from taggit.forms import TagField
from dcim.models import Device from dcim.models import Device
from utilities.forms import BootstrapMixin, BulkEditForm, FilterChoiceField, FlexibleModelChoiceField, SlugField from utilities.forms import BootstrapMixin, BulkEditForm, FilterChoiceField, FlexibleModelChoiceField, SlugField
@ -70,10 +71,11 @@ class SecretForm(BootstrapMixin, forms.ModelForm):
label='Plaintext (verify)', label='Plaintext (verify)',
widget=forms.PasswordInput() widget=forms.PasswordInput()
) )
tags = TagField(required=False)
class Meta: class Meta:
model = Secret model = Secret
fields = ['role', 'name', 'plaintext', 'plaintext2'] fields = ['role', 'name', 'plaintext', 'plaintext2', 'tags']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@ -12,6 +12,7 @@ from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import force_bytes, python_2_unicode_compatible from django.utils.encoding import force_bytes, python_2_unicode_compatible
from taggit.managers import TaggableManager
from utilities.models import CreatedUpdatedModel from utilities.models import CreatedUpdatedModel
from .exceptions import InvalidKey from .exceptions import InvalidKey
@ -336,6 +337,8 @@ class Secret(CreatedUpdatedModel):
editable=False editable=False
) )
tags = TaggableManager()
plaintext = None plaintext = None
csv_headers = ['device', 'role', 'name', 'plaintext'] csv_headers = ['device', 'role', 'name', 'plaintext']

View File

@ -110,6 +110,10 @@
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
<tr>
<td>Tags</td>
<td>{{ circuit.tags.all|join:" " }}</td>
</tr>
</table> </table>
</div> </div>
{% with circuit.get_custom_fields as custom_fields %} {% with circuit.get_custom_fields as custom_fields %}

View File

@ -44,6 +44,12 @@
{% render_field form.comments %} {% render_field form.comments %}
</div> </div>
</div> </div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Tags</strong></div>
<div class="panel-body">
{% render_field form.tags %}
</div>
</div>
{% endblock %} {% endblock %}
{% block javascript %} {% block javascript %}

View File

@ -102,6 +102,10 @@
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
<tr>
<td>Tags</td>
<td>{{ provider.tags.all|join:" " }}</td>
</tr>
<tr> <tr>
<td>Circuits</td> <td>Circuits</td>
<td> <td>

View File

@ -33,4 +33,10 @@
{% render_field form.comments %} {% render_field form.comments %}
</div> </div>
</div> </div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Tags</strong></div>
<div class="panel-body">
{% render_field form.tags %}
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -84,4 +84,10 @@
{% render_field form.comments %} {% render_field form.comments %}
</div> </div>
</div> </div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Tags</strong></div>
<div class="panel-body">
{% render_field form.tags %}
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -12,7 +12,6 @@
{% render_field form.u_height %} {% render_field form.u_height %}
{% render_field form.is_full_depth %} {% render_field form.is_full_depth %}
{% render_field form.interface_ordering %} {% render_field form.interface_ordering %}
{% render_field form.tags %}
</div> </div>
</div> </div>
<div class="panel panel-default"> <div class="panel panel-default">
@ -38,4 +37,10 @@
{% render_field form.comments %} {% render_field form.comments %}
</div> </div>
</div> </div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Tags</strong></div>
<div class="panel-body">
{% render_field form.tags %}
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -11,7 +11,6 @@
{% render_field form.group %} {% render_field form.group %}
{% render_field form.role %} {% render_field form.role %}
{% render_field form.serial %} {% render_field form.serial %}
{% render_field form.tags %}
</div> </div>
</div> </div>
<div class="panel panel-default"> <div class="panel panel-default">
@ -44,4 +43,10 @@
{% render_field form.comments %} {% render_field form.comments %}
</div> </div>
</div> </div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Tags</strong></div>
<div class="panel-body">
{% render_field form.tags %}
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -13,7 +13,6 @@
{% render_field form.asn %} {% render_field form.asn %}
{% render_field form.time_zone %} {% render_field form.time_zone %}
{% render_field form.description %} {% render_field form.description %}
{% render_field form.tags %}
</div> </div>
</div> </div>
<div class="panel panel-default"> <div class="panel panel-default">
@ -47,4 +46,10 @@
{% render_field form.comments %} {% render_field form.comments %}
</div> </div>
</div> </div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Tags</strong></div>
<div class="panel-body">
{% render_field form.tags %}
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -81,6 +81,10 @@
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
<tr>
<td>Tags</td>
<td>{{ aggregate.tags.all|join:" " }}</td>
</tr>
</table> </table>
</div> </div>
</div> </div>

View File

@ -19,4 +19,10 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
<div class="panel panel-default">
<div class="panel-heading"><strong>Tags</strong></div>
<div class="panel-body">
{% render_field form.tags %}
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -133,6 +133,10 @@
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
<tr>
<td>Tags</td>
<td>{{ ipaddress.tags.all|join:" " }}</td>
</tr>
</table> </table>
</div> </div>
{% with ipaddress.get_custom_fields as custom_fields %} {% with ipaddress.get_custom_fields as custom_fields %}

View File

@ -66,6 +66,12 @@
{% render_field form.nat_inside %} {% render_field form.nat_inside %}
</div> </div>
</div> </div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Tags</strong></div>
<div class="panel-body">
{% render_field form.tags %}
</div>
</div>
{% if form.custom_fields %} {% if form.custom_fields %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><strong>Custom Fields</strong></div> <div class="panel-heading"><strong>Custom Fields</strong></div>

View File

@ -121,6 +121,10 @@
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
<tr>
<td>Tags</td>
<td>{{ prefix.tags.all|join:" " }}</td>
</tr>
<tr> <tr>
<td>Utilization</td> <td>Utilization</td>
<td>{% utilization_graph prefix.get_utilization %}</td> <td>{% utilization_graph prefix.get_utilization %}</td>

View File

@ -28,6 +28,12 @@
{% render_field form.tenant %} {% render_field form.tenant %}
</div> </div>
</div> </div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Tags</strong></div>
<div class="panel-body">
{% render_field form.tags %}
</div>
</div>
{% if form.custom_fields %} {% if form.custom_fields %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><strong>Custom Fields</strong></div> <div class="panel-heading"><strong>Custom Fields</strong></div>

View File

@ -80,6 +80,10 @@
<span class="text-muted">N/A</span> <span class="text-muted">N/A</span>
{% endif %} {% endif %}
</td> </td>
</tr>
<tr>
<td>Tags</td>
<td>{{ vlan.tags.all|join:" " }}</td>
</tr> </tr>
</table> </table>
</div> </div>

View File

@ -21,6 +21,12 @@
{% render_field form.tenant %} {% render_field form.tenant %}
</div> </div>
</div> </div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Tags</strong></div>
<div class="panel-body">
{% render_field form.tags %}
</div>
</div>
{% if form.custom_fields %} {% if form.custom_fields %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><strong>Custom Fields</strong></div> <div class="panel-heading"><strong>Custom Fields</strong></div>

View File

@ -77,6 +77,10 @@
<span class="text-muted">N/A</span> <span class="text-muted">N/A</span>
{% endif %} {% endif %}
</td> </td>
</tr>
<tr>
<td>Tags</td>
<td>{{ vrf.tags.all|join:" " }}</td>
</tr> </tr>
</table> </table>
</div> </div>

View File

@ -18,6 +18,12 @@
{% render_field form.tenant %} {% render_field form.tenant %}
</div> </div>
</div> </div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Tags</strong></div>
<div class="panel-body">
{% render_field form.tags %}
</div>
</div>
{% if form.custom_fields %} {% if form.custom_fields %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><strong>Custom Fields</strong></div> <div class="panel-heading"><strong>Custom Fields</strong></div>

View File

@ -55,6 +55,10 @@
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
<tr>
<td>Tags</td>
<td>{{ secret.tags.all|join:" " }}</td>
</tr>
</table> </table>
</div> </div>
</div> </div>

View File

@ -54,6 +54,12 @@
{% render_field form.plaintext2 %} {% render_field form.plaintext2 %}
</div> </div>
</div> </div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Tags</strong></div>
<div class="panel-body">
{% render_field form.tags %}
</div>
</div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">

View File

@ -68,6 +68,10 @@
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
<tr>
<td>Tags</td>
<td>{{ tenant.tags.all|join:" " }}</td>
</tr>
</table> </table>
</div> </div>
{% with tenant.get_custom_fields as custom_fields %} {% with tenant.get_custom_fields as custom_fields %}

View File

@ -26,4 +26,10 @@
{% render_field form.comments %} {% render_field form.comments %}
</div> </div>
</div> </div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Tags</strong></div>
<div class="panel-body">
{% render_field form.tags %}
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -76,6 +76,10 @@
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
<tr>
<td>Tags</td>
<td>{{ cluster.tags.all|join:" " }}</td>
</tr>
<tr> <tr>
<td>Virtual Machines</td> <td>Virtual Machines</td>
<td><a href="{% url 'virtualization:virtualmachine_list' %}?cluster_id={{ cluster.pk }}">{{ cluster.virtual_machines.count }}</a></td> <td><a href="{% url 'virtualization:virtualmachine_list' %}?cluster_id={{ cluster.pk }}">{{ cluster.virtual_machines.count }}</a></td>

View File

@ -0,0 +1,34 @@
{% extends 'utilities/obj_edit.html' %}
{% load form_helpers %}
{% block form %}
<div class="panel panel-default">
<div class="panel-heading"><strong>Cluster</strong></div>
<div class="panel-body">
{% render_field form.name %}
{% render_field form.type %}
{% render_field form.group %}
{% render_field form.site %}
</div>
</div>
{% if form.custom_fields %}
<div class="panel panel-default">
<div class="panel-heading"><strong>Custom Fields</strong></div>
<div class="panel-body">
{% render_custom_fields form %}
</div>
</div>
{% endif %}
<div class="panel panel-default">
<div class="panel-heading"><strong>Comments</strong></div>
<div class="panel-body">
{% render_field form.comments %}
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Tags</strong></div>
<div class="panel-body">
{% render_field form.tags %}
</div>
</div>
{% endblock %}

View File

@ -121,6 +121,10 @@
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
<tr>
<td>Tags</td>
<td>{{ vm.tags.all|join:" " }}</td>
</tr>
</table> </table>
</div> </div>
{% include 'inc/custom_fields_panel.html' with custom_fields=vm.get_custom_fields %} {% include 'inc/custom_fields_panel.html' with custom_fields=vm.get_custom_fields %}

View File

@ -54,4 +54,10 @@
{% render_field form.comments %} {% render_field form.comments %}
</div> </div>
</div> </div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Tags</strong></div>
<div class="panel-body">
{% render_field form.tags %}
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -1,10 +1,11 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from rest_framework import serializers from rest_framework import serializers
from taggit.models import Tag
from extras.api.customfields import CustomFieldModelSerializer from extras.api.customfields import CustomFieldModelSerializer
from tenancy.models import Tenant, TenantGroup 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): class TenantSerializer(CustomFieldModelSerializer):
group = NestedTenantGroupSerializer(required=False) group = NestedTenantGroupSerializer(required=False)
tags = TagField(queryset=Tag.objects.all(), required=False, many=True)
class Meta: class Meta:
model = Tenant 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): class NestedTenantSerializer(WritableNestedSerializer):

View File

@ -2,6 +2,7 @@ from __future__ import unicode_literals
from django import forms from django import forms
from django.db.models import Count from django.db.models import Count
from taggit.forms import TagField
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
from utilities.forms import ( from utilities.forms import (
@ -40,10 +41,11 @@ class TenantGroupCSVForm(forms.ModelForm):
class TenantForm(BootstrapMixin, CustomFieldForm): class TenantForm(BootstrapMixin, CustomFieldForm):
slug = SlugField() slug = SlugField()
comments = CommentField() comments = CommentField()
tags = TagField(required=False)
class Meta: class Meta:
model = Tenant model = Tenant
fields = ['name', 'slug', 'group', 'description', 'comments'] fields = ['name', 'slug', 'group', 'description', 'comments', 'tags']
class TenantCSVForm(forms.ModelForm): class TenantCSVForm(forms.ModelForm):

View File

@ -4,6 +4,7 @@ from django.contrib.contenttypes.fields import GenericRelation
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
from taggit.managers import TaggableManager
from extras.models import CustomFieldModel from extras.models import CustomFieldModel
from utilities.models import CreatedUpdatedModel from utilities.models import CreatedUpdatedModel
@ -74,6 +75,8 @@ class Tenant(CreatedUpdatedModel, CustomFieldModel):
object_id_field='obj_id' object_id_field='obj_id'
) )
tags = TaggableManager()
csv_headers = ['name', 'slug', 'group', 'description', 'comments'] csv_headers = ['name', 'slug', 'group', 'description', 'comments']
class Meta: class Meta:

View File

@ -1,14 +1,15 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from rest_framework import serializers from rest_framework import serializers
from taggit.models import Tag
from dcim.api.serializers import NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer 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 dcim.models import Interface
from extras.api.customfields import CustomFieldModelSerializer from extras.api.customfields import CustomFieldModelSerializer
from ipam.models import IPAddress, VLAN from ipam.models import IPAddress, VLAN
from tenancy.api.serializers import NestedTenantSerializer 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.constants import VM_STATUS_CHOICES
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
@ -59,10 +60,13 @@ class ClusterSerializer(CustomFieldModelSerializer):
type = NestedClusterTypeSerializer() type = NestedClusterTypeSerializer()
group = NestedClusterGroupSerializer(required=False, allow_null=True) group = NestedClusterGroupSerializer(required=False, allow_null=True)
site = NestedSiteSerializer(required=False, allow_null=True) site = NestedSiteSerializer(required=False, allow_null=True)
tags = TagField(queryset=Tag.objects.all(), required=False, many=True)
class Meta: class Meta:
model = Cluster 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): class NestedClusterSerializer(WritableNestedSerializer):
@ -95,12 +99,13 @@ class VirtualMachineSerializer(CustomFieldModelSerializer):
primary_ip = VirtualMachineIPAddressSerializer(read_only=True) primary_ip = VirtualMachineIPAddressSerializer(read_only=True)
primary_ip4 = VirtualMachineIPAddressSerializer(required=False, allow_null=True) primary_ip4 = VirtualMachineIPAddressSerializer(required=False, allow_null=True)
primary_ip6 = 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: class Meta:
model = VirtualMachine model = VirtualMachine
fields = [ fields = [
'id', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'primary_ip', 'primary_ip4', 'primary_ip6', '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',
] ]

View File

@ -4,6 +4,7 @@ from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db.models import Count from django.db.models import Count
from mptt.forms import TreeNodeChoiceField 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.constants import IFACE_FF_VIRTUAL, IFACE_MODE_ACCESS, IFACE_MODE_TAGGED_ALL
from dcim.forms import INTERFACE_MODE_HELP_TEXT from dcim.forms import INTERFACE_MODE_HELP_TEXT
@ -78,10 +79,11 @@ class ClusterGroupCSVForm(forms.ModelForm):
class ClusterForm(BootstrapMixin, CustomFieldForm): class ClusterForm(BootstrapMixin, CustomFieldForm):
comments = CommentField(widget=SmallTextarea) comments = CommentField(widget=SmallTextarea)
tags = TagField(required=False)
class Meta: class Meta:
model = Cluster model = Cluster
fields = ['name', 'type', 'group', 'site', 'comments'] fields = ['name', 'type', 'group', 'site', 'comments', 'tags']
class ClusterCSVForm(forms.ModelForm): class ClusterCSVForm(forms.ModelForm):
@ -244,12 +246,13 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
api_url='/api/virtualization/clusters/?group_id={{cluster_group}}' api_url='/api/virtualization/clusters/?group_id={{cluster_group}}'
) )
) )
tags = TagField(required=False)
class Meta: class Meta:
model = VirtualMachine model = VirtualMachine
fields = [ fields = [
'name', 'status', 'cluster_group', 'cluster', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', '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): def __init__(self, *args, **kwargs):

View File

@ -6,6 +6,7 @@ from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
from taggit.managers import TaggableManager
from dcim.models import Device from dcim.models import Device
from extras.models import CustomFieldModel from extras.models import CustomFieldModel
@ -124,6 +125,8 @@ class Cluster(CreatedUpdatedModel, CustomFieldModel):
object_id_field='obj_id' object_id_field='obj_id'
) )
tags = TaggableManager()
csv_headers = ['name', 'type', 'group', 'site', 'comments'] csv_headers = ['name', 'type', 'group', 'site', 'comments']
class Meta: class Meta:
@ -242,6 +245,8 @@ class VirtualMachine(CreatedUpdatedModel, CustomFieldModel):
object_id_field='obj_id' object_id_field='obj_id'
) )
tags = TaggableManager()
csv_headers = [ csv_headers = [
'name', 'status', 'role', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments', 'name', 'status', 'role', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',
] ]

View File

@ -126,6 +126,7 @@ class ClusterView(View):
class ClusterCreateView(PermissionRequiredMixin, ObjectEditView): class ClusterCreateView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'virtualization.add_cluster' permission_required = 'virtualization.add_cluster'
template_name = 'virtualization/cluster_edit.html'
model = Cluster model = Cluster
model_form = forms.ClusterForm model_form = forms.ClusterForm