From f1bc88fc0c779389e3b10cf15476c090233f3537 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 3 Aug 2018 09:43:03 -0400 Subject: [PATCH] Adopt django-taggit-serializer for representation of assigned tags in the API --- base_requirements.txt | 1 + netbox/circuits/api/serializers.py | 12 +++--- netbox/dcim/api/serializers.py | 52 ++++++++++++------------ netbox/ipam/api/serializers.py | 24 +++++------ netbox/netbox/settings.py | 1 + netbox/secrets/api/serializers.py | 8 ++-- netbox/tenancy/api/serializers.py | 8 ++-- netbox/utilities/api.py | 20 ++------- netbox/utilities/utils.py | 4 +- netbox/virtualization/api/serializers.py | 12 +++--- requirements.txt | 1 + 11 files changed, 66 insertions(+), 77 deletions(-) diff --git a/base_requirements.txt b/base_requirements.txt index 0e565c335..6012ffa6c 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -7,6 +7,7 @@ django-filter==1.1.0 django-mptt django-tables2 django-taggit +django-taggit-serializer django-timezone-field # https://github.com/encode/django-rest-framework/issues/6053 djangorestframework==3.8.1 diff --git a/netbox/circuits/api/serializers.py b/netbox/circuits/api/serializers.py index 4b2b7fbc3..739fbf8ff 100644 --- a/netbox/circuits/api/serializers.py +++ b/netbox/circuits/api/serializers.py @@ -1,22 +1,22 @@ from __future__ import unicode_literals from rest_framework import serializers -from taggit.models import Tag +from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField 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 ChoiceField, TagField, ValidatedModelSerializer, WritableNestedSerializer +from utilities.api import ChoiceField, ValidatedModelSerializer, WritableNestedSerializer # # Providers # -class ProviderSerializer(CustomFieldModelSerializer): - tags = TagField(queryset=Tag.objects.all(), required=False, many=True) +class ProviderSerializer(TaggitSerializer, CustomFieldModelSerializer): + tags = TagListSerializerField(required=False) class Meta: model = Provider @@ -57,12 +57,12 @@ class NestedCircuitTypeSerializer(WritableNestedSerializer): # Circuits # -class CircuitSerializer(CustomFieldModelSerializer): +class CircuitSerializer(TaggitSerializer, CustomFieldModelSerializer): provider = NestedProviderSerializer() status = ChoiceField(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) + tags = TagListSerializerField(required=False) class Meta: model = Circuit diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 2dc69a6f9..12981c6e3 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from rest_framework import serializers from rest_framework.validators import UniqueTogetherValidator -from taggit.models import Tag +from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField from circuits.models import Circuit, CircuitTermination from dcim.constants import ( @@ -20,7 +20,7 @@ from ipam.models import IPAddress, VLAN from tenancy.api.serializers import NestedTenantSerializer from users.api.serializers import NestedUserSerializer from utilities.api import ( - ChoiceField, SerializedPKRelatedField, TagField, TimeZoneField, ValidatedModelSerializer, + ChoiceField, SerializedPKRelatedField, TimeZoneField, ValidatedModelSerializer, WritableNestedSerializer, ) from virtualization.models import Cluster @@ -50,12 +50,12 @@ class RegionSerializer(serializers.ModelSerializer): # Sites # -class SiteSerializer(CustomFieldModelSerializer): +class SiteSerializer(TaggitSerializer, CustomFieldModelSerializer): status = ChoiceField(choices=SITE_STATUS_CHOICES, required=False) region = NestedRegionSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True) time_zone = TimeZoneField(required=False) - tags = TagField(queryset=Tag.objects.all(), required=False, many=True) + tags = TagListSerializerField(required=False) class Meta: model = Site @@ -118,14 +118,14 @@ class NestedRackRoleSerializer(WritableNestedSerializer): # Racks # -class RackSerializer(CustomFieldModelSerializer): +class RackSerializer(TaggitSerializer, CustomFieldModelSerializer): site = NestedSiteSerializer() group = NestedRackGroupSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True) role = NestedRackRoleSerializer(required=False, allow_null=True) type = ChoiceField(choices=RACK_TYPE_CHOICES, required=False) width = ChoiceField(choices=RACK_WIDTH_CHOICES, required=False) - tags = TagField(queryset=Tag.objects.all(), required=False, many=True) + tags = TagListSerializerField(required=False) class Meta: model = Rack @@ -220,12 +220,12 @@ class NestedManufacturerSerializer(WritableNestedSerializer): # Device types # -class DeviceTypeSerializer(CustomFieldModelSerializer): +class DeviceTypeSerializer(TaggitSerializer, CustomFieldModelSerializer): manufacturer = NestedManufacturerSerializer() interface_ordering = ChoiceField(choices=IFACE_ORDERING_CHOICES, required=False) subdevice_role = ChoiceField(choices=SUBDEVICE_ROLE_CHOICES, required=False) instance_count = serializers.IntegerField(source='instances.count', read_only=True) - tags = TagField(queryset=Tag.objects.all(), required=False, many=True) + tags = TagListSerializerField(required=False) class Meta: model = DeviceType @@ -389,7 +389,7 @@ class DeviceVirtualChassisSerializer(serializers.ModelSerializer): fields = ['id', 'url', 'master'] -class DeviceSerializer(CustomFieldModelSerializer): +class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer): device_type = NestedDeviceTypeSerializer() device_role = NestedDeviceRoleSerializer() tenant = NestedTenantSerializer(required=False, allow_null=True) @@ -404,7 +404,7 @@ class DeviceSerializer(CustomFieldModelSerializer): parent_device = serializers.SerializerMethodField() cluster = NestedClusterSerializer(required=False, allow_null=True) virtual_chassis = DeviceVirtualChassisSerializer(required=False, allow_null=True) - tags = TagField(queryset=Tag.objects.all(), required=False, many=True) + tags = TagListSerializerField(required=False) class Meta: model = Device @@ -459,9 +459,9 @@ class DeviceWithConfigContextSerializer(DeviceSerializer): # Console server ports # -class ConsoleServerPortSerializer(ValidatedModelSerializer): +class ConsoleServerPortSerializer(TaggitSerializer, ValidatedModelSerializer): device = NestedDeviceSerializer() - tags = TagField(queryset=Tag.objects.all(), required=False, many=True) + tags = TagListSerializerField(required=False) class Meta: model = ConsoleServerPort @@ -482,10 +482,10 @@ class NestedConsoleServerPortSerializer(WritableNestedSerializer): # Console ports # -class ConsolePortSerializer(ValidatedModelSerializer): +class ConsolePortSerializer(TaggitSerializer, ValidatedModelSerializer): device = NestedDeviceSerializer() cs_port = NestedConsoleServerPortSerializer(required=False, allow_null=True) - tags = TagField(queryset=Tag.objects.all(), required=False, many=True) + tags = TagListSerializerField(required=False) class Meta: model = ConsolePort @@ -496,9 +496,9 @@ class ConsolePortSerializer(ValidatedModelSerializer): # Power outlets # -class PowerOutletSerializer(ValidatedModelSerializer): +class PowerOutletSerializer(TaggitSerializer, ValidatedModelSerializer): device = NestedDeviceSerializer() - tags = TagField(queryset=Tag.objects.all(), required=False, many=True) + tags = TagListSerializerField(required=False) class Meta: model = PowerOutlet @@ -519,10 +519,10 @@ class NestedPowerOutletSerializer(WritableNestedSerializer): # Power ports # -class PowerPortSerializer(ValidatedModelSerializer): +class PowerPortSerializer(TaggitSerializer, ValidatedModelSerializer): device = NestedDeviceSerializer() power_outlet = NestedPowerOutletSerializer(required=False, allow_null=True) - tags = TagField(queryset=Tag.objects.all(), required=False, many=True) + tags = TagListSerializerField(required=False) class Meta: model = PowerPort @@ -569,7 +569,7 @@ class InterfaceVLANSerializer(WritableNestedSerializer): fields = ['id', 'url', 'vid', 'name', 'display_name'] -class InterfaceSerializer(ValidatedModelSerializer): +class InterfaceSerializer(TaggitSerializer, ValidatedModelSerializer): device = NestedDeviceSerializer() form_factor = ChoiceField(choices=IFACE_FF_CHOICES, required=False) lag = NestedInterfaceSerializer(required=False, allow_null=True) @@ -584,7 +584,7 @@ class InterfaceSerializer(ValidatedModelSerializer): required=False, many=True ) - tags = TagField(queryset=Tag.objects.all(), required=False, many=True) + tags = TagListSerializerField(required=False) class Meta: model = Interface @@ -640,10 +640,10 @@ class InterfaceSerializer(ValidatedModelSerializer): # Device bays # -class DeviceBaySerializer(ValidatedModelSerializer): +class DeviceBaySerializer(TaggitSerializer, ValidatedModelSerializer): device = NestedDeviceSerializer() installed_device = NestedDeviceSerializer(required=False, allow_null=True) - tags = TagField(queryset=Tag.objects.all(), required=False, many=True) + tags = TagListSerializerField(required=False) class Meta: model = DeviceBay @@ -662,12 +662,12 @@ class NestedDeviceBaySerializer(WritableNestedSerializer): # Inventory items # -class InventoryItemSerializer(ValidatedModelSerializer): +class InventoryItemSerializer(TaggitSerializer, ValidatedModelSerializer): device = NestedDeviceSerializer() # Provide a default value to satisfy UniqueTogetherValidator parent = serializers.PrimaryKeyRelatedField(queryset=InventoryItem.objects.all(), allow_null=True, default=None) manufacturer = NestedManufacturerSerializer() - tags = TagField(queryset=Tag.objects.all(), required=False, many=True) + tags = TagListSerializerField(required=False) class Meta: model = InventoryItem @@ -718,9 +718,9 @@ class ContextualInterfaceConnectionSerializer(serializers.ModelSerializer): # Virtual chassis # -class VirtualChassisSerializer(ValidatedModelSerializer): +class VirtualChassisSerializer(TaggitSerializer, ValidatedModelSerializer): master = NestedDeviceSerializer() - tags = TagField(queryset=Tag.objects.all(), required=False, many=True) + tags = TagListSerializerField(required=False) class Meta: model = VirtualChassis diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index 92526eb56..4d774706f 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -5,7 +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 taggit_serializer.serializers import TaggitSerializer, TagListSerializerField from dcim.api.serializers import NestedDeviceSerializer, InterfaceSerializer, NestedSiteSerializer from dcim.models import Interface @@ -16,7 +16,7 @@ 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 ( - ChoiceField, SerializedPKRelatedField, TagField, ValidatedModelSerializer, WritableNestedSerializer, + ChoiceField, SerializedPKRelatedField, ValidatedModelSerializer, WritableNestedSerializer, ) from virtualization.api.serializers import NestedVirtualMachineSerializer @@ -25,9 +25,9 @@ from virtualization.api.serializers import NestedVirtualMachineSerializer # VRFs # -class VRFSerializer(CustomFieldModelSerializer): +class VRFSerializer(TaggitSerializer, CustomFieldModelSerializer): tenant = NestedTenantSerializer(required=False, allow_null=True) - tags = TagField(queryset=Tag.objects.all(), required=False, many=True) + tags = TagListSerializerField(required=False) class Meta: model = VRF @@ -87,9 +87,9 @@ class NestedRIRSerializer(WritableNestedSerializer): # Aggregates # -class AggregateSerializer(CustomFieldModelSerializer): +class AggregateSerializer(TaggitSerializer, CustomFieldModelSerializer): rir = NestedRIRSerializer() - tags = TagField(queryset=Tag.objects.all(), required=False, many=True) + tags = TagListSerializerField(required=False) class Meta: model = Aggregate @@ -147,13 +147,13 @@ class NestedVLANGroupSerializer(WritableNestedSerializer): # VLANs # -class VLANSerializer(CustomFieldModelSerializer): +class VLANSerializer(TaggitSerializer, CustomFieldModelSerializer): site = NestedSiteSerializer(required=False, allow_null=True) group = NestedVLANGroupSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True) status = ChoiceField(choices=VLAN_STATUS_CHOICES, required=False) role = NestedRoleSerializer(required=False, allow_null=True) - tags = TagField(queryset=Tag.objects.all(), required=False, many=True) + tags = TagListSerializerField(required=False) class Meta: model = VLAN @@ -190,14 +190,14 @@ class NestedVLANSerializer(WritableNestedSerializer): # Prefixes # -class PrefixSerializer(CustomFieldModelSerializer): +class PrefixSerializer(TaggitSerializer, CustomFieldModelSerializer): site = NestedSiteSerializer(required=False, allow_null=True) vrf = NestedVRFSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True) vlan = NestedVLANSerializer(required=False, allow_null=True) status = ChoiceField(choices=PREFIX_STATUS_CHOICES, required=False) role = NestedRoleSerializer(required=False, allow_null=True) - tags = TagField(queryset=Tag.objects.all(), required=False, many=True) + tags = TagListSerializerField(required=False) class Meta: model = Prefix @@ -254,13 +254,13 @@ class IPAddressInterfaceSerializer(serializers.ModelSerializer): return reverse(url_name, kwargs={'pk': obj.pk}, request=self.context['request']) -class IPAddressSerializer(CustomFieldModelSerializer): +class IPAddressSerializer(TaggitSerializer, CustomFieldModelSerializer): vrf = NestedVRFSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True) status = ChoiceField(choices=IPADDRESS_STATUS_CHOICES, required=False) role = ChoiceField(choices=IPADDRESS_ROLE_CHOICES, required=False) interface = IPAddressInterfaceSerializer(required=False, allow_null=True) - tags = TagField(queryset=Tag.objects.all(), required=False, many=True) + tags = TagListSerializerField(required=False) class Meta: model = IPAddress diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 6b925b3a0..c0ccfa12b 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -144,6 +144,7 @@ INSTALLED_APPS = [ 'mptt', 'rest_framework', 'taggit', + 'taggit_serializer', 'timezone_field', 'circuits', 'dcim', diff --git a/netbox/secrets/api/serializers.py b/netbox/secrets/api/serializers.py index 3c24edf2b..ee7217b63 100644 --- a/netbox/secrets/api/serializers.py +++ b/netbox/secrets/api/serializers.py @@ -2,12 +2,12 @@ from __future__ import unicode_literals from rest_framework import serializers from rest_framework.validators import UniqueTogetherValidator -from taggit.models import Tag +from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField from dcim.api.serializers import NestedDeviceSerializer from extras.api.customfields import CustomFieldModelSerializer from secrets.models import Secret, SecretRole -from utilities.api import TagField, ValidatedModelSerializer, WritableNestedSerializer +from utilities.api import ValidatedModelSerializer, WritableNestedSerializer # @@ -33,11 +33,11 @@ class NestedSecretRoleSerializer(WritableNestedSerializer): # Secrets # -class SecretSerializer(CustomFieldModelSerializer): +class SecretSerializer(TaggitSerializer, CustomFieldModelSerializer): device = NestedDeviceSerializer() role = NestedSecretRoleSerializer() plaintext = serializers.CharField() - tags = TagField(queryset=Tag.objects.all(), required=False, many=True) + tags = TagListSerializerField(required=False) class Meta: model = Secret diff --git a/netbox/tenancy/api/serializers.py b/netbox/tenancy/api/serializers.py index c7b94e7e9..592e35a6e 100644 --- a/netbox/tenancy/api/serializers.py +++ b/netbox/tenancy/api/serializers.py @@ -1,11 +1,11 @@ from __future__ import unicode_literals from rest_framework import serializers -from taggit.models import Tag +from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField from extras.api.customfields import CustomFieldModelSerializer from tenancy.models import Tenant, TenantGroup -from utilities.api import TagField, ValidatedModelSerializer, WritableNestedSerializer +from utilities.api import ValidatedModelSerializer, WritableNestedSerializer # @@ -31,9 +31,9 @@ class NestedTenantGroupSerializer(WritableNestedSerializer): # Tenants # -class TenantSerializer(CustomFieldModelSerializer): +class TenantSerializer(TaggitSerializer, CustomFieldModelSerializer): group = NestedTenantGroupSerializer(required=False) - tags = TagField(queryset=Tag.objects.all(), required=False, many=True) + tags = TagListSerializerField(required=False) class Meta: model = Tenant diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py index 296f1bb10..4dc56cc8a 100644 --- a/netbox/utilities/api.py +++ b/netbox/utilities/api.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals from collections import OrderedDict import pytz -from taggit.models import Tag from django.conf import settings from django.contrib.contenttypes.models import ContentType @@ -13,7 +12,7 @@ from rest_framework.exceptions import APIException from rest_framework.permissions import BasePermission from rest_framework.relations import PrimaryKeyRelatedField from rest_framework.response import Response -from rest_framework.serializers import Field, ModelSerializer, RelatedField, ValidationError +from rest_framework.serializers import Field, ModelSerializer, ValidationError from rest_framework.viewsets import ModelViewSet as _ModelViewSet, ViewSet from .utils import dynamic_import @@ -56,20 +55,6 @@ class IsAuthenticatedOrLoginNotRequired(BasePermission): # Fields # -class TagField(RelatedField): - """ - Represent a writable list of Tags associated with an object (use with many=True). - """ - def to_internal_value(self, data): - obj = self.parent.parent.instance - content_type = ContentType.objects.get_for_model(obj) - tag, _ = Tag.objects.get_or_create(content_type=content_type, object_id=obj.pk, name=data) - return tag - - def to_representation(self, value): - return value.name - - class ChoiceField(Field): """ Represent a ChoiceField as {'value': , 'label': }. @@ -147,9 +132,10 @@ class ValidatedModelSerializer(ModelSerializer): """ def validate(self, data): - # Remove custom field data (if any) prior to model validation + # Remove custom fields data and tags (if any) prior to model validation attrs = data.copy() attrs.pop('custom_fields', None) + attrs.pop('tags', None) # Run clean() on an instance of the model if self.instance is None: diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index 14c29d211..2ba5fa4ba 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -101,8 +101,8 @@ def serialize_object(obj, extra=None): } # Include any tags - if hasattr(obj, 'tags'): - data['tags'] = [tag.name for tag in obj.tags.all()] + # if hasattr(obj, 'tags'): + # data['tags'] = [tag.name for tag in obj.tags.all()] # Append any extra data if extra is not None: diff --git a/netbox/virtualization/api/serializers.py b/netbox/virtualization/api/serializers.py index 81385878b..57ce1d57a 100644 --- a/netbox/virtualization/api/serializers.py +++ b/netbox/virtualization/api/serializers.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals from rest_framework import serializers -from taggit.models import Tag +from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField from dcim.api.serializers import NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer from dcim.constants import IFACE_MODE_CHOICES @@ -9,7 +9,7 @@ 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 ChoiceField, TagField, ValidatedModelSerializer, WritableNestedSerializer +from utilities.api import ChoiceField, ValidatedModelSerializer, WritableNestedSerializer from virtualization.constants import VM_STATUS_CHOICES from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine @@ -56,11 +56,11 @@ class NestedClusterGroupSerializer(WritableNestedSerializer): # Clusters # -class ClusterSerializer(CustomFieldModelSerializer): +class ClusterSerializer(TaggitSerializer, 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) + tags = TagListSerializerField(required=False) class Meta: model = Cluster @@ -90,7 +90,7 @@ class VirtualMachineIPAddressSerializer(serializers.ModelSerializer): fields = ['id', 'url', 'family', 'address'] -class VirtualMachineSerializer(CustomFieldModelSerializer): +class VirtualMachineSerializer(TaggitSerializer, CustomFieldModelSerializer): status = ChoiceField(choices=VM_STATUS_CHOICES, required=False) cluster = NestedClusterSerializer(required=False, allow_null=True) role = NestedDeviceRoleSerializer(required=False, allow_null=True) @@ -99,7 +99,7 @@ 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) + tags = TagListSerializerField(required=False) class Meta: model = VirtualMachine diff --git a/requirements.txt b/requirements.txt index d641c54e3..d15bfb19d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ django-filter==1.1.0 django-mptt==0.9.1 django-tables2==1.21.2 django-taggit==0.22.2 +django-taggit-serializer==0.1.7 django-timezone-field==2.1 djangorestframework==3.8.1 drf-yasg[validation]==1.9.1