mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-17 04:32:51 -06:00
Rename CustomField.content_types to object_types & use ObjectType proxy
This commit is contained in:
parent
0df68bf291
commit
aeeec284a5
@ -1,8 +1,8 @@
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from circuits.models import *
|
from circuits.models import *
|
||||||
|
from core.models import ObjectType
|
||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
from extras.models import CustomField
|
from extras.models import CustomField
|
||||||
@ -293,8 +293,8 @@ class DeviceTestCase(TestCase):
|
|||||||
|
|
||||||
# Create a CustomField with a default value & assign it to all component models
|
# Create a CustomField with a default value & assign it to all component models
|
||||||
cf1 = CustomField.objects.create(name='cf1', default='foo')
|
cf1 = CustomField.objects.create(name='cf1', default='foo')
|
||||||
cf1.content_types.set(
|
cf1.object_types.set(
|
||||||
ContentType.objects.filter(app_label='dcim', model__in=[
|
ObjectType.objects.filter(app_label='dcim', model__in=[
|
||||||
'consoleport',
|
'consoleport',
|
||||||
'consoleserverport',
|
'consoleserverport',
|
||||||
'powerport',
|
'powerport',
|
||||||
|
@ -26,7 +26,7 @@ class CustomFieldDefaultValues:
|
|||||||
|
|
||||||
# Retrieve the CustomFields for the parent model
|
# Retrieve the CustomFields for the parent model
|
||||||
content_type = ContentType.objects.get_for_model(self.model)
|
content_type = ContentType.objects.get_for_model(self.model)
|
||||||
fields = CustomField.objects.filter(content_types=content_type)
|
fields = CustomField.objects.filter(object_types=content_type)
|
||||||
|
|
||||||
# Populate the default value for each CustomField
|
# Populate the default value for each CustomField
|
||||||
value = {}
|
value = {}
|
||||||
@ -48,7 +48,7 @@ class CustomFieldsDataField(Field):
|
|||||||
"""
|
"""
|
||||||
if not hasattr(self, '_custom_fields'):
|
if not hasattr(self, '_custom_fields'):
|
||||||
content_type = ContentType.objects.get_for_model(self.parent.Meta.model)
|
content_type = ContentType.objects.get_for_model(self.parent.Meta.model)
|
||||||
self._custom_fields = CustomField.objects.filter(content_types=content_type)
|
self._custom_fields = CustomField.objects.filter(object_types=content_type)
|
||||||
return self._custom_fields
|
return self._custom_fields
|
||||||
|
|
||||||
def to_representation(self, obj):
|
def to_representation(self, obj):
|
||||||
|
@ -117,7 +117,7 @@ class WebhookSerializer(NetBoxModelSerializer):
|
|||||||
|
|
||||||
class CustomFieldSerializer(ValidatedModelSerializer):
|
class CustomFieldSerializer(ValidatedModelSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='extras-api:customfield-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='extras-api:customfield-detail')
|
||||||
content_types = ContentTypeField(
|
object_types = ContentTypeField(
|
||||||
queryset=ObjectType.objects.with_feature('custom_fields'),
|
queryset=ObjectType.objects.with_feature('custom_fields'),
|
||||||
many=True
|
many=True
|
||||||
)
|
)
|
||||||
@ -139,7 +139,7 @@ class CustomFieldSerializer(ValidatedModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = CustomField
|
model = CustomField
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'content_types', 'type', 'object_type', 'data_type', 'name', 'label', 'group_name',
|
'id', 'url', 'display', 'object_types', 'type', 'object_type', 'data_type', 'name', 'label', 'group_name',
|
||||||
'description', 'required', 'search_weight', 'filter_logic', 'ui_visible', 'ui_editable', 'is_cloneable',
|
'description', 'required', 'search_weight', 'filter_logic', 'ui_visible', 'ui_editable', 'is_cloneable',
|
||||||
'default', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'choice_set',
|
'default', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'choice_set',
|
||||||
'created', 'last_updated',
|
'created', 'last_updated',
|
||||||
|
@ -124,10 +124,12 @@ class CustomFieldFilterSet(BaseFilterSet):
|
|||||||
type = django_filters.MultipleChoiceFilter(
|
type = django_filters.MultipleChoiceFilter(
|
||||||
choices=CustomFieldTypeChoices
|
choices=CustomFieldTypeChoices
|
||||||
)
|
)
|
||||||
content_type_id = MultiValueNumberFilter(
|
object_types_id = MultiValueNumberFilter(
|
||||||
field_name='content_types__id'
|
field_name='object_types__id'
|
||||||
|
)
|
||||||
|
object_types = ContentTypeFilter(
|
||||||
|
field_name='object_types'
|
||||||
)
|
)
|
||||||
content_types = ContentTypeFilter()
|
|
||||||
choice_set_id = django_filters.ModelMultipleChoiceFilter(
|
choice_set_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=CustomFieldChoiceSet.objects.all()
|
queryset=CustomFieldChoiceSet.objects.all()
|
||||||
)
|
)
|
||||||
@ -140,7 +142,7 @@ class CustomFieldFilterSet(BaseFilterSet):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = CustomField
|
model = CustomField
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'content_types', 'name', 'group_name', 'required', 'search_weight', 'filter_logic', 'ui_visible',
|
'id', 'object_types', 'name', 'group_name', 'required', 'search_weight', 'filter_logic', 'ui_visible',
|
||||||
'ui_editable', 'weight', 'is_cloneable', 'description',
|
'ui_editable', 'weight', 'is_cloneable', 'description',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -30,8 +30,8 @@ __all__ = (
|
|||||||
|
|
||||||
|
|
||||||
class CustomFieldImportForm(CSVModelForm):
|
class CustomFieldImportForm(CSVModelForm):
|
||||||
content_types = CSVMultipleContentTypeField(
|
object_types = CSVMultipleContentTypeField(
|
||||||
label=_('Content types'),
|
label=_('Object types'),
|
||||||
queryset=ObjectType.objects.with_feature('custom_fields'),
|
queryset=ObjectType.objects.with_feature('custom_fields'),
|
||||||
help_text=_("One or more assigned object types")
|
help_text=_("One or more assigned object types")
|
||||||
)
|
)
|
||||||
@ -69,7 +69,7 @@ class CustomFieldImportForm(CSVModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = CustomField
|
model = CustomField
|
||||||
fields = (
|
fields = (
|
||||||
'name', 'label', 'group_name', 'type', 'content_types', 'object_type', 'required', 'description',
|
'name', 'label', 'group_name', 'type', 'object_types', 'object_type', 'required', 'description',
|
||||||
'search_weight', 'filter_logic', 'default', 'choice_set', 'weight', 'validation_minimum',
|
'search_weight', 'filter_logic', 'default', 'choice_set', 'weight', 'validation_minimum',
|
||||||
'validation_maximum', 'validation_regex', 'ui_visible', 'ui_editable', 'is_cloneable',
|
'validation_maximum', 'validation_regex', 'ui_visible', 'ui_editable', 'is_cloneable',
|
||||||
)
|
)
|
||||||
|
@ -38,11 +38,11 @@ class CustomFieldFilterForm(SavedFiltersMixin, FilterForm):
|
|||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id')),
|
(None, ('q', 'filter_id')),
|
||||||
(_('Attributes'), (
|
(_('Attributes'), (
|
||||||
'type', 'content_type_id', 'group_name', 'weight', 'required', 'choice_set_id', 'ui_visible', 'ui_editable',
|
'type', 'object_types_id', 'group_name', 'weight', 'required', 'choice_set_id', 'ui_visible', 'ui_editable',
|
||||||
'is_cloneable',
|
'is_cloneable',
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
content_type_id = ContentTypeMultipleChoiceField(
|
object_types_id = ContentTypeMultipleChoiceField(
|
||||||
queryset=ObjectType.objects.with_feature('custom_fields'),
|
queryset=ObjectType.objects.with_feature('custom_fields'),
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Object type')
|
label=_('Object type')
|
||||||
|
@ -2,7 +2,6 @@ import json
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
@ -39,8 +38,8 @@ __all__ = (
|
|||||||
|
|
||||||
|
|
||||||
class CustomFieldForm(forms.ModelForm):
|
class CustomFieldForm(forms.ModelForm):
|
||||||
content_types = ContentTypeMultipleChoiceField(
|
object_types = ContentTypeMultipleChoiceField(
|
||||||
label=_('Content types'),
|
label=_('Object types'),
|
||||||
queryset=ObjectType.objects.with_feature('custom_fields')
|
queryset=ObjectType.objects.with_feature('custom_fields')
|
||||||
)
|
)
|
||||||
object_type = ContentTypeChoiceField(
|
object_type = ContentTypeChoiceField(
|
||||||
@ -56,7 +55,7 @@ class CustomFieldForm(forms.ModelForm):
|
|||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Custom Field'), (
|
(_('Custom Field'), (
|
||||||
'content_types', 'name', 'label', 'group_name', 'type', 'object_type', 'required', 'description',
|
'object_types', 'name', 'label', 'group_name', 'type', 'object_type', 'required', 'description',
|
||||||
)),
|
)),
|
||||||
(_('Behavior'), ('search_weight', 'filter_logic', 'ui_visible', 'ui_editable', 'weight', 'is_cloneable')),
|
(_('Behavior'), ('search_weight', 'filter_logic', 'ui_visible', 'ui_editable', 'weight', 'is_cloneable')),
|
||||||
(_('Values'), ('default', 'choice_set')),
|
(_('Values'), ('default', 'choice_set')),
|
||||||
|
@ -39,7 +39,7 @@ class CustomFieldType(ObjectType):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.CustomField
|
model = models.CustomField
|
||||||
exclude = ('content_types', )
|
exclude = ('object_types', 'object_type')
|
||||||
filterset_class = filtersets.CustomFieldFilterSet
|
filterset_class = filtersets.CustomFieldFilterSet
|
||||||
|
|
||||||
|
|
||||||
|
28
netbox/extras/migrations/0111_rename_content_types.py
Normal file
28
netbox/extras/migrations/0111_rename_content_types.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0010_gfk_indexes'),
|
||||||
|
('extras', '0110_remove_eventrule_action_parameters'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='customfield',
|
||||||
|
old_name='content_types',
|
||||||
|
new_name='object_types',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='customfield',
|
||||||
|
name='object_types',
|
||||||
|
field=models.ManyToManyField(related_name='custom_fields', to='core.objecttype'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='customfield',
|
||||||
|
name='object_type',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='core.objecttype'),
|
||||||
|
),
|
||||||
|
]
|
@ -53,7 +53,7 @@ class CustomFieldManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
|||||||
Return all CustomFields assigned to the given model.
|
Return all CustomFields assigned to the given model.
|
||||||
"""
|
"""
|
||||||
content_type = ObjectType.objects.get_for_model(model._meta.concrete_model)
|
content_type = ObjectType.objects.get_for_model(model._meta.concrete_model)
|
||||||
return self.get_queryset().filter(content_types=content_type)
|
return self.get_queryset().filter(object_types=content_type)
|
||||||
|
|
||||||
def get_defaults_for_model(self, model):
|
def get_defaults_for_model(self, model):
|
||||||
"""
|
"""
|
||||||
@ -66,8 +66,8 @@ class CustomFieldManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
|||||||
|
|
||||||
|
|
||||||
class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
||||||
content_types = models.ManyToManyField(
|
object_types = models.ManyToManyField(
|
||||||
to='contenttypes.ContentType',
|
to='core.ObjectType',
|
||||||
related_name='custom_fields',
|
related_name='custom_fields',
|
||||||
help_text=_('The object(s) to which this field applies.')
|
help_text=_('The object(s) to which this field applies.')
|
||||||
)
|
)
|
||||||
@ -79,7 +79,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
|||||||
help_text=_('The type of data this custom field holds')
|
help_text=_('The type of data this custom field holds')
|
||||||
)
|
)
|
||||||
object_type = models.ForeignKey(
|
object_type = models.ForeignKey(
|
||||||
to='contenttypes.ContentType',
|
to='core.ObjectType',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
@ -284,7 +284,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
|||||||
"""
|
"""
|
||||||
Called when a CustomField has been renamed. Updates all assigned object data.
|
Called when a CustomField has been renamed. Updates all assigned object data.
|
||||||
"""
|
"""
|
||||||
for ct in self.content_types.all():
|
for ct in self.object_types.all():
|
||||||
model = ct.model_class()
|
model = ct.model_class()
|
||||||
params = {f'custom_field_data__{old_name}__isnull': False}
|
params = {f'custom_field_data__{old_name}__isnull': False}
|
||||||
instances = model.objects.filter(**params)
|
instances = model.objects.filter(**params)
|
||||||
|
@ -205,13 +205,13 @@ def handle_cf_deleted(instance, **kwargs):
|
|||||||
"""
|
"""
|
||||||
Handle the cleanup of old custom field data when a CustomField is deleted.
|
Handle the cleanup of old custom field data when a CustomField is deleted.
|
||||||
"""
|
"""
|
||||||
instance.remove_stale_data(instance.content_types.all())
|
instance.remove_stale_data(instance.object_types.all())
|
||||||
|
|
||||||
|
|
||||||
post_save.connect(handle_cf_renamed, sender=CustomField)
|
post_save.connect(handle_cf_renamed, sender=CustomField)
|
||||||
pre_delete.connect(handle_cf_deleted, sender=CustomField)
|
pre_delete.connect(handle_cf_deleted, sender=CustomField)
|
||||||
m2m_changed.connect(handle_cf_added_obj_types, sender=CustomField.content_types.through)
|
m2m_changed.connect(handle_cf_added_obj_types, sender=CustomField.object_types.through)
|
||||||
m2m_changed.connect(handle_cf_removed_obj_types, sender=CustomField.content_types.through)
|
m2m_changed.connect(handle_cf_removed_obj_types, sender=CustomField.object_types.through)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -7,10 +7,10 @@ from django.utils.timezone import make_aware
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from core.choices import ManagedFileRootPathChoices
|
from core.choices import ManagedFileRootPathChoices
|
||||||
|
from core.models import ObjectType
|
||||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, Location, RackRole, Site
|
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, Location, RackRole, Site
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.models import *
|
from extras.models import *
|
||||||
from extras.reports import Report
|
|
||||||
from extras.scripts import BooleanVar, IntegerVar, Script as PythonClass, StringVar
|
from extras.scripts import BooleanVar, IntegerVar, Script as PythonClass, StringVar
|
||||||
from utilities.testing import APITestCase, APIViewTestCases
|
from utilities.testing import APITestCase, APIViewTestCases
|
||||||
|
|
||||||
@ -152,17 +152,17 @@ class CustomFieldTest(APIViewTestCases.APIViewTestCase):
|
|||||||
brief_fields = ['description', 'display', 'id', 'name', 'url']
|
brief_fields = ['description', 'display', 'id', 'name', 'url']
|
||||||
create_data = [
|
create_data = [
|
||||||
{
|
{
|
||||||
'content_types': ['dcim.site'],
|
'object_types': ['dcim.site'],
|
||||||
'name': 'cf4',
|
'name': 'cf4',
|
||||||
'type': 'date',
|
'type': 'date',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'content_types': ['dcim.site'],
|
'object_types': ['dcim.site'],
|
||||||
'name': 'cf5',
|
'name': 'cf5',
|
||||||
'type': 'url',
|
'type': 'url',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'content_types': ['dcim.site'],
|
'object_types': ['dcim.site'],
|
||||||
'name': 'cf6',
|
'name': 'cf6',
|
||||||
'type': 'text',
|
'type': 'text',
|
||||||
},
|
},
|
||||||
@ -171,14 +171,14 @@ class CustomFieldTest(APIViewTestCases.APIViewTestCase):
|
|||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
}
|
}
|
||||||
update_data = {
|
update_data = {
|
||||||
'content_types': ['dcim.device'],
|
'object_types': ['dcim.device'],
|
||||||
'name': 'New_Name',
|
'name': 'New_Name',
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
site_ct = ContentType.objects.get_for_model(Site)
|
site_ct = ObjectType.objects.get_for_model(Site)
|
||||||
|
|
||||||
custom_fields = (
|
custom_fields = (
|
||||||
CustomField(
|
CustomField(
|
||||||
@ -196,7 +196,7 @@ class CustomFieldTest(APIViewTestCases.APIViewTestCase):
|
|||||||
)
|
)
|
||||||
CustomField.objects.bulk_create(custom_fields)
|
CustomField.objects.bulk_create(custom_fields)
|
||||||
for cf in custom_fields:
|
for cf in custom_fields:
|
||||||
cf.content_types.add(site_ct)
|
cf.object_types.add(site_ct)
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldChoiceSetTest(APIViewTestCases.APIViewTestCase):
|
class CustomFieldChoiceSetTest(APIViewTestCases.APIViewTestCase):
|
||||||
|
@ -3,6 +3,7 @@ from django.test import override_settings
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
|
from core.models import ObjectType
|
||||||
from dcim.choices import SiteStatusChoices
|
from dcim.choices import SiteStatusChoices
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
@ -23,14 +24,14 @@ class ChangeLogViewTest(ModelViewTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Create a custom field on the Site model
|
# Create a custom field on the Site model
|
||||||
ct = ContentType.objects.get_for_model(Site)
|
site_type = ObjectType.objects.get_for_model(Site)
|
||||||
cf = CustomField(
|
cf = CustomField(
|
||||||
type=CustomFieldTypeChoices.TYPE_TEXT,
|
type=CustomFieldTypeChoices.TYPE_TEXT,
|
||||||
name='cf1',
|
name='cf1',
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
cf.save()
|
cf.save()
|
||||||
cf.content_types.set([ct])
|
cf.object_types.set([site_type])
|
||||||
|
|
||||||
# Create a select custom field on the Site model
|
# Create a select custom field on the Site model
|
||||||
cf_select = CustomField(
|
cf_select = CustomField(
|
||||||
@ -40,7 +41,7 @@ class ChangeLogViewTest(ModelViewTestCase):
|
|||||||
choice_set=choice_set
|
choice_set=choice_set
|
||||||
)
|
)
|
||||||
cf_select.save()
|
cf_select.save()
|
||||||
cf_select.content_types.set([ct])
|
cf_select.object_types.set([site_type])
|
||||||
|
|
||||||
def test_create_object(self):
|
def test_create_object(self):
|
||||||
tags = create_tags('Tag 1', 'Tag 2')
|
tags = create_tags('Tag 1', 'Tag 2')
|
||||||
@ -275,14 +276,14 @@ class ChangeLogAPITest(APITestCase):
|
|||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
|
||||||
# Create a custom field on the Site model
|
# Create a custom field on the Site model
|
||||||
ct = ContentType.objects.get_for_model(Site)
|
site_type = ObjectType.objects.get_for_model(Site)
|
||||||
cf = CustomField(
|
cf = CustomField(
|
||||||
type=CustomFieldTypeChoices.TYPE_TEXT,
|
type=CustomFieldTypeChoices.TYPE_TEXT,
|
||||||
name='cf1',
|
name='cf1',
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
cf.save()
|
cf.save()
|
||||||
cf.content_types.set([ct])
|
cf.object_types.set([site_type])
|
||||||
|
|
||||||
# Create a select custom field on the Site model
|
# Create a select custom field on the Site model
|
||||||
choice_set = CustomFieldChoiceSet.objects.create(
|
choice_set = CustomFieldChoiceSet.objects.create(
|
||||||
@ -296,7 +297,7 @@ class ChangeLogAPITest(APITestCase):
|
|||||||
choice_set=choice_set
|
choice_set=choice_set
|
||||||
)
|
)
|
||||||
cf_select.save()
|
cf_select.save()
|
||||||
cf_select.content_types.set([ct])
|
cf_select.object_types.set([site_type])
|
||||||
|
|
||||||
# Create some tags
|
# Create some tags
|
||||||
tags = (
|
tags = (
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
|
from core.models import ObjectType
|
||||||
from dcim.filtersets import SiteFilterSet
|
from dcim.filtersets import SiteFilterSet
|
||||||
from dcim.forms import SiteImportForm
|
from dcim.forms import SiteImportForm
|
||||||
from dcim.models import Manufacturer, Rack, Site
|
from dcim.models import Manufacturer, Rack, Site
|
||||||
@ -28,7 +28,7 @@ class CustomFieldTest(TestCase):
|
|||||||
Site(name='Site C', slug='site-c'),
|
Site(name='Site C', slug='site-c'),
|
||||||
])
|
])
|
||||||
|
|
||||||
cls.object_type = ContentType.objects.get_for_model(Site)
|
cls.object_type = ObjectType.objects.get_for_model(Site)
|
||||||
|
|
||||||
def test_invalid_name(self):
|
def test_invalid_name(self):
|
||||||
"""
|
"""
|
||||||
@ -50,7 +50,7 @@ class CustomFieldTest(TestCase):
|
|||||||
type=CustomFieldTypeChoices.TYPE_TEXT,
|
type=CustomFieldTypeChoices.TYPE_TEXT,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
cf.content_types.set([self.object_type])
|
cf.object_types.set([self.object_type])
|
||||||
instance = Site.objects.first()
|
instance = Site.objects.first()
|
||||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ class CustomFieldTest(TestCase):
|
|||||||
type=CustomFieldTypeChoices.TYPE_LONGTEXT,
|
type=CustomFieldTypeChoices.TYPE_LONGTEXT,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
cf.content_types.set([self.object_type])
|
cf.object_types.set([self.object_type])
|
||||||
instance = Site.objects.first()
|
instance = Site.objects.first()
|
||||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ class CustomFieldTest(TestCase):
|
|||||||
type=CustomFieldTypeChoices.TYPE_INTEGER,
|
type=CustomFieldTypeChoices.TYPE_INTEGER,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
cf.content_types.set([self.object_type])
|
cf.object_types.set([self.object_type])
|
||||||
instance = Site.objects.first()
|
instance = Site.objects.first()
|
||||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ class CustomFieldTest(TestCase):
|
|||||||
type=CustomFieldTypeChoices.TYPE_DECIMAL,
|
type=CustomFieldTypeChoices.TYPE_DECIMAL,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
cf.content_types.set([self.object_type])
|
cf.object_types.set([self.object_type])
|
||||||
instance = Site.objects.first()
|
instance = Site.objects.first()
|
||||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||||
|
|
||||||
@ -151,7 +151,7 @@ class CustomFieldTest(TestCase):
|
|||||||
type=CustomFieldTypeChoices.TYPE_INTEGER,
|
type=CustomFieldTypeChoices.TYPE_INTEGER,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
cf.content_types.set([self.object_type])
|
cf.object_types.set([self.object_type])
|
||||||
instance = Site.objects.first()
|
instance = Site.objects.first()
|
||||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ class CustomFieldTest(TestCase):
|
|||||||
type=CustomFieldTypeChoices.TYPE_DATE,
|
type=CustomFieldTypeChoices.TYPE_DATE,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
cf.content_types.set([self.object_type])
|
cf.object_types.set([self.object_type])
|
||||||
instance = Site.objects.first()
|
instance = Site.objects.first()
|
||||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||||
|
|
||||||
@ -203,7 +203,7 @@ class CustomFieldTest(TestCase):
|
|||||||
type=CustomFieldTypeChoices.TYPE_DATETIME,
|
type=CustomFieldTypeChoices.TYPE_DATETIME,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
cf.content_types.set([self.object_type])
|
cf.object_types.set([self.object_type])
|
||||||
instance = Site.objects.first()
|
instance = Site.objects.first()
|
||||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||||
|
|
||||||
@ -228,7 +228,7 @@ class CustomFieldTest(TestCase):
|
|||||||
type=CustomFieldTypeChoices.TYPE_URL,
|
type=CustomFieldTypeChoices.TYPE_URL,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
cf.content_types.set([self.object_type])
|
cf.object_types.set([self.object_type])
|
||||||
instance = Site.objects.first()
|
instance = Site.objects.first()
|
||||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||||
|
|
||||||
@ -253,7 +253,7 @@ class CustomFieldTest(TestCase):
|
|||||||
type=CustomFieldTypeChoices.TYPE_JSON,
|
type=CustomFieldTypeChoices.TYPE_JSON,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
cf.content_types.set([self.object_type])
|
cf.object_types.set([self.object_type])
|
||||||
instance = Site.objects.first()
|
instance = Site.objects.first()
|
||||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||||
|
|
||||||
@ -290,7 +290,7 @@ class CustomFieldTest(TestCase):
|
|||||||
required=False,
|
required=False,
|
||||||
choice_set=choice_set
|
choice_set=choice_set
|
||||||
)
|
)
|
||||||
cf.content_types.set([self.object_type])
|
cf.object_types.set([self.object_type])
|
||||||
instance = Site.objects.first()
|
instance = Site.objects.first()
|
||||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||||
|
|
||||||
@ -327,7 +327,7 @@ class CustomFieldTest(TestCase):
|
|||||||
required=False,
|
required=False,
|
||||||
choice_set=choice_set
|
choice_set=choice_set
|
||||||
)
|
)
|
||||||
cf.content_types.set([self.object_type])
|
cf.object_types.set([self.object_type])
|
||||||
instance = Site.objects.first()
|
instance = Site.objects.first()
|
||||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||||
|
|
||||||
@ -350,10 +350,10 @@ class CustomFieldTest(TestCase):
|
|||||||
cf = CustomField.objects.create(
|
cf = CustomField.objects.create(
|
||||||
name='object_field',
|
name='object_field',
|
||||||
type=CustomFieldTypeChoices.TYPE_OBJECT,
|
type=CustomFieldTypeChoices.TYPE_OBJECT,
|
||||||
object_type=ContentType.objects.get_for_model(VLAN),
|
object_type=ObjectType.objects.get_for_model(VLAN),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
cf.content_types.set([self.object_type])
|
cf.object_types.set([self.object_type])
|
||||||
instance = Site.objects.first()
|
instance = Site.objects.first()
|
||||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||||
|
|
||||||
@ -382,10 +382,10 @@ class CustomFieldTest(TestCase):
|
|||||||
cf = CustomField.objects.create(
|
cf = CustomField.objects.create(
|
||||||
name='object_field',
|
name='object_field',
|
||||||
type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
|
type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
|
||||||
object_type=ContentType.objects.get_for_model(VLAN),
|
object_type=ObjectType.objects.get_for_model(VLAN),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
cf.content_types.set([self.object_type])
|
cf.object_types.set([self.object_type])
|
||||||
instance = Site.objects.first()
|
instance = Site.objects.first()
|
||||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||||
|
|
||||||
@ -402,13 +402,13 @@ class CustomFieldTest(TestCase):
|
|||||||
self.assertIsNone(instance.custom_field_data.get(cf.name))
|
self.assertIsNone(instance.custom_field_data.get(cf.name))
|
||||||
|
|
||||||
def test_rename_customfield(self):
|
def test_rename_customfield(self):
|
||||||
obj_type = ContentType.objects.get_for_model(Site)
|
obj_type = ObjectType.objects.get_for_model(Site)
|
||||||
FIELD_DATA = 'abc'
|
FIELD_DATA = 'abc'
|
||||||
|
|
||||||
# Create a custom field
|
# Create a custom field
|
||||||
cf = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='field1')
|
cf = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='field1')
|
||||||
cf.save()
|
cf.save()
|
||||||
cf.content_types.set([obj_type])
|
cf.object_types.set([obj_type])
|
||||||
|
|
||||||
# Assign custom field data to an object
|
# Assign custom field data to an object
|
||||||
site = Site.objects.create(
|
site = Site.objects.create(
|
||||||
@ -437,7 +437,7 @@ class CustomFieldTest(TestCase):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
site = Site.objects.create(name='Site 1', slug='site-1')
|
site = Site.objects.create(name='Site 1', slug='site-1')
|
||||||
object_type = ContentType.objects.get_for_model(Site)
|
object_type = ObjectType.objects.get_for_model(Site)
|
||||||
|
|
||||||
# Text
|
# Text
|
||||||
CustomField(name='test', type='text', required=True, default="Default text").full_clean()
|
CustomField(name='test', type='text', required=True, default="Default text").full_clean()
|
||||||
@ -524,10 +524,10 @@ class CustomFieldManagerTest(TestCase):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
content_type = ContentType.objects.get_for_model(Site)
|
object_type = ObjectType.objects.get_for_model(Site)
|
||||||
custom_field = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='text_field', default='foo')
|
custom_field = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='text_field', default='foo')
|
||||||
custom_field.save()
|
custom_field.save()
|
||||||
custom_field.content_types.set([content_type])
|
custom_field.object_types.set([object_type])
|
||||||
|
|
||||||
def test_get_for_model(self):
|
def test_get_for_model(self):
|
||||||
self.assertEqual(CustomField.objects.get_for_model(Site).count(), 1)
|
self.assertEqual(CustomField.objects.get_for_model(Site).count(), 1)
|
||||||
@ -538,7 +538,7 @@ class CustomFieldAPITest(APITestCase):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
content_type = ContentType.objects.get_for_model(Site)
|
object_type = ObjectType.objects.get_for_model(Site)
|
||||||
|
|
||||||
# Create some VLANs
|
# Create some VLANs
|
||||||
vlans = (
|
vlans = (
|
||||||
@ -581,19 +581,19 @@ class CustomFieldAPITest(APITestCase):
|
|||||||
CustomField(
|
CustomField(
|
||||||
type=CustomFieldTypeChoices.TYPE_OBJECT,
|
type=CustomFieldTypeChoices.TYPE_OBJECT,
|
||||||
name='object_field',
|
name='object_field',
|
||||||
object_type=ContentType.objects.get_for_model(VLAN),
|
object_type=ObjectType.objects.get_for_model(VLAN),
|
||||||
default=vlans[0].pk,
|
default=vlans[0].pk,
|
||||||
),
|
),
|
||||||
CustomField(
|
CustomField(
|
||||||
type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
|
type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
|
||||||
name='multiobject_field',
|
name='multiobject_field',
|
||||||
object_type=ContentType.objects.get_for_model(VLAN),
|
object_type=ObjectType.objects.get_for_model(VLAN),
|
||||||
default=[vlans[0].pk, vlans[1].pk],
|
default=[vlans[0].pk, vlans[1].pk],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
for cf in custom_fields:
|
for cf in custom_fields:
|
||||||
cf.save()
|
cf.save()
|
||||||
cf.content_types.set([content_type])
|
cf.object_types.set([object_type])
|
||||||
|
|
||||||
# Create some sites *after* creating the custom fields. This ensures that
|
# Create some sites *after* creating the custom fields. This ensures that
|
||||||
# default values are not set for the assigned objects.
|
# default values are not set for the assigned objects.
|
||||||
@ -1163,7 +1163,7 @@ class CustomFieldImportTest(TestCase):
|
|||||||
)
|
)
|
||||||
for cf in custom_fields:
|
for cf in custom_fields:
|
||||||
cf.save()
|
cf.save()
|
||||||
cf.content_types.set([ContentType.objects.get_for_model(Site)])
|
cf.object_types.set([ObjectType.objects.get_for_model(Site)])
|
||||||
|
|
||||||
def test_import(self):
|
def test_import(self):
|
||||||
"""
|
"""
|
||||||
@ -1256,11 +1256,11 @@ class CustomFieldModelTest(TestCase):
|
|||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
cf1 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='foo')
|
cf1 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='foo')
|
||||||
cf1.save()
|
cf1.save()
|
||||||
cf1.content_types.set([ContentType.objects.get_for_model(Site)])
|
cf1.object_types.set([ObjectType.objects.get_for_model(Site)])
|
||||||
|
|
||||||
cf2 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='bar')
|
cf2 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='bar')
|
||||||
cf2.save()
|
cf2.save()
|
||||||
cf2.content_types.set([ContentType.objects.get_for_model(Rack)])
|
cf2.object_types.set([ObjectType.objects.get_for_model(Rack)])
|
||||||
|
|
||||||
def test_cf_data(self):
|
def test_cf_data(self):
|
||||||
"""
|
"""
|
||||||
@ -1299,7 +1299,7 @@ class CustomFieldModelTest(TestCase):
|
|||||||
"""
|
"""
|
||||||
cf3 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='baz', required=True)
|
cf3 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='baz', required=True)
|
||||||
cf3.save()
|
cf3.save()
|
||||||
cf3.content_types.set([ContentType.objects.get_for_model(Site)])
|
cf3.object_types.set([ObjectType.objects.get_for_model(Site)])
|
||||||
|
|
||||||
site = Site(name='Test Site', slug='test-site')
|
site = Site(name='Test Site', slug='test-site')
|
||||||
|
|
||||||
@ -1318,7 +1318,7 @@ class CustomFieldModelFilterTest(TestCase):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
obj_type = ContentType.objects.get_for_model(Site)
|
object_type = ObjectType.objects.get_for_model(Site)
|
||||||
|
|
||||||
manufacturers = Manufacturer.objects.bulk_create((
|
manufacturers = Manufacturer.objects.bulk_create((
|
||||||
Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
|
Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
|
||||||
@ -1335,17 +1335,17 @@ class CustomFieldModelFilterTest(TestCase):
|
|||||||
# Integer filtering
|
# Integer filtering
|
||||||
cf = CustomField(name='cf1', type=CustomFieldTypeChoices.TYPE_INTEGER)
|
cf = CustomField(name='cf1', type=CustomFieldTypeChoices.TYPE_INTEGER)
|
||||||
cf.save()
|
cf.save()
|
||||||
cf.content_types.set([obj_type])
|
cf.object_types.set([object_type])
|
||||||
|
|
||||||
# Decimal filtering
|
# Decimal filtering
|
||||||
cf = CustomField(name='cf2', type=CustomFieldTypeChoices.TYPE_DECIMAL)
|
cf = CustomField(name='cf2', type=CustomFieldTypeChoices.TYPE_DECIMAL)
|
||||||
cf.save()
|
cf.save()
|
||||||
cf.content_types.set([obj_type])
|
cf.object_types.set([object_type])
|
||||||
|
|
||||||
# Boolean filtering
|
# Boolean filtering
|
||||||
cf = CustomField(name='cf3', type=CustomFieldTypeChoices.TYPE_BOOLEAN)
|
cf = CustomField(name='cf3', type=CustomFieldTypeChoices.TYPE_BOOLEAN)
|
||||||
cf.save()
|
cf.save()
|
||||||
cf.content_types.set([obj_type])
|
cf.object_types.set([object_type])
|
||||||
|
|
||||||
# Exact text filtering
|
# Exact text filtering
|
||||||
cf = CustomField(
|
cf = CustomField(
|
||||||
@ -1354,7 +1354,7 @@ class CustomFieldModelFilterTest(TestCase):
|
|||||||
filter_logic=CustomFieldFilterLogicChoices.FILTER_EXACT
|
filter_logic=CustomFieldFilterLogicChoices.FILTER_EXACT
|
||||||
)
|
)
|
||||||
cf.save()
|
cf.save()
|
||||||
cf.content_types.set([obj_type])
|
cf.object_types.set([object_type])
|
||||||
|
|
||||||
# Loose text filtering
|
# Loose text filtering
|
||||||
cf = CustomField(
|
cf = CustomField(
|
||||||
@ -1363,12 +1363,12 @@ class CustomFieldModelFilterTest(TestCase):
|
|||||||
filter_logic=CustomFieldFilterLogicChoices.FILTER_LOOSE
|
filter_logic=CustomFieldFilterLogicChoices.FILTER_LOOSE
|
||||||
)
|
)
|
||||||
cf.save()
|
cf.save()
|
||||||
cf.content_types.set([obj_type])
|
cf.object_types.set([object_type])
|
||||||
|
|
||||||
# Date filtering
|
# Date filtering
|
||||||
cf = CustomField(name='cf6', type=CustomFieldTypeChoices.TYPE_DATE)
|
cf = CustomField(name='cf6', type=CustomFieldTypeChoices.TYPE_DATE)
|
||||||
cf.save()
|
cf.save()
|
||||||
cf.content_types.set([obj_type])
|
cf.object_types.set([object_type])
|
||||||
|
|
||||||
# Exact URL filtering
|
# Exact URL filtering
|
||||||
cf = CustomField(
|
cf = CustomField(
|
||||||
@ -1377,7 +1377,7 @@ class CustomFieldModelFilterTest(TestCase):
|
|||||||
filter_logic=CustomFieldFilterLogicChoices.FILTER_EXACT
|
filter_logic=CustomFieldFilterLogicChoices.FILTER_EXACT
|
||||||
)
|
)
|
||||||
cf.save()
|
cf.save()
|
||||||
cf.content_types.set([obj_type])
|
cf.object_types.set([object_type])
|
||||||
|
|
||||||
# Loose URL filtering
|
# Loose URL filtering
|
||||||
cf = CustomField(
|
cf = CustomField(
|
||||||
@ -1386,7 +1386,7 @@ class CustomFieldModelFilterTest(TestCase):
|
|||||||
filter_logic=CustomFieldFilterLogicChoices.FILTER_LOOSE
|
filter_logic=CustomFieldFilterLogicChoices.FILTER_LOOSE
|
||||||
)
|
)
|
||||||
cf.save()
|
cf.save()
|
||||||
cf.content_types.set([obj_type])
|
cf.object_types.set([object_type])
|
||||||
|
|
||||||
# Selection filtering
|
# Selection filtering
|
||||||
cf = CustomField(
|
cf = CustomField(
|
||||||
@ -1395,7 +1395,7 @@ class CustomFieldModelFilterTest(TestCase):
|
|||||||
choice_set=choice_set
|
choice_set=choice_set
|
||||||
)
|
)
|
||||||
cf.save()
|
cf.save()
|
||||||
cf.content_types.set([obj_type])
|
cf.object_types.set([object_type])
|
||||||
|
|
||||||
# Multiselect filtering
|
# Multiselect filtering
|
||||||
cf = CustomField(
|
cf = CustomField(
|
||||||
@ -1404,25 +1404,25 @@ class CustomFieldModelFilterTest(TestCase):
|
|||||||
choice_set=choice_set
|
choice_set=choice_set
|
||||||
)
|
)
|
||||||
cf.save()
|
cf.save()
|
||||||
cf.content_types.set([obj_type])
|
cf.object_types.set([object_type])
|
||||||
|
|
||||||
# Object filtering
|
# Object filtering
|
||||||
cf = CustomField(
|
cf = CustomField(
|
||||||
name='cf11',
|
name='cf11',
|
||||||
type=CustomFieldTypeChoices.TYPE_OBJECT,
|
type=CustomFieldTypeChoices.TYPE_OBJECT,
|
||||||
object_type=ContentType.objects.get_for_model(Manufacturer)
|
object_type=ObjectType.objects.get_for_model(Manufacturer)
|
||||||
)
|
)
|
||||||
cf.save()
|
cf.save()
|
||||||
cf.content_types.set([obj_type])
|
cf.object_types.set([object_type])
|
||||||
|
|
||||||
# Multi-object filtering
|
# Multi-object filtering
|
||||||
cf = CustomField(
|
cf = CustomField(
|
||||||
name='cf12',
|
name='cf12',
|
||||||
type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
|
type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
|
||||||
object_type=ContentType.objects.get_for_model(Manufacturer)
|
object_type=ObjectType.objects.get_for_model(Manufacturer)
|
||||||
)
|
)
|
||||||
cf.save()
|
cf.save()
|
||||||
cf.content_types.set([obj_type])
|
cf.object_types.set([object_type])
|
||||||
|
|
||||||
Site.objects.bulk_create([
|
Site.objects.bulk_create([
|
||||||
Site(name='Site 1', slug='site-1', custom_field_data={
|
Site(name='Site 1', slug='site-1', custom_field_data={
|
||||||
|
@ -7,6 +7,7 @@ from django.test import TestCase
|
|||||||
|
|
||||||
from circuits.models import Provider
|
from circuits.models import Provider
|
||||||
from core.choices import ManagedFileRootPathChoices
|
from core.choices import ManagedFileRootPathChoices
|
||||||
|
from core.models import ObjectType
|
||||||
from dcim.filtersets import SiteFilterSet
|
from dcim.filtersets import SiteFilterSet
|
||||||
from dcim.models import DeviceRole, DeviceType, Manufacturer, Platform, Rack, Region, Site, SiteGroup
|
from dcim.models import DeviceRole, DeviceType, Manufacturer, Platform, Rack, Region, Site, SiteGroup
|
||||||
from dcim.models import Location
|
from dcim.models import Location
|
||||||
@ -87,11 +88,11 @@ class CustomFieldTestCase(TestCase, BaseFilterSetTests):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
CustomField.objects.bulk_create(custom_fields)
|
CustomField.objects.bulk_create(custom_fields)
|
||||||
custom_fields[0].content_types.add(ContentType.objects.get_by_natural_key('dcim', 'site'))
|
custom_fields[0].object_types.add(ObjectType.objects.get_by_natural_key('dcim', 'site'))
|
||||||
custom_fields[1].content_types.add(ContentType.objects.get_by_natural_key('dcim', 'rack'))
|
custom_fields[1].object_types.add(ObjectType.objects.get_by_natural_key('dcim', 'rack'))
|
||||||
custom_fields[2].content_types.add(ContentType.objects.get_by_natural_key('dcim', 'device'))
|
custom_fields[2].object_types.add(ObjectType.objects.get_by_natural_key('dcim', 'device'))
|
||||||
custom_fields[3].content_types.add(ContentType.objects.get_by_natural_key('dcim', 'device'))
|
custom_fields[3].object_types.add(ObjectType.objects.get_by_natural_key('dcim', 'device'))
|
||||||
custom_fields[4].content_types.add(ContentType.objects.get_by_natural_key('dcim', 'device'))
|
custom_fields[4].object_types.add(ObjectType.objects.get_by_natural_key('dcim', 'device'))
|
||||||
|
|
||||||
def test_q(self):
|
def test_q(self):
|
||||||
params = {'q': 'foobar1'}
|
params = {'q': 'foobar1'}
|
||||||
@ -101,10 +102,10 @@ class CustomFieldTestCase(TestCase, BaseFilterSetTests):
|
|||||||
params = {'name': ['Custom Field 1', 'Custom Field 2']}
|
params = {'name': ['Custom Field 1', 'Custom Field 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_content_types(self):
|
def test_object_types(self):
|
||||||
params = {'content_types': 'dcim.site'}
|
params = {'object_types': 'dcim.site'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
params = {'content_type_id': [ContentType.objects.get_by_natural_key('dcim', 'site').pk]}
|
params = {'object_types_id': [ObjectType.objects.get_by_natural_key('dcim', 'site').pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
def test_required(self):
|
def test_required(self):
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from core.models import ObjectType
|
||||||
from dcim.forms import SiteForm
|
from dcim.forms import SiteForm
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from extras.choices import CustomFieldTypeChoices
|
from extras.choices import CustomFieldTypeChoices
|
||||||
@ -12,66 +13,66 @@ class CustomFieldModelFormTest(TestCase):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
obj_type = ContentType.objects.get_for_model(Site)
|
object_type = ObjectType.objects.get_for_model(Site)
|
||||||
choice_set = CustomFieldChoiceSet.objects.create(
|
choice_set = CustomFieldChoiceSet.objects.create(
|
||||||
name='Choice Set 1',
|
name='Choice Set 1',
|
||||||
extra_choices=(('a', 'A'), ('b', 'B'), ('c', 'C'))
|
extra_choices=(('a', 'A'), ('b', 'B'), ('c', 'C'))
|
||||||
)
|
)
|
||||||
|
|
||||||
cf_text = CustomField.objects.create(name='text', type=CustomFieldTypeChoices.TYPE_TEXT)
|
cf_text = CustomField.objects.create(name='text', type=CustomFieldTypeChoices.TYPE_TEXT)
|
||||||
cf_text.content_types.set([obj_type])
|
cf_text.object_types.set([object_type])
|
||||||
|
|
||||||
cf_longtext = CustomField.objects.create(name='longtext', type=CustomFieldTypeChoices.TYPE_LONGTEXT)
|
cf_longtext = CustomField.objects.create(name='longtext', type=CustomFieldTypeChoices.TYPE_LONGTEXT)
|
||||||
cf_longtext.content_types.set([obj_type])
|
cf_longtext.object_types.set([object_type])
|
||||||
|
|
||||||
cf_integer = CustomField.objects.create(name='integer', type=CustomFieldTypeChoices.TYPE_INTEGER)
|
cf_integer = CustomField.objects.create(name='integer', type=CustomFieldTypeChoices.TYPE_INTEGER)
|
||||||
cf_integer.content_types.set([obj_type])
|
cf_integer.object_types.set([object_type])
|
||||||
|
|
||||||
cf_integer = CustomField.objects.create(name='decimal', type=CustomFieldTypeChoices.TYPE_DECIMAL)
|
cf_integer = CustomField.objects.create(name='decimal', type=CustomFieldTypeChoices.TYPE_DECIMAL)
|
||||||
cf_integer.content_types.set([obj_type])
|
cf_integer.object_types.set([object_type])
|
||||||
|
|
||||||
cf_boolean = CustomField.objects.create(name='boolean', type=CustomFieldTypeChoices.TYPE_BOOLEAN)
|
cf_boolean = CustomField.objects.create(name='boolean', type=CustomFieldTypeChoices.TYPE_BOOLEAN)
|
||||||
cf_boolean.content_types.set([obj_type])
|
cf_boolean.object_types.set([object_type])
|
||||||
|
|
||||||
cf_date = CustomField.objects.create(name='date', type=CustomFieldTypeChoices.TYPE_DATE)
|
cf_date = CustomField.objects.create(name='date', type=CustomFieldTypeChoices.TYPE_DATE)
|
||||||
cf_date.content_types.set([obj_type])
|
cf_date.object_types.set([object_type])
|
||||||
|
|
||||||
cf_datetime = CustomField.objects.create(name='datetime', type=CustomFieldTypeChoices.TYPE_DATETIME)
|
cf_datetime = CustomField.objects.create(name='datetime', type=CustomFieldTypeChoices.TYPE_DATETIME)
|
||||||
cf_datetime.content_types.set([obj_type])
|
cf_datetime.object_types.set([object_type])
|
||||||
|
|
||||||
cf_url = CustomField.objects.create(name='url', type=CustomFieldTypeChoices.TYPE_URL)
|
cf_url = CustomField.objects.create(name='url', type=CustomFieldTypeChoices.TYPE_URL)
|
||||||
cf_url.content_types.set([obj_type])
|
cf_url.object_types.set([object_type])
|
||||||
|
|
||||||
cf_json = CustomField.objects.create(name='json', type=CustomFieldTypeChoices.TYPE_JSON)
|
cf_json = CustomField.objects.create(name='json', type=CustomFieldTypeChoices.TYPE_JSON)
|
||||||
cf_json.content_types.set([obj_type])
|
cf_json.object_types.set([object_type])
|
||||||
|
|
||||||
cf_select = CustomField.objects.create(
|
cf_select = CustomField.objects.create(
|
||||||
name='select',
|
name='select',
|
||||||
type=CustomFieldTypeChoices.TYPE_SELECT,
|
type=CustomFieldTypeChoices.TYPE_SELECT,
|
||||||
choice_set=choice_set
|
choice_set=choice_set
|
||||||
)
|
)
|
||||||
cf_select.content_types.set([obj_type])
|
cf_select.object_types.set([object_type])
|
||||||
|
|
||||||
cf_multiselect = CustomField.objects.create(
|
cf_multiselect = CustomField.objects.create(
|
||||||
name='multiselect',
|
name='multiselect',
|
||||||
type=CustomFieldTypeChoices.TYPE_MULTISELECT,
|
type=CustomFieldTypeChoices.TYPE_MULTISELECT,
|
||||||
choice_set=choice_set
|
choice_set=choice_set
|
||||||
)
|
)
|
||||||
cf_multiselect.content_types.set([obj_type])
|
cf_multiselect.object_types.set([object_type])
|
||||||
|
|
||||||
cf_object = CustomField.objects.create(
|
cf_object = CustomField.objects.create(
|
||||||
name='object',
|
name='object',
|
||||||
type=CustomFieldTypeChoices.TYPE_OBJECT,
|
type=CustomFieldTypeChoices.TYPE_OBJECT,
|
||||||
object_type=ContentType.objects.get_for_model(Site)
|
object_type=ContentType.objects.get_for_model(Site)
|
||||||
)
|
)
|
||||||
cf_object.content_types.set([obj_type])
|
cf_object.object_types.set([object_type])
|
||||||
|
|
||||||
cf_multiobject = CustomField.objects.create(
|
cf_multiobject = CustomField.objects.create(
|
||||||
name='multiobject',
|
name='multiobject',
|
||||||
type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
|
type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
|
||||||
object_type=ContentType.objects.get_for_model(Site)
|
object_type=ContentType.objects.get_for_model(Site)
|
||||||
)
|
)
|
||||||
cf_multiobject.content_types.set([obj_type])
|
cf_multiobject.object_types.set([object_type])
|
||||||
|
|
||||||
def test_empty_values(self):
|
def test_empty_values(self):
|
||||||
"""
|
"""
|
||||||
|
@ -5,6 +5,7 @@ from django.contrib.auth import get_user_model
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from core.models import ObjectType
|
||||||
from dcim.models import DeviceType, Manufacturer, Site
|
from dcim.models import DeviceType, Manufacturer, Site
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.models import *
|
from extras.models import *
|
||||||
@ -19,7 +20,7 @@ class CustomFieldTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
|
||||||
site_ct = ContentType.objects.get_for_model(Site)
|
site_type = ObjectType.objects.get_for_model(Site)
|
||||||
CustomFieldChoiceSet.objects.create(
|
CustomFieldChoiceSet.objects.create(
|
||||||
name='Choice Set 1',
|
name='Choice Set 1',
|
||||||
extra_choices=(
|
extra_choices=(
|
||||||
@ -36,13 +37,13 @@ class CustomFieldTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
)
|
)
|
||||||
for customfield in custom_fields:
|
for customfield in custom_fields:
|
||||||
customfield.save()
|
customfield.save()
|
||||||
customfield.content_types.add(site_ct)
|
customfield.object_types.add(site_type)
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'name': 'field_x',
|
'name': 'field_x',
|
||||||
'label': 'Field X',
|
'label': 'Field X',
|
||||||
'type': 'text',
|
'type': 'text',
|
||||||
'content_types': [site_ct.pk],
|
'object_types': [site_type.pk],
|
||||||
'search_weight': 2000,
|
'search_weight': 2000,
|
||||||
'filter_logic': CustomFieldFilterLogicChoices.FILTER_EXACT,
|
'filter_logic': CustomFieldFilterLogicChoices.FILTER_EXACT,
|
||||||
'default': None,
|
'default': None,
|
||||||
@ -53,7 +54,7 @@ class CustomFieldTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
'name,label,type,content_types,object_type,weight,search_weight,filter_logic,choice_set,validation_minimum,validation_maximum,validation_regex,ui_visible,ui_editable',
|
'name,label,type,object_types,object_type,weight,search_weight,filter_logic,choice_set,validation_minimum,validation_maximum,validation_regex,ui_visible,ui_editable',
|
||||||
'field4,Field 4,text,dcim.site,,100,1000,exact,,,,[a-z]{3},always,yes',
|
'field4,Field 4,text,dcim.site,,100,1000,exact,,,,[a-z]{3},always,yes',
|
||||||
'field5,Field 5,integer,dcim.site,,100,2000,exact,,1,100,,always,yes',
|
'field5,Field 5,integer,dcim.site,,100,2000,exact,,1,100,,always,yes',
|
||||||
'field6,Field 6,select,dcim.site,,100,3000,exact,Choice Set 1,,,,always,yes',
|
'field6,Field 6,select,dcim.site,,100,3000,exact,Choice Set 1,,,,always,yes',
|
||||||
|
@ -46,9 +46,9 @@ class CustomFieldView(generic.ObjectView):
|
|||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
related_models = ()
|
related_models = ()
|
||||||
|
|
||||||
for content_type in instance.content_types.all():
|
for object_type in instance.object_types.all():
|
||||||
related_models += (
|
related_models += (
|
||||||
content_type.model_class().objects.restrict(request.user, 'view').exclude(
|
object_type.model_class().objects.restrict(request.user, 'view').exclude(
|
||||||
Q(**{f'custom_field_data__{instance.name}': ''}) |
|
Q(**{f'custom_field_data__{instance.name}': ''}) |
|
||||||
Q(**{f'custom_field_data__{instance.name}': None})
|
Q(**{f'custom_field_data__{instance.name}': None})
|
||||||
),
|
),
|
||||||
|
@ -5,6 +5,7 @@ from django.http import Http404
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from core.models import ObjectType
|
||||||
from extras.models import ExportTemplate
|
from extras.models import ExportTemplate
|
||||||
from netbox.api.serializers import BulkOperationSerializer
|
from netbox.api.serializers import BulkOperationSerializer
|
||||||
|
|
||||||
@ -26,9 +27,9 @@ class CustomFieldsMixin:
|
|||||||
context = super().get_serializer_context()
|
context = super().get_serializer_context()
|
||||||
|
|
||||||
if hasattr(self.queryset.model, 'custom_fields'):
|
if hasattr(self.queryset.model, 'custom_fields'):
|
||||||
content_type = ContentType.objects.get_for_model(self.queryset.model)
|
object_type = ObjectType.objects.get_for_model(self.queryset.model)
|
||||||
context.update({
|
context.update({
|
||||||
'custom_fields': content_type.custom_fields.all(),
|
'custom_fields': object_type.custom_fields.all(),
|
||||||
})
|
})
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
@ -281,7 +281,7 @@ class NetBoxModelFilterSet(ChangeLoggedModelFilterSet):
|
|||||||
|
|
||||||
# Dynamically add a Filter for each CustomField applicable to the parent model
|
# Dynamically add a Filter for each CustomField applicable to the parent model
|
||||||
custom_fields = CustomField.objects.filter(
|
custom_fields = CustomField.objects.filter(
|
||||||
content_types=ContentType.objects.get_for_model(self._meta.model)
|
object_types=ContentType.objects.get_for_model(self._meta.model)
|
||||||
).exclude(
|
).exclude(
|
||||||
filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED
|
filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED
|
||||||
)
|
)
|
||||||
|
@ -88,7 +88,7 @@ class NetBoxModelImportForm(CSVModelForm, NetBoxModelForm):
|
|||||||
|
|
||||||
def _get_custom_fields(self, content_type):
|
def _get_custom_fields(self, content_type):
|
||||||
return CustomField.objects.filter(
|
return CustomField.objects.filter(
|
||||||
content_types=content_type,
|
object_types=content_type,
|
||||||
ui_editable=CustomFieldUIEditableChoices.YES
|
ui_editable=CustomFieldUIEditableChoices.YES
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ from django import forms
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
from core.models import ObjectType
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.models import *
|
from extras.models import *
|
||||||
from utilities.forms.fields import DynamicModelMultipleChoiceField
|
from utilities.forms.fields import DynamicModelMultipleChoiceField
|
||||||
@ -32,16 +33,16 @@ class CustomFieldsMixin:
|
|||||||
|
|
||||||
def _get_content_type(self):
|
def _get_content_type(self):
|
||||||
"""
|
"""
|
||||||
Return the ContentType of the form's model.
|
Return the ObjectType of the form's model.
|
||||||
"""
|
"""
|
||||||
if not getattr(self, 'model', None):
|
if not getattr(self, 'model', None):
|
||||||
raise NotImplementedError(_("{class_name} must specify a model class.").format(
|
raise NotImplementedError(_("{class_name} must specify a model class.").format(
|
||||||
class_name=self.__class__.__name__
|
class_name=self.__class__.__name__
|
||||||
))
|
))
|
||||||
return ContentType.objects.get_for_model(self.model)
|
return ObjectType.objects.get_for_model(self.model)
|
||||||
|
|
||||||
def _get_custom_fields(self, content_type):
|
def _get_custom_fields(self, content_type):
|
||||||
return CustomField.objects.filter(content_types=content_type).exclude(
|
return CustomField.objects.filter(object_types=content_type).exclude(
|
||||||
ui_editable=CustomFieldUIEditableChoices.HIDDEN
|
ui_editable=CustomFieldUIEditableChoices.HIDDEN
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -70,8 +70,8 @@ class CoreMiddleware:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Cleanly handle exceptions that occur from REST API requests
|
# Cleanly handle exceptions that occur from REST API requests
|
||||||
if is_api_request(request):
|
# if is_api_request(request):
|
||||||
return rest_api_server_error(request)
|
# return rest_api_server_error(request)
|
||||||
|
|
||||||
# Ignore Http404s (defer to Django's built-in 404 handling)
|
# Ignore Http404s (defer to Django's built-in 404 handling)
|
||||||
if isinstance(exception, Http404):
|
if isinstance(exception, Http404):
|
||||||
|
@ -11,6 +11,7 @@ from django.utils.module_loading import import_string
|
|||||||
import netaddr
|
import netaddr
|
||||||
from netaddr.core import AddrFormatError
|
from netaddr.core import AddrFormatError
|
||||||
|
|
||||||
|
from core.models import ObjectType
|
||||||
from extras.models import CachedValue, CustomField
|
from extras.models import CachedValue, CustomField
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
from utilities.querysets import RestrictedPrefetch
|
from utilities.querysets import RestrictedPrefetch
|
||||||
@ -134,7 +135,7 @@ class CachedValueSearchBackend(SearchBackend):
|
|||||||
# objects). This must be done before generating the final results list, which returns
|
# objects). This must be done before generating the final results list, which returns
|
||||||
# a RawQuerySet.
|
# a RawQuerySet.
|
||||||
content_type_ids = set(queryset.values_list('object_type', flat=True))
|
content_type_ids = set(queryset.values_list('object_type', flat=True))
|
||||||
content_types = ContentType.objects.filter(pk__in=content_type_ids)
|
object_types = ObjectType.objects.filter(pk__in=content_type_ids)
|
||||||
|
|
||||||
# Construct a Prefetch to pre-fetch only those related objects for which the
|
# Construct a Prefetch to pre-fetch only those related objects for which the
|
||||||
# user has permission to view.
|
# user has permission to view.
|
||||||
@ -153,7 +154,7 @@ class CachedValueSearchBackend(SearchBackend):
|
|||||||
|
|
||||||
# Iterate through each ContentType represented in the search results and prefetch any
|
# Iterate through each ContentType represented in the search results and prefetch any
|
||||||
# related objects necessary to render the prescribed display attributes (display_attrs).
|
# related objects necessary to render the prescribed display attributes (display_attrs).
|
||||||
for ct in content_types:
|
for ct in object_types:
|
||||||
model = ct.model_class()
|
model = ct.model_class()
|
||||||
indexer = registry['search'].get(content_type_identifier(ct))
|
indexer = registry['search'].get(content_type_identifier(ct))
|
||||||
if not (display_attrs := getattr(indexer, 'display_attrs', None)):
|
if not (display_attrs := getattr(indexer, 'display_attrs', None)):
|
||||||
@ -182,7 +183,7 @@ class CachedValueSearchBackend(SearchBackend):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
def cache(self, instances, indexer=None, remove_existing=True):
|
def cache(self, instances, indexer=None, remove_existing=True):
|
||||||
content_type = None
|
object_type = None
|
||||||
custom_fields = None
|
custom_fields = None
|
||||||
|
|
||||||
# Convert a single instance to an iterable
|
# Convert a single instance to an iterable
|
||||||
@ -204,8 +205,8 @@ class CachedValueSearchBackend(SearchBackend):
|
|||||||
break
|
break
|
||||||
|
|
||||||
# Prefetch any associated custom fields
|
# Prefetch any associated custom fields
|
||||||
content_type = ContentType.objects.get_for_model(indexer.model)
|
object_type = ObjectType.objects.get_for_model(indexer.model)
|
||||||
custom_fields = CustomField.objects.filter(content_types=content_type).exclude(search_weight=0)
|
custom_fields = CustomField.objects.filter(object_types=object_type).exclude(search_weight=0)
|
||||||
|
|
||||||
# Wipe out any previously cached values for the object
|
# Wipe out any previously cached values for the object
|
||||||
if remove_existing:
|
if remove_existing:
|
||||||
@ -215,7 +216,7 @@ class CachedValueSearchBackend(SearchBackend):
|
|||||||
for field in indexer.to_cache(instance, custom_fields=custom_fields):
|
for field in indexer.to_cache(instance, custom_fields=custom_fields):
|
||||||
buffer.append(
|
buffer.append(
|
||||||
CachedValue(
|
CachedValue(
|
||||||
object_type=content_type,
|
object_type=object_type,
|
||||||
object_id=instance.pk,
|
object_id=instance.pk,
|
||||||
field=field.name,
|
field=field.name,
|
||||||
type=field.type,
|
type=field.type,
|
||||||
|
@ -3,7 +3,6 @@ from copy import deepcopy
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.core.exceptions import FieldDoesNotExist
|
from django.core.exceptions import FieldDoesNotExist
|
||||||
from django.db.models.fields.related import RelatedField
|
from django.db.models.fields.related import RelatedField
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@ -12,6 +11,7 @@ from django.utils.safestring import mark_safe
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django_tables2.data import TableQuerysetData
|
from django_tables2.data import TableQuerysetData
|
||||||
|
|
||||||
|
from core.models import ObjectType
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.models import CustomField, CustomLink
|
from extras.models import CustomField, CustomLink
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
@ -201,14 +201,14 @@ class NetBoxTable(BaseTable):
|
|||||||
])
|
])
|
||||||
|
|
||||||
# Add custom field & custom link columns
|
# Add custom field & custom link columns
|
||||||
content_type = ContentType.objects.get_for_model(self._meta.model)
|
object_type = ObjectType.objects.get_for_model(self._meta.model)
|
||||||
custom_fields = CustomField.objects.filter(
|
custom_fields = CustomField.objects.filter(
|
||||||
content_types=content_type
|
object_types=object_type
|
||||||
).exclude(ui_visible=CustomFieldUIVisibleChoices.HIDDEN)
|
).exclude(ui_visible=CustomFieldUIVisibleChoices.HIDDEN)
|
||||||
extra_columns.extend([
|
extra_columns.extend([
|
||||||
(f'cf_{cf.name}', columns.CustomFieldColumn(cf)) for cf in custom_fields
|
(f'cf_{cf.name}', columns.CustomFieldColumn(cf)) for cf in custom_fields
|
||||||
])
|
])
|
||||||
custom_links = CustomLink.objects.filter(content_types=content_type, enabled=True)
|
custom_links = CustomLink.objects.filter(content_types=object_type, enabled=True)
|
||||||
extra_columns.extend([
|
extra_columns.extend([
|
||||||
(f'cl_{cl.name}', columns.CustomLinkColumn(cl)) for cl in custom_links
|
(f'cl_{cl.name}', columns.CustomLinkColumn(cl)) for cl in custom_links
|
||||||
])
|
])
|
||||||
|
@ -10,6 +10,7 @@ from django.test import Client, TestCase as _TestCase
|
|||||||
from netaddr import IPNetwork
|
from netaddr import IPNetwork
|
||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
|
from core.models import ObjectType
|
||||||
from users.models import ObjectPermission
|
from users.models import ObjectPermission
|
||||||
from utilities.permissions import resolve_permission_ct
|
from utilities.permissions import resolve_permission_ct
|
||||||
from utilities.utils import content_type_identifier
|
from utilities.utils import content_type_identifier
|
||||||
@ -112,7 +113,7 @@ class ModelTestCase(TestCase):
|
|||||||
# Handle ManyToManyFields
|
# Handle ManyToManyFields
|
||||||
if value and type(field) in (ManyToManyField, TaggableManager):
|
if value and type(field) in (ManyToManyField, TaggableManager):
|
||||||
|
|
||||||
if field.related_model is ContentType and api:
|
if field.related_model in (ContentType, ObjectType) and api:
|
||||||
model_dict[key] = sorted([content_type_identifier(ct) for ct in value])
|
model_dict[key] = sorted([content_type_identifier(ct) for ct in value])
|
||||||
else:
|
else:
|
||||||
model_dict[key] = sorted([obj.pk for obj in value])
|
model_dict[key] = sorted([obj.pk for obj in value])
|
||||||
@ -120,8 +121,8 @@ class ModelTestCase(TestCase):
|
|||||||
elif api:
|
elif api:
|
||||||
|
|
||||||
# Replace ContentType numeric IDs with <app_label>.<model>
|
# Replace ContentType numeric IDs with <app_label>.<model>
|
||||||
if type(getattr(instance, key)) is ContentType:
|
if type(getattr(instance, key)) in (ContentType, ObjectType):
|
||||||
ct = ContentType.objects.get(pk=value)
|
ct = ObjectType.objects.get(pk=value)
|
||||||
model_dict[key] = content_type_identifier(ct)
|
model_dict[key] = content_type_identifier(ct)
|
||||||
|
|
||||||
# Convert IPNetwork instances to strings
|
# Convert IPNetwork instances to strings
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import urllib.parse
|
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.test import Client, TestCase, override_settings
|
from django.test import Client, TestCase, override_settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
|
from core.models import ObjectType
|
||||||
from dcim.models import Region, Site
|
from dcim.models import Region, Site
|
||||||
from extras.choices import CustomFieldTypeChoices
|
from extras.choices import CustomFieldTypeChoices
|
||||||
from extras.models import CustomField
|
from extras.models import CustomField
|
||||||
@ -240,10 +238,10 @@ class APIDocsTestCase(TestCase):
|
|||||||
self.client = Client()
|
self.client = Client()
|
||||||
|
|
||||||
# Populate a CustomField to activate CustomFieldSerializer
|
# Populate a CustomField to activate CustomFieldSerializer
|
||||||
content_type = ContentType.objects.get_for_model(Site)
|
object_type = ObjectType.objects.get_for_model(Site)
|
||||||
self.cf_text = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='test')
|
self.cf_text = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='test')
|
||||||
self.cf_text.save()
|
self.cf_text.save()
|
||||||
self.cf_text.content_types.set([content_type])
|
self.cf_text.object_types.set([object_type])
|
||||||
self.cf_text.save()
|
self.cf_text.save()
|
||||||
|
|
||||||
def test_api_docs(self):
|
def test_api_docs(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user