content: import datetime
  import json
  from decimal import Decimal
  
  from django.core.exceptions import ValidationError
  from django.test import tag
  from django.urls import reverse
  from rest_framework import status
  
  from core.models import ObjectType
  from dcim.filtersets import SiteFilterSet
  from dcim.forms import SiteImportForm
  from dcim.models import Manufacturer, Rack, Site
  from extras.choices import *
  from extras.models import CustomField, CustomFieldChoiceSet
  from ipam.models import VLAN
  from netbox.choices import CSVDelimiterChoices, ImportFormatChoices
  from utilities.testing import APITestCase, TestCase
  from virtualization.models import VirtualMachine
  
  
  class CustomFieldTest(TestCase):
  
      @classmethod
      def setUpTestData(cls):
  
          Site.objects.bulk_create([
              Site(name='Site A', slug='site-a'),
              Site(name='Site B', slug='site-b'),
              Site(name='Site C', slug='site-c'),
          ])
  
          cls.object_type = ObjectType.objects.get_for_model(Site)
  
      def test_invalid_name(self):
          """
          Try creating a CustomField with an invalid name.
          """
          with self.assertRaises(ValidationError):
              # Invalid character
              CustomField(name='?', type=CustomFieldTypeChoices.TYPE_TEXT).full_clean()
          with self.assertRaises(ValidationError):
              # Double underscores not permitted
              CustomField(name='foo__bar', type=CustomFieldTypeChoices.TYPE_TEXT).full_clean()
  
      def test_text_field(self):
          value = 'Foobar!'
  
          # Create a custom field & check that initial value is null
          cf = CustomField.objects.create(
              name='text_field',
              type=CustomFieldTypeChoices.TYPE_TEXT,
              required=False
          )
          cf.object_types.set([self.object_type])
          instance = Site.objects.first()
          self.assertIsNone(instance.custom_field_data[cf.name])
  
          # Assign a value and check that it is saved
          instance.custom_field_data[cf.name] = value
          instance.save()
          instance.refresh_from_db()
          self.assertEqual(instance.custom_field_data[cf.name], value)
  
          # Delete the stored value and check that it is now null
          instance.custom_field_data.pop(cf.name)
          instance.save()
          instance.refresh_from_db()
          self.assertIsNone(instance.custom_field_data.get(cf.name))
  
      def test_longtext_field(self):
          value = 'A' * 256
  
          # Create a custom field & check that initial value is null
          cf = CustomField.objects.create(
              name='longtext_field',
              type=CustomFieldTypeChoices.TYPE_LONGTEXT,
              required=False
          )
          cf.object_types.set([self.object_type])
          instance = Site.objects.first()
          self.assertIsNone(instance.custom_field_data[cf.name])
  
          # Assign a value and check that it is saved
          instance.custom_field_data[cf.name] = value
          instance.save()
          instance.refresh_from_db()
          self.assertEqual(instance.custom_field_data[cf.name], value)
  
          # Delete the stored value and check that it is now null
          instance.custom_field_data.pop(cf.name)
          instance.save()
          instance.refresh_from_db()
          self.assertIsNone(instance.custom_field_data.get(cf.name))
  
      def test_integer_field(self):
  
          # Create a custom field & check that initial value is null
          cf = CustomField.objects.create(
              name='integer_field',
              type=CustomFieldTypeChoices.TYPE_INTEGER,
              required=False
          )
          cf.object_types.set([self.object_type])
          instance = Site.objects.first()
          self.assertIsNone(instance.custom_field_data[cf.name])
  
          for value in (123456, 0, -123456):
  
              # Assign a value and check that it is saved
              instance.custom_field_data[cf.name] = value
              instance.save()
              instance.refresh_from_db()
              self.assertEqual(instance.custom_field_data[cf.name], value)
  
              # Delete the stored value and check that it is now null
              instance.custom_field_data.pop(cf.name)
              instance.save()
              instance.refresh_from_db()
              self.assertIsNone(instance.custom_field_data.get(cf.name))
  
      def test_decimal_field(self):
  
          # Create a custom field & check that initial value is null
          cf = CustomField.objects.create(
              name='decimal_field',
              type=CustomFieldTypeChoices.TYPE_DECIMAL,
              required=False
          )
          cf.object_types.set([self.object_type])
          instance = Site.objects.first()
          self.assertIsNone(instance.custom_field_data[cf.name])
  
          for value in (123456.54, 0, -123456.78):
  
              # Assign a value and check that it is saved
              instance.custom_field_data[cf.name] = value
              instance.save()
              instance.refresh_from_db()
              self.assertEqual(instance.custom_field_data[cf.name], value)
  
              # Delete the stored value and check that it is now null
              instance.custom_field_data.pop(cf.name)
              instance.save()
              instance.refresh_from_db()
              self.assertIsNone(instance.custom_field_data.get(cf.name))
  
      def test_boolean_field(self):
  
          # Create a custom field & check that initial value is null
          cf = CustomField.objects.create(
              name='boolean_field',
              type=CustomFieldTypeChoices.TYPE_INTEGER,
              required=False
          )
          cf.object_types.set([self.object_type])
          instance = Site.objects.first()
          self.assertIsNone(instance.custom_field_data[cf.name])
  
          for value in (True, False):
  
              # Assign a value and check that it is saved
              instance.custom_field_data[cf.name] = value
              instance.save()
              instance.refresh_from_db()
              self.assertEqual(instance.custom_field_data[cf.name], value)
  
              # Delete the stored value and check that it is now null
              instance.custom_field_data.pop(cf.name)
              instance.save()
              instance.refresh_from_db()
              self.assertIsNone(instance.custom_field_data.get(cf.name))
  
      def test_date_field(self):
          value = datetime.date(2016, 6, 23)
  
          # Create a custom field & check that initial value is null
          cf = CustomField.objects.create(
              name='date_field',
              type=CustomFieldTypeChoices.TYPE_DATE,
              required=False
          )
          cf.object_types.set([self.object_type])
          instance = Site.objects.first()
          self.assertIsNone(instance.custom_field_data[cf.name])
  
          # Assign a value and check that it is saved
          instance.custom_field_data[cf.name] = cf.serialize(value)
          instance.save()
          instance.refresh_from_db()
          self.assertEqual(instance.cf[cf.name], value)
  
          # Delete the stored value and check that it is now null
          instance.custom_field_data.pop(cf.name)
          instance.save()
          instance.refresh_from_db()
          self.assertIsNone(instance.custom_field_data.get(cf.name))
  
      def test_datetime_field(self):
          value = datetime.datetime(2016, 6, 23, 9, 45, 0)
  
          # Create a custom field & check that initial value is null
          cf = CustomField.objects.create(
              name='date_field',
              type=CustomFieldTypeChoices.TYPE_DATETIME,
              required=False
          )
          cf.object_types.set([self.object_type])
          instance = Site.objects.first()
          self.assertIsNone(instance.custom_field_data[cf.name])
  
          # Assign a value and check that it is saved
          instance.custom_field_data[cf.name] = cf.serialize(value)
          instance.save()
          instance.refresh_from_db()
          self.assertEqual(instance.cf[cf.name], value)
  
          # Delete the stored value and check that it is now null
          instance.custom_field_data.pop(cf.name)
          instance.save()
          instance.refresh_from_db()
          self.assertIsNone(instance.custom_field_data.get(cf.name))
  
      def test_url_field(self):
          value = 'http://example.com/'
  
          # Create a custom field & check that initial value is null
          cf = CustomField.objects.create(
              name='url_field',
              type=CustomFieldTypeChoices.TYPE_URL,
              required=False
          )
          cf.object_types.set([self.object_type])
          instance = Site.objects.first()
          self.assertIsNone(instance.custom_field_data[cf.name])
  
          # Assign a value and check that it is saved
          instance.custom_field_data[cf.name] = value
          instance.save()
          instance.refresh_from_db()
          self.assertEqual(instance.custom_field_data[cf.name], value)
  
          # Delete the stored value and check that it is now null
          instance.custom_field_data.pop(cf.name)
          instance.save()
          instance.refresh_from_db()
          self.assertIsNone(instance.custom_field_data.get(cf.name))
  
      def test_json_field(self):
          value = '{"foo": 1, "bar": 2}'
  
          # Create a custom field & check that initial value is null
          cf = CustomField.objects.create(
              name='json_field',
              type=CustomFieldTypeChoices.TYPE_JSON,
              required=False
          )
          cf.object_types.set([self.object_type])
          instance = Site.objects.first()
          self.assertIsNone(instance.custom_field_data[cf.name])
  
          # Assign a value and check that it is saved
          instance.custom_field_data[cf.name] = value
          instance.save()
          instance.refresh_from_db()
          self.assertEqual(instance.custom_field_data[cf.name], value)
  
          # Delete the stored value and check that it is now null
          instance.custom_field_data.pop(cf.name)
          instance.save()
          instance.refresh_from_db()
          self.assertIsNone(instance.custom_field_data.get(cf.name))
  
      @tag('regression')
      def test_json_field_falsy_defaults(self):
          """Test that falsy JSON default values are properly handled"""
          falsy_test_cases = [
              ({}, 'empty_dict'),
              ([], 'empty_array'),
              (0, 'zero'),
              (False, 'false_bool'),
              ("", 'empty_string'),
          ]
  
          for default, suffix in falsy_test_cases:
              with self.subTest(default=default, suffix=suffix):
                  cf = CustomField.objects.create(
                      name=f'json_falsy_{suffix}',
                      type=CustomFieldTypeChoices.TYPE_JSON,
                      default=default,
                      required=False
                  )
                  cf.object_types.set([self.object_type])
  
                  instance = Site.objects.create(name=f'Test Site {suffix}', slug=f'test-site-{suffix}')
  
                  self.assertIsNotNone(instance.custom_field_data)
                  self.assertIn(cf.name, instance.custom_field_data)
  
                  instance.refresh_from_db()
                  stored = instance.custom_field_data[cf.name]
                  self.assertEqual(stored, default)
  
      @tag('regression')
      def test_json_field_falsy_to_form_field(self):
          """Test form field generation preserves falsy defaults"""
          falsy_test_cases = (
              ({}, json.dumps({}), 'empty_dict'),
              ([], json.dumps([]), 'empty_array'),
              (0, json.dumps(0), 'zero'),
              (False, json.dumps(False), 'false_bool'),
              ("", '""', 'empty_string'),
          )
  
          for default, expected, suffix in falsy_test_cases:
              with self.subTest(default=default, expected=expected, suffix=suffix):
                  cf = CustomField.objects.create(
                      name=f'json_falsy_{suffix}',
                      type=CustomFieldTypeChoices.TYPE_JSON,
                      default=default,
                      required=False
                  )
                  cf.object_types.set([self.object_type])
  
                  form_field = cf.to_form_field(set_initial=True)
                  self.assertEqual(form_field.initial, expected)
  
      def test_select_field(self):
          CHOICES = (
              ('a', 'Option A'),
              ('b', 'Option B'),
              ('c', 'Option C'),
          )
          value = 'a'
  
          # Create a set of custom field choices
          choice_set = CustomFieldChoiceSet.objects.create(
              name='Custom Field Choice Set 1',
              extra_choices=CHOICES
          )
  
          # Create a custom field & check that initial value is null
          cf = CustomField.objects.create(
              name='select_field',
              type=CustomFieldTypeChoices.TYPE_SELECT,
              required=False,
              choice_set=choice_set
          )
          cf.object_types.set([self.object_type])
          instance = Site.objects.first()
          self.assertIsNone(instance.custom_field_data[cf.name])
  
          # Assign a value and check that it is saved
          instance.custom_field_data[cf.name] = value
          instance.save()
          instance.refresh_from_db()
          self.assertEqual(instance.custom_field_data[cf.name], value)
  
          # Delete the stored value and check that it is now null
          instance.custom_field_data.pop(cf.name)
          instance.save()
          instance.refresh_from_db()
          self.assertIsNone(instance.custom_field_data.get(cf.name))
  
      def test_multiselect_field(self):
          CHOICES = (
              ('a', 'Option A'),
              ('b', 'Option B'),
              ('c', 'Option C'),
          )
          value = ['a', 'b']
  
          # Create a set of custom field choices
          choice_set = CustomFieldChoiceSet.objects.create(
              name='Custom Field Choice Set 1',
              extra_choices=CHOICES
          )
  
          # Create a custom field & check that initial value is null
          cf = CustomField.objects.create(
              name='multiselect_field',
              type=CustomFieldTypeChoices.TYPE_MULTISELECT,
              required=False,
              choice_set=choice_set
          )
          cf.object_types.set([self.object_type])
          instance = Site.objects.first()
          self.assertIsNone(instance.custom_field_data[cf.name])
  
          # Assign a value and check that it is saved
          instance.custom_field_data[cf.name] = value
          instance.save()
          instance.refresh_from_db()
          self.assertEqual(instance.custom_field_data[cf.name], value)
  
          # Delete the stored value and check that it is now null
          instance.custom_field_data.pop(cf.name)
          instance.save()
          instance.refresh_from_db()
          self.assertIsNone(instance.custom_field_data.get(cf.name))
  
      def test_remove_selected_choice(self):
          """
          Removing a ChoiceSet choice that is referenced by an object should raise
          a ValidationError exception.
          """
          CHOICES = (
              ('a', 'Option A'),
              ('b', 'Option B'),
              ('c', 'Option C'),
              ('d', 'Option D'),
          )
  
          # Create a set of custom field choices
          choice_set = CustomFieldChoiceSet.objects.create(
              name='Custom Field Choice Set 1',
              extra_choices=CHOICES
          )
  
          # Create a select custom field
          cf = CustomField.objects.create(
              name='select_field',
              type=CustomFieldTypeChoices.TYPE_SELECT,
              required=False,
              choice_set=choice_set
          )
          cf.object_types.set([self.object_type])
  
          # Create a multi-select custom field
          cf_multiselect = CustomField.objects.create(
              name='multiselect_field',
              type=CustomFieldTypeChoices.TYPE_MULTISELECT,
              required=False,
              choice_set=choice_set
          )
          cf_multiselect.object_types.set([self.object_type])
  
          # Assign a choice for both custom fields on an object
          instance = Site.objects.first()
          instance.custom_field_data[cf.name] = 'a'
          instance.custom_field_data[cf_multiselect.name] = ['b', 'c']
          instance.save()
  
          # Attempting to delete a selected choice should fail
          with self.assertRaises(ValidationError):
              choice_set.extra_choices = (
                  ('b', 'Option B'),
                  ('c', 'Option C'),
                  ('d', 'Option D'),
              )
              choice_set.full_clean()
  
          # Attempting to delete either of the multi-select choices should fail
          with self.assertRaises(ValidationError):
              choice_set.extra_choices = (
                  ('a', 'Option A'),
                  ('b', 'Option B'),
                  ('d', 'Option D'),
              )
              choice_set.full_clean()
  
          # Removing a non-selected choice should succeed
          choice_set.extra_choices = (
              ('a', 'Option A'),
              ('b', 'Option B'),
              ('c', 'Option C'),
          )
          choice_set.full_clean()
  
      def test_object_field(self):
          value = VLAN.objects.create(name='VLAN 1', vid=1).pk
  
          # Create a custom field & check that initial value is null
          cf = CustomField.objects.create(
              name='object_field',
              type=CustomFieldTypeChoices.TYPE_OBJECT,
              related_object_type=ObjectType.objects.get_for_model(VLAN),
              required=False
          )
          cf.object_types.set([self.object_type])
          instance = Site.objects.first()
          self.assertIsNone(instance.custom_field_data[cf.name])
  
          # Assign a value and check that it is saved
          instance.custom_field_data[cf.name] = value
          instance.save()
          instance.refresh_from_db()
          self.assertEqual(instance.custom_field_data[cf.name], value)
  
          # Delete the stored value and check that it is now null
          instance.custom_field_data.pop(cf.name)
          instance.save()
          instance.refresh_from_db()
          self.assertIsNone(instance.custom_field_data.get(cf.name))
  
      def test_multiobject_field(self):
          vlans = (
              VLAN(name='VLAN 1', vid=1),
              VLAN(name='VLAN 2', vid=2),
              VLAN(name='VLAN 3', vid=3),
          )
          VLAN.objects.bulk_create(vlans)
          value = [vlan.pk for vlan in vlans]
  
          # Create a custom field & check that initial value is null
          cf = CustomField.objects.create(
              name='object_field',
              type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
              related_object_type=ObjectType.objects.get_for_model(VLAN),
              required=False
          )
          cf.object_types.set([self.object_type])
          instance = Site.objects.first()
          self.assertIsNone(instance.custom_field_data[cf.name])
  
          # Assign a value and check that it is saved
          instance.custom_field_data[cf.name] = value
          instance.save()
          instance.refresh_from_db()
          self.assertEqual(instance.custom_field_data[cf.name], value)
  
          # Delete the stored value and check that it is now null
          instance.custom_field_data.pop(cf.name)
          instance.save()
          instance.refresh_from_db()
          self.assertIsNone(instance.custom_field_data.get(cf.name))
  
      def test_rename_customfield(self):
          obj_type = ObjectType.objects.get_for_model(Site)
          FIELD_DATA = 'abc'
  
          # Create a custom field
          cf = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='field1')
          cf.save()
          cf.object_types.set([obj_type])
  
          # Assign custom field data to an object
          site = Site.objects.create(
              name='Site 1',
              slug='site-1',
              custom_field_data={'field1': FIELD_DATA}
          )
          site.refresh_from_db()
          self.assertEqual(site.custom_field_data['field1'], FIELD_DATA)
  
          # Rename the custom field
          cf.name = 'field2'
          cf.save()
  
          # Check that custom field data on the object has been updated
          site.refresh_from_db()
          self.assertNotIn('field1', site.custom_field_data)
          self.assertEqual(site.custom_field_data['field2'], FIELD_DATA)
  
      def test_default_value_validation(self):
          choiceset = CustomFieldChoiceSet.objects.create(
              name="Test Choice Set",
              extra_choices=(
                  ('choice1', 'Choice 1'),
                  ('choice2', 'Choice 2'),
              )
          )
          site = Site.objects.create(name='Site 1', slug='site-1')
          object_type = ObjectType.objects.get_for_model(Site)
  
          # Text
          CustomField(name='test', type='text', required=True, default="Default text").full_clean()
  
          # Integer
          CustomField(name='test', type='integer', required=True, default=1).full_clean()
          with self.assertRaises(ValidationError):
              CustomField(name='test', type='integer', required=True, default='xxx').full_clean()
  
          # Boolean
          CustomField(name='test', type='boolean', required=True, default=True).full_clean()
          with self.assertRaises(ValidationError):
              CustomField(name='test', type='boolean', required=True, default='xxx').full_clean()
  
          # Date
          CustomField(name='test', type='date', required=True, default="2023-02-25").full_clean()
          with self.assertRaises(ValidationError):
              CustomField(name='test', type='date', required=True, default='xxx').full_clean()
  
          # Datetime
          CustomField(name='test', type='datetime', required=True, default="2023-02-25 02:02:02").full_clean()
          with self.assertRaises(ValidationError):
              CustomField(name='test', type='datetime', required=True, default='xxx').full_clean()
  
          # URL
          CustomField(name='test', type='url', required=True, default="https://www.netbox.dev").full_clean()
  
          # JSON
          CustomField(name='test', type='json', required=True, default='{"test": "object"}').full_clean()
  
          # Selection
          CustomField(name='test', type='select', required=True, choice_set=choiceset, default='choice1').full_clean()
          with self.assertRaises(ValidationError):
              CustomField(name='test', type='select', required=True, choice_set=choiceset, default='xxx').full_clean()
  
          # Multi-select
          CustomField(
              name='test',
              type='multiselect',
              required=True,
              choice_set=choiceset,
              default=['choice1']  # Single default choice
          ).full_clean()
          CustomField(
              name='test',
              type='multiselect',
              required=True,
              choice_set=choiceset,
              default=['choice1', 'choice2']  # Multiple default choices
          ).full_clean()
          with self.assertRaises(ValidationError):
              CustomField(
                  name='test',
                  type='multiselect',
                  required=True,
                  choice_set=choiceset,
                  default=['xxx']
              ).full_clean()
  
          # Object
          CustomField(
              name='test',
              type='object',
              required=True,
              related_object_type=object_type,
              default=site.pk
          ).full_clean()
          with (self.assertRaises(ValidationError)):
              CustomField(
                  name='test',
                  type='object',
                  required=True,
                  related_object_type=object_type,
                  default="xxx"
              ).full_clean()
  
          # Multi-object
          CustomField(
              name='test',
              type='multiobject',
              required=True,
              related_object_type=object_type,
              default=[site.pk]
          ).full_clean()
          with self.assertRaises(ValidationError):
              CustomField(
                  name='test',
                  type='multiobject',
                  required=True,
                  related_object_type=object_type,
                  default=["xxx"]
              ).full_clean()
  
  
  class CustomFieldManagerTest(TestCase):
  
      @classmethod
      def setUpTestData(cls):
          object_type = ObjectType.objects.get_for_model(Site)
          custom_field = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='text_field', default='foo')
          custom_field.save()
          custom_field.object_types.set([object_type])
  
      def test_get_for_model(self):
          self.assertEqual(CustomField.objects.get_for_model(Site).count(), 1)
          self.assertEqual(CustomField.objects.get_for_model(VirtualMachine).count(), 0)
  
  
  class CustomFieldAPITest(APITestCase):
  
      @classmethod
      def setUpTestData(cls):
          object_type = ObjectType.objects.get_for_model(Site)
  
          # Create some VLANs
          vlans = (
              VLAN(name='VLAN 1', vid=1),
              VLAN(name='VLAN 2', vid=2),
              VLAN(name='VLAN 3', vid=3),
              VLAN(name='VLAN 4', vid=4),
              VLAN(name='VLAN 5', vid=5),
          )
          VLAN.objects.bulk_create(vlans)
  
          # Create a set of custom field choices
          choice_set = CustomFieldChoiceSet.objects.create(
              name='Custom Field Choice Set 1',
              extra_choices=(('foo', 'Foo'), ('bar', 'Bar'), ('baz', 'Baz'))
          )
  
          custom_fields = (
              CustomField(
                  type=CustomFieldTypeChoices.TYPE_TEXT,
                  name='text_field',
                  default='foo'
              ),
              CustomField(
                  type=CustomFieldTypeChoices.TYPE_LONGTEXT,
                  name='longtext_field',
                  default='ABC'
              ),
              CustomField(
                  type=CustomFieldTypeChoices.TYPE_INTEGER,
                  name='integer_field',
                  default=123
              ),
              CustomField(
                  type=CustomFieldTypeChoices.TYPE_DECIMAL,
                  name='decimal_field',
                  default=123.45
              ),
              CustomField(
                  type=CustomFieldTypeChoices.TYPE_BOOLEAN,
                  name='boolean_field',
                  default=False
              ),
              CustomField(
                  type=CustomFieldTypeChoices.TYPE_DATE,
                  name='date_field',
                  default='2020-01-01'
              ),
              CustomField(
                  type=CustomFieldTypeChoices.TYPE_DATETIME,
                  name='datetime_field',
                  default='2020-01-01T01:23:45'
              ),
              CustomField(
                  type=CustomFieldTypeChoices.TYPE_URL,
                  name='url_field',
                  default='http://example.com/1'
              ),
              CustomField(
                  type=CustomFieldTypeChoices.TYPE_JSON,
                  name='json_field',
                  default='{"x": "y"}'
              ),
              CustomField(
                  type=CustomFieldTypeChoices.TYPE_SELECT,
                  name='select_field',
                  default='foo',
                  choice_set=choice_set
              ),
              CustomField(
                  type=CustomFieldTypeChoices.TYPE_MULTISELECT,
                  name='multiselect_field',
                  default=['foo'],
                  choice_set=choice_set,
              ),
              CustomField(
                  type=CustomFieldTypeChoices.TYPE_OBJECT,
                  name='object_field',
                  related_object_type=ObjectType.objects.get_for_model(VLAN),
                  default=vlans[0].pk,
              ),
              CustomField(
                  type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
                  name='multiobject_field',
                  related_object_type=ObjectType.objects.get_for_model(VLAN),
                  default=[vlans[0].pk, vlans[1].pk],
              ),
          )
          for cf in custom_fields:
              cf.save()
              cf.object_types.set([object_type])
  
          # Create some sites *after* creating the custom fields. This ensures that
          # default values are not set for the assigned objects.
          sites = (
              Site(name='Site 1', slug='site-1'),
              Site(name='Site 2', slug='site-2'),
          )
          Site.objects.bulk_create(sites)
  
          # Assign custom field values for site 2
          sites[1].custom_field_data = {
              custom_fields[0].name: 'bar',
              custom_fields[1].name: 'DEF',
              custom_fields[2].name: 456,
              custom_fields[3].name: Decimal('456.78'),
              custom_fields[4].name: True,
              custom_fields[5].name: '2020-01-02',
              custom_fields[6].name: '2020-01-02 12:00:00',
              custom_fields[7].name: 'http://example.com/2',
              custom_fields[8].name: '{"foo": 1, "bar": 2}',
              custom_fields[9].name: 'bar',
              custom_fields[10].name: ['bar', 'baz'],
              custom_fields[11].name: vlans[1].pk,
              custom_fields[12].name: [vlans[2].pk, vlans[3].pk],
          }
          sites[1].save()
  
      def test_get_custom_fields(self):
          TYPES = {
              CustomFieldTypeChoices.TYPE_TEXT: 'string',
              CustomFieldTypeChoices.TYPE_LONGTEXT: 'string',
              CustomFieldTypeChoices.TYPE_INTEGER: 'integer',
              CustomFieldTypeChoices.TYPE_DECIMAL: 'decimal',
              CustomFieldTypeChoices.TYPE_BOOLEAN: 'boolean',
              CustomFieldTypeChoices.TYPE_DATE: 'string',
              CustomFieldTypeChoices.TYPE_DATETIME: 'string',
              CustomFieldTypeChoices.TYPE_URL: 'string',
              CustomFieldTypeChoices.TYPE_JSON: 'object',
              CustomFieldTypeChoices.TYPE_SELECT: 'string',
              CustomFieldTypeChoices.TYPE_MULTISELECT: 'array',
              CustomFieldTypeChoices.TYPE_OBJECT: 'object',
              CustomFieldTypeChoices.TYPE_MULTIOBJECT: 'array',
          }
  
          self.add_permissions('extras.view_customfield')
          url = reverse('extras-api:customfield-list')
          response = self.client.get(url, **self.header)
          self.assertEqual(response.data['count'], len(TYPES))
  
          # Validate data types
          for customfield in response.data['results']:
              cf_type = customfield['type']['value']
              self.assertEqual(customfield['data_type'], TYPES[cf_type])
  
      def test_get_single_object_without_custom_field_data(self):
          """
          Validate that custom fields are present on an object even if it has no values defined.
          """
          site1 = Site.objects.get(name='Site 1')
          url = reverse('dcim-api:site-detail', kwargs={'pk': site1.pk})
          self.add_permissions('dcim.view_site')
  
          response = self.client.get(url, **self.header)
          self.assertEqual(response.data['name'], site1.name)
          self.assertEqual(response.data['custom_fields'], {
              'text_field': None,
              'longtext_field': None,
              'integer_field': None,
              'decimal_field': None,
              'boolean_field': None,
              'date_field': None,
              'datetime_field': None,
              'url_field': None,
              'json_field': None,
              'select_field': None,
              'multiselect_field': None,
              'object_field': None,
              'multiobject_field': None,
          })
  
      def test_get_single_object_with_custom_field_data(self):
          """
          Validate that custom fields are present and correctly set for an object with values defined.
          """
          site2 = Site.objects.get(name='Site 2')
          site2_cfvs = site2.cf
          url = reverse('dcim-api:site-detail', kwargs={'pk': site2.pk})
          self.add_permissions('dcim.view_site')
  
          response = self.client.get(url, **self.header)
          self.assertEqual(response.data['name'], site2.name)
          self.assertEqual(response.data['custom_fields']['text_field'], site2_cfvs['text_field'])
          self.assertEqual(response.data['custom_fields']['longtext_field'], site2_cfvs['longtext_field'])
          self.assertEqual(response.data['custom_fields']['integer_field'], site2_cfvs['integer_field'])
          self.assertEqual(response.data['custom_fields']['decimal_field'], site2_cfvs['decimal_field'])
          self.assertEqual(response.data['custom_fields']['boolean_field'], site2_cfvs['boolean_field'])
          self.assertEqual(response.data['custom_fields']['date_field'], site2_cfvs['date_field'])
          self.assertEqual(response.data['custom_fields']['datetime_field'], site2_cfvs['datetime_field'])
          self.assertEqual(response.data['custom_fields']['url_field'], site2_cfvs['url_field'])
          self.assertEqual(response.data['custom_fields']['json_field'], site2_cfvs['json_field'])
          self.assertEqual(response.data['custom_fields']['select_field'], site2_cfvs['select_field'])
          self.assertEqual(response.data['custom_fields']['multiselect_field'], site2_cfvs['multiselect_field'])
          self.assertEqual(response.data['custom_fields']['object_field']['id'], site2_cfvs['object_field'].pk)
          self.assertEqual(
  