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.test import TestCase
|
||||
|
||||
from circuits.models import *
|
||||
from core.models import ObjectType
|
||||
from dcim.choices import *
|
||||
from dcim.models import *
|
||||
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
|
||||
cf1 = CustomField.objects.create(name='cf1', default='foo')
|
||||
cf1.content_types.set(
|
||||
ContentType.objects.filter(app_label='dcim', model__in=[
|
||||
cf1.object_types.set(
|
||||
ObjectType.objects.filter(app_label='dcim', model__in=[
|
||||
'consoleport',
|
||||
'consoleserverport',
|
||||
'powerport',
|
||||
|
@ -26,7 +26,7 @@ class CustomFieldDefaultValues:
|
||||
|
||||
# Retrieve the CustomFields for the parent 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
|
||||
value = {}
|
||||
@ -48,7 +48,7 @@ class CustomFieldsDataField(Field):
|
||||
"""
|
||||
if not hasattr(self, '_custom_fields'):
|
||||
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
|
||||
|
||||
def to_representation(self, obj):
|
||||
|
@ -117,7 +117,7 @@ class WebhookSerializer(NetBoxModelSerializer):
|
||||
|
||||
class CustomFieldSerializer(ValidatedModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='extras-api:customfield-detail')
|
||||
content_types = ContentTypeField(
|
||||
object_types = ContentTypeField(
|
||||
queryset=ObjectType.objects.with_feature('custom_fields'),
|
||||
many=True
|
||||
)
|
||||
@ -139,7 +139,7 @@ class CustomFieldSerializer(ValidatedModelSerializer):
|
||||
class Meta:
|
||||
model = CustomField
|
||||
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',
|
||||
'default', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'choice_set',
|
||||
'created', 'last_updated',
|
||||
|
@ -124,10 +124,12 @@ class CustomFieldFilterSet(BaseFilterSet):
|
||||
type = django_filters.MultipleChoiceFilter(
|
||||
choices=CustomFieldTypeChoices
|
||||
)
|
||||
content_type_id = MultiValueNumberFilter(
|
||||
field_name='content_types__id'
|
||||
object_types_id = MultiValueNumberFilter(
|
||||
field_name='object_types__id'
|
||||
)
|
||||
object_types = ContentTypeFilter(
|
||||
field_name='object_types'
|
||||
)
|
||||
content_types = ContentTypeFilter()
|
||||
choice_set_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=CustomFieldChoiceSet.objects.all()
|
||||
)
|
||||
@ -140,7 +142,7 @@ class CustomFieldFilterSet(BaseFilterSet):
|
||||
class Meta:
|
||||
model = CustomField
|
||||
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',
|
||||
]
|
||||
|
||||
|
@ -30,8 +30,8 @@ __all__ = (
|
||||
|
||||
|
||||
class CustomFieldImportForm(CSVModelForm):
|
||||
content_types = CSVMultipleContentTypeField(
|
||||
label=_('Content types'),
|
||||
object_types = CSVMultipleContentTypeField(
|
||||
label=_('Object types'),
|
||||
queryset=ObjectType.objects.with_feature('custom_fields'),
|
||||
help_text=_("One or more assigned object types")
|
||||
)
|
||||
@ -69,7 +69,7 @@ class CustomFieldImportForm(CSVModelForm):
|
||||
class Meta:
|
||||
model = CustomField
|
||||
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',
|
||||
'validation_maximum', 'validation_regex', 'ui_visible', 'ui_editable', 'is_cloneable',
|
||||
)
|
||||
|
@ -38,11 +38,11 @@ class CustomFieldFilterForm(SavedFiltersMixin, FilterForm):
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id')),
|
||||
(_('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',
|
||||
)),
|
||||
)
|
||||
content_type_id = ContentTypeMultipleChoiceField(
|
||||
object_types_id = ContentTypeMultipleChoiceField(
|
||||
queryset=ObjectType.objects.with_feature('custom_fields'),
|
||||
required=False,
|
||||
label=_('Object type')
|
||||
|
@ -2,7 +2,6 @@ import json
|
||||
import re
|
||||
|
||||
from django import forms
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
@ -39,8 +38,8 @@ __all__ = (
|
||||
|
||||
|
||||
class CustomFieldForm(forms.ModelForm):
|
||||
content_types = ContentTypeMultipleChoiceField(
|
||||
label=_('Content types'),
|
||||
object_types = ContentTypeMultipleChoiceField(
|
||||
label=_('Object types'),
|
||||
queryset=ObjectType.objects.with_feature('custom_fields')
|
||||
)
|
||||
object_type = ContentTypeChoiceField(
|
||||
@ -56,7 +55,7 @@ class CustomFieldForm(forms.ModelForm):
|
||||
|
||||
fieldsets = (
|
||||
(_('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')),
|
||||
(_('Values'), ('default', 'choice_set')),
|
||||
|
@ -39,7 +39,7 @@ class CustomFieldType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.CustomField
|
||||
exclude = ('content_types', )
|
||||
exclude = ('object_types', 'object_type')
|
||||
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.
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
@ -66,8 +66,8 @@ class CustomFieldManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
||||
|
||||
|
||||
class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
||||
content_types = models.ManyToManyField(
|
||||
to='contenttypes.ContentType',
|
||||
object_types = models.ManyToManyField(
|
||||
to='core.ObjectType',
|
||||
related_name='custom_fields',
|
||||
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')
|
||||
)
|
||||
object_type = models.ForeignKey(
|
||||
to='contenttypes.ContentType',
|
||||
to='core.ObjectType',
|
||||
on_delete=models.PROTECT,
|
||||
blank=True,
|
||||
null=True,
|
||||
@ -284,7 +284,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
||||
"""
|
||||
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()
|
||||
params = {f'custom_field_data__{old_name}__isnull': False}
|
||||
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.
|
||||
"""
|
||||
instance.remove_stale_data(instance.content_types.all())
|
||||
instance.remove_stale_data(instance.object_types.all())
|
||||
|
||||
|
||||
post_save.connect(handle_cf_renamed, 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_removed_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.object_types.through)
|
||||
|
||||
|
||||
#
|
||||
|
@ -7,10 +7,10 @@ from django.utils.timezone import make_aware
|
||||
from rest_framework import status
|
||||
|
||||
from core.choices import ManagedFileRootPathChoices
|
||||
from core.models import ObjectType
|
||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Rack, Location, RackRole, Site
|
||||
from extras.choices import *
|
||||
from extras.models import *
|
||||
from extras.reports import Report
|
||||
from extras.scripts import BooleanVar, IntegerVar, Script as PythonClass, StringVar
|
||||
from utilities.testing import APITestCase, APIViewTestCases
|
||||
|
||||
@ -152,17 +152,17 @@ class CustomFieldTest(APIViewTestCases.APIViewTestCase):
|
||||
brief_fields = ['description', 'display', 'id', 'name', 'url']
|
||||
create_data = [
|
||||
{
|
||||
'content_types': ['dcim.site'],
|
||||
'object_types': ['dcim.site'],
|
||||
'name': 'cf4',
|
||||
'type': 'date',
|
||||
},
|
||||
{
|
||||
'content_types': ['dcim.site'],
|
||||
'object_types': ['dcim.site'],
|
||||
'name': 'cf5',
|
||||
'type': 'url',
|
||||
},
|
||||
{
|
||||
'content_types': ['dcim.site'],
|
||||
'object_types': ['dcim.site'],
|
||||
'name': 'cf6',
|
||||
'type': 'text',
|
||||
},
|
||||
@ -171,14 +171,14 @@ class CustomFieldTest(APIViewTestCases.APIViewTestCase):
|
||||
'description': 'New description',
|
||||
}
|
||||
update_data = {
|
||||
'content_types': ['dcim.device'],
|
||||
'object_types': ['dcim.device'],
|
||||
'name': 'New_Name',
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
site_ct = ContentType.objects.get_for_model(Site)
|
||||
site_ct = ObjectType.objects.get_for_model(Site)
|
||||
|
||||
custom_fields = (
|
||||
CustomField(
|
||||
@ -196,7 +196,7 @@ class CustomFieldTest(APIViewTestCases.APIViewTestCase):
|
||||
)
|
||||
CustomField.objects.bulk_create(custom_fields)
|
||||
for cf in custom_fields:
|
||||
cf.content_types.add(site_ct)
|
||||
cf.object_types.add(site_ct)
|
||||
|
||||
|
||||
class CustomFieldChoiceSetTest(APIViewTestCases.APIViewTestCase):
|
||||
|
@ -3,6 +3,7 @@ from django.test import override_settings
|
||||
from django.urls import reverse
|
||||
from rest_framework import status
|
||||
|
||||
from core.models import ObjectType
|
||||
from dcim.choices import SiteStatusChoices
|
||||
from dcim.models import Site
|
||||
from extras.choices import *
|
||||
@ -23,14 +24,14 @@ class ChangeLogViewTest(ModelViewTestCase):
|
||||
)
|
||||
|
||||
# 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(
|
||||
type=CustomFieldTypeChoices.TYPE_TEXT,
|
||||
name='cf1',
|
||||
required=False
|
||||
)
|
||||
cf.save()
|
||||
cf.content_types.set([ct])
|
||||
cf.object_types.set([site_type])
|
||||
|
||||
# Create a select custom field on the Site model
|
||||
cf_select = CustomField(
|
||||
@ -40,7 +41,7 @@ class ChangeLogViewTest(ModelViewTestCase):
|
||||
choice_set=choice_set
|
||||
)
|
||||
cf_select.save()
|
||||
cf_select.content_types.set([ct])
|
||||
cf_select.object_types.set([site_type])
|
||||
|
||||
def test_create_object(self):
|
||||
tags = create_tags('Tag 1', 'Tag 2')
|
||||
@ -275,14 +276,14 @@ class ChangeLogAPITest(APITestCase):
|
||||
def setUpTestData(cls):
|
||||
|
||||
# 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(
|
||||
type=CustomFieldTypeChoices.TYPE_TEXT,
|
||||
name='cf1',
|
||||
required=False
|
||||
)
|
||||
cf.save()
|
||||
cf.content_types.set([ct])
|
||||
cf.object_types.set([site_type])
|
||||
|
||||
# Create a select custom field on the Site model
|
||||
choice_set = CustomFieldChoiceSet.objects.create(
|
||||
@ -296,7 +297,7 @@ class ChangeLogAPITest(APITestCase):
|
||||
choice_set=choice_set
|
||||
)
|
||||
cf_select.save()
|
||||
cf_select.content_types.set([ct])
|
||||
cf_select.object_types.set([site_type])
|
||||
|
||||
# Create some tags
|
||||
tags = (
|
||||
|
@ -1,11 +1,11 @@
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ValidationError
|
||||
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
|
||||
@ -28,7 +28,7 @@ class CustomFieldTest(TestCase):
|
||||
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):
|
||||
"""
|
||||
@ -50,7 +50,7 @@ class CustomFieldTest(TestCase):
|
||||
type=CustomFieldTypeChoices.TYPE_TEXT,
|
||||
required=False
|
||||
)
|
||||
cf.content_types.set([self.object_type])
|
||||
cf.object_types.set([self.object_type])
|
||||
instance = Site.objects.first()
|
||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||
|
||||
@ -75,7 +75,7 @@ class CustomFieldTest(TestCase):
|
||||
type=CustomFieldTypeChoices.TYPE_LONGTEXT,
|
||||
required=False
|
||||
)
|
||||
cf.content_types.set([self.object_type])
|
||||
cf.object_types.set([self.object_type])
|
||||
instance = Site.objects.first()
|
||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||
|
||||
@ -99,7 +99,7 @@ class CustomFieldTest(TestCase):
|
||||
type=CustomFieldTypeChoices.TYPE_INTEGER,
|
||||
required=False
|
||||
)
|
||||
cf.content_types.set([self.object_type])
|
||||
cf.object_types.set([self.object_type])
|
||||
instance = Site.objects.first()
|
||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||
|
||||
@ -125,7 +125,7 @@ class CustomFieldTest(TestCase):
|
||||
type=CustomFieldTypeChoices.TYPE_DECIMAL,
|
||||
required=False
|
||||
)
|
||||
cf.content_types.set([self.object_type])
|
||||
cf.object_types.set([self.object_type])
|
||||
instance = Site.objects.first()
|
||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||
|
||||
@ -151,7 +151,7 @@ class CustomFieldTest(TestCase):
|
||||
type=CustomFieldTypeChoices.TYPE_INTEGER,
|
||||
required=False
|
||||
)
|
||||
cf.content_types.set([self.object_type])
|
||||
cf.object_types.set([self.object_type])
|
||||
instance = Site.objects.first()
|
||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||
|
||||
@ -178,7 +178,7 @@ class CustomFieldTest(TestCase):
|
||||
type=CustomFieldTypeChoices.TYPE_DATE,
|
||||
required=False
|
||||
)
|
||||
cf.content_types.set([self.object_type])
|
||||
cf.object_types.set([self.object_type])
|
||||
instance = Site.objects.first()
|
||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||
|
||||
@ -203,7 +203,7 @@ class CustomFieldTest(TestCase):
|
||||
type=CustomFieldTypeChoices.TYPE_DATETIME,
|
||||
required=False
|
||||
)
|
||||
cf.content_types.set([self.object_type])
|
||||
cf.object_types.set([self.object_type])
|
||||
instance = Site.objects.first()
|
||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||
|
||||
@ -228,7 +228,7 @@ class CustomFieldTest(TestCase):
|
||||
type=CustomFieldTypeChoices.TYPE_URL,
|
||||
required=False
|
||||
)
|
||||
cf.content_types.set([self.object_type])
|
||||
cf.object_types.set([self.object_type])
|
||||
instance = Site.objects.first()
|
||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||
|
||||
@ -253,7 +253,7 @@ class CustomFieldTest(TestCase):
|
||||
type=CustomFieldTypeChoices.TYPE_JSON,
|
||||
required=False
|
||||
)
|
||||
cf.content_types.set([self.object_type])
|
||||
cf.object_types.set([self.object_type])
|
||||
instance = Site.objects.first()
|
||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||
|
||||
@ -290,7 +290,7 @@ class CustomFieldTest(TestCase):
|
||||
required=False,
|
||||
choice_set=choice_set
|
||||
)
|
||||
cf.content_types.set([self.object_type])
|
||||
cf.object_types.set([self.object_type])
|
||||
instance = Site.objects.first()
|
||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||
|
||||
@ -327,7 +327,7 @@ class CustomFieldTest(TestCase):
|
||||
required=False,
|
||||
choice_set=choice_set
|
||||
)
|
||||
cf.content_types.set([self.object_type])
|
||||
cf.object_types.set([self.object_type])
|
||||
instance = Site.objects.first()
|
||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||
|
||||
@ -350,10 +350,10 @@ class CustomFieldTest(TestCase):
|
||||
cf = CustomField.objects.create(
|
||||
name='object_field',
|
||||
type=CustomFieldTypeChoices.TYPE_OBJECT,
|
||||
object_type=ContentType.objects.get_for_model(VLAN),
|
||||
object_type=ObjectType.objects.get_for_model(VLAN),
|
||||
required=False
|
||||
)
|
||||
cf.content_types.set([self.object_type])
|
||||
cf.object_types.set([self.object_type])
|
||||
instance = Site.objects.first()
|
||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||
|
||||
@ -382,10 +382,10 @@ class CustomFieldTest(TestCase):
|
||||
cf = CustomField.objects.create(
|
||||
name='object_field',
|
||||
type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
|
||||
object_type=ContentType.objects.get_for_model(VLAN),
|
||||
object_type=ObjectType.objects.get_for_model(VLAN),
|
||||
required=False
|
||||
)
|
||||
cf.content_types.set([self.object_type])
|
||||
cf.object_types.set([self.object_type])
|
||||
instance = Site.objects.first()
|
||||
self.assertIsNone(instance.custom_field_data[cf.name])
|
||||
|
||||
@ -402,13 +402,13 @@ class CustomFieldTest(TestCase):
|
||||
self.assertIsNone(instance.custom_field_data.get(cf.name))
|
||||
|
||||
def test_rename_customfield(self):
|
||||
obj_type = ContentType.objects.get_for_model(Site)
|
||||
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.content_types.set([obj_type])
|
||||
cf.object_types.set([obj_type])
|
||||
|
||||
# Assign custom field data to an object
|
||||
site = Site.objects.create(
|
||||
@ -437,7 +437,7 @@ class CustomFieldTest(TestCase):
|
||||
)
|
||||
)
|
||||
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
|
||||
CustomField(name='test', type='text', required=True, default="Default text").full_clean()
|
||||
@ -524,10 +524,10 @@ class CustomFieldManagerTest(TestCase):
|
||||
|
||||
@classmethod
|
||||
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.save()
|
||||
custom_field.content_types.set([content_type])
|
||||
custom_field.object_types.set([object_type])
|
||||
|
||||
def test_get_for_model(self):
|
||||
self.assertEqual(CustomField.objects.get_for_model(Site).count(), 1)
|
||||
@ -538,7 +538,7 @@ class CustomFieldAPITest(APITestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
content_type = ContentType.objects.get_for_model(Site)
|
||||
object_type = ObjectType.objects.get_for_model(Site)
|
||||
|
||||
# Create some VLANs
|
||||
vlans = (
|
||||
@ -581,19 +581,19 @@ class CustomFieldAPITest(APITestCase):
|
||||
CustomField(
|
||||
type=CustomFieldTypeChoices.TYPE_OBJECT,
|
||||
name='object_field',
|
||||
object_type=ContentType.objects.get_for_model(VLAN),
|
||||
object_type=ObjectType.objects.get_for_model(VLAN),
|
||||
default=vlans[0].pk,
|
||||
),
|
||||
CustomField(
|
||||
type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
|
||||
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],
|
||||
),
|
||||
)
|
||||
for cf in custom_fields:
|
||||
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
|
||||
# default values are not set for the assigned objects.
|
||||
@ -1163,7 +1163,7 @@ class CustomFieldImportTest(TestCase):
|
||||
)
|
||||
for cf in custom_fields:
|
||||
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):
|
||||
"""
|
||||
@ -1256,11 +1256,11 @@ class CustomFieldModelTest(TestCase):
|
||||
def setUpTestData(cls):
|
||||
cf1 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='foo')
|
||||
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.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):
|
||||
"""
|
||||
@ -1299,7 +1299,7 @@ class CustomFieldModelTest(TestCase):
|
||||
"""
|
||||
cf3 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='baz', required=True)
|
||||
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')
|
||||
|
||||
@ -1318,7 +1318,7 @@ class CustomFieldModelFilterTest(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
obj_type = ContentType.objects.get_for_model(Site)
|
||||
object_type = ObjectType.objects.get_for_model(Site)
|
||||
|
||||
manufacturers = Manufacturer.objects.bulk_create((
|
||||
Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
|
||||
@ -1335,17 +1335,17 @@ class CustomFieldModelFilterTest(TestCase):
|
||||
# Integer filtering
|
||||
cf = CustomField(name='cf1', type=CustomFieldTypeChoices.TYPE_INTEGER)
|
||||
cf.save()
|
||||
cf.content_types.set([obj_type])
|
||||
cf.object_types.set([object_type])
|
||||
|
||||
# Decimal filtering
|
||||
cf = CustomField(name='cf2', type=CustomFieldTypeChoices.TYPE_DECIMAL)
|
||||
cf.save()
|
||||
cf.content_types.set([obj_type])
|
||||
cf.object_types.set([object_type])
|
||||
|
||||
# Boolean filtering
|
||||
cf = CustomField(name='cf3', type=CustomFieldTypeChoices.TYPE_BOOLEAN)
|
||||
cf.save()
|
||||
cf.content_types.set([obj_type])
|
||||
cf.object_types.set([object_type])
|
||||
|
||||
# Exact text filtering
|
||||
cf = CustomField(
|
||||
@ -1354,7 +1354,7 @@ class CustomFieldModelFilterTest(TestCase):
|
||||
filter_logic=CustomFieldFilterLogicChoices.FILTER_EXACT
|
||||
)
|
||||
cf.save()
|
||||
cf.content_types.set([obj_type])
|
||||
cf.object_types.set([object_type])
|
||||
|
||||
# Loose text filtering
|
||||
cf = CustomField(
|
||||
@ -1363,12 +1363,12 @@ class CustomFieldModelFilterTest(TestCase):
|
||||
filter_logic=CustomFieldFilterLogicChoices.FILTER_LOOSE
|
||||
)
|
||||
cf.save()
|
||||
cf.content_types.set([obj_type])
|
||||
cf.object_types.set([object_type])
|
||||
|
||||
# Date filtering
|
||||
cf = CustomField(name='cf6', type=CustomFieldTypeChoices.TYPE_DATE)
|
||||
cf.save()
|
||||
cf.content_types.set([obj_type])
|
||||
cf.object_types.set([object_type])
|
||||
|
||||
# Exact URL filtering
|
||||
cf = CustomField(
|
||||
@ -1377,7 +1377,7 @@ class CustomFieldModelFilterTest(TestCase):
|
||||
filter_logic=CustomFieldFilterLogicChoices.FILTER_EXACT
|
||||
)
|
||||
cf.save()
|
||||
cf.content_types.set([obj_type])
|
||||
cf.object_types.set([object_type])
|
||||
|
||||
# Loose URL filtering
|
||||
cf = CustomField(
|
||||
@ -1386,7 +1386,7 @@ class CustomFieldModelFilterTest(TestCase):
|
||||
filter_logic=CustomFieldFilterLogicChoices.FILTER_LOOSE
|
||||
)
|
||||
cf.save()
|
||||
cf.content_types.set([obj_type])
|
||||
cf.object_types.set([object_type])
|
||||
|
||||
# Selection filtering
|
||||
cf = CustomField(
|
||||
@ -1395,7 +1395,7 @@ class CustomFieldModelFilterTest(TestCase):
|
||||
choice_set=choice_set
|
||||
)
|
||||
cf.save()
|
||||
cf.content_types.set([obj_type])
|
||||
cf.object_types.set([object_type])
|
||||
|
||||
# Multiselect filtering
|
||||
cf = CustomField(
|
||||
@ -1404,25 +1404,25 @@ class CustomFieldModelFilterTest(TestCase):
|
||||
choice_set=choice_set
|
||||
)
|
||||
cf.save()
|
||||
cf.content_types.set([obj_type])
|
||||
cf.object_types.set([object_type])
|
||||
|
||||
# Object filtering
|
||||
cf = CustomField(
|
||||
name='cf11',
|
||||
type=CustomFieldTypeChoices.TYPE_OBJECT,
|
||||
object_type=ContentType.objects.get_for_model(Manufacturer)
|
||||
object_type=ObjectType.objects.get_for_model(Manufacturer)
|
||||
)
|
||||
cf.save()
|
||||
cf.content_types.set([obj_type])
|
||||
cf.object_types.set([object_type])
|
||||
|
||||
# Multi-object filtering
|
||||
cf = CustomField(
|
||||
name='cf12',
|
||||
type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
|
||||
object_type=ContentType.objects.get_for_model(Manufacturer)
|
||||
object_type=ObjectType.objects.get_for_model(Manufacturer)
|
||||
)
|
||||
cf.save()
|
||||
cf.content_types.set([obj_type])
|
||||
cf.object_types.set([object_type])
|
||||
|
||||
Site.objects.bulk_create([
|
||||
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 core.choices import ManagedFileRootPathChoices
|
||||
from core.models import ObjectType
|
||||
from dcim.filtersets import SiteFilterSet
|
||||
from dcim.models import DeviceRole, DeviceType, Manufacturer, Platform, Rack, Region, Site, SiteGroup
|
||||
from dcim.models import Location
|
||||
@ -87,11 +88,11 @@ class CustomFieldTestCase(TestCase, BaseFilterSetTests):
|
||||
),
|
||||
)
|
||||
CustomField.objects.bulk_create(custom_fields)
|
||||
custom_fields[0].content_types.add(ContentType.objects.get_by_natural_key('dcim', 'site'))
|
||||
custom_fields[1].content_types.add(ContentType.objects.get_by_natural_key('dcim', 'rack'))
|
||||
custom_fields[2].content_types.add(ContentType.objects.get_by_natural_key('dcim', 'device'))
|
||||
custom_fields[3].content_types.add(ContentType.objects.get_by_natural_key('dcim', 'device'))
|
||||
custom_fields[4].content_types.add(ContentType.objects.get_by_natural_key('dcim', 'device'))
|
||||
custom_fields[0].object_types.add(ObjectType.objects.get_by_natural_key('dcim', 'site'))
|
||||
custom_fields[1].object_types.add(ObjectType.objects.get_by_natural_key('dcim', 'rack'))
|
||||
custom_fields[2].object_types.add(ObjectType.objects.get_by_natural_key('dcim', 'device'))
|
||||
custom_fields[3].object_types.add(ObjectType.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):
|
||||
params = {'q': 'foobar1'}
|
||||
@ -101,10 +102,10 @@ class CustomFieldTestCase(TestCase, BaseFilterSetTests):
|
||||
params = {'name': ['Custom Field 1', 'Custom Field 2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_content_types(self):
|
||||
params = {'content_types': 'dcim.site'}
|
||||
def test_object_types(self):
|
||||
params = {'object_types': 'dcim.site'}
|
||||
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)
|
||||
|
||||
def test_required(self):
|
||||
|
@ -1,6 +1,7 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from core.models import ObjectType
|
||||
from dcim.forms import SiteForm
|
||||
from dcim.models import Site
|
||||
from extras.choices import CustomFieldTypeChoices
|
||||
@ -12,66 +13,66 @@ class CustomFieldModelFormTest(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
obj_type = ContentType.objects.get_for_model(Site)
|
||||
object_type = ObjectType.objects.get_for_model(Site)
|
||||
choice_set = CustomFieldChoiceSet.objects.create(
|
||||
name='Choice Set 1',
|
||||
extra_choices=(('a', 'A'), ('b', 'B'), ('c', 'C'))
|
||||
)
|
||||
|
||||
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.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.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.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.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.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.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.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.content_types.set([obj_type])
|
||||
cf_json.object_types.set([object_type])
|
||||
|
||||
cf_select = CustomField.objects.create(
|
||||
name='select',
|
||||
type=CustomFieldTypeChoices.TYPE_SELECT,
|
||||
choice_set=choice_set
|
||||
)
|
||||
cf_select.content_types.set([obj_type])
|
||||
cf_select.object_types.set([object_type])
|
||||
|
||||
cf_multiselect = CustomField.objects.create(
|
||||
name='multiselect',
|
||||
type=CustomFieldTypeChoices.TYPE_MULTISELECT,
|
||||
choice_set=choice_set
|
||||
)
|
||||
cf_multiselect.content_types.set([obj_type])
|
||||
cf_multiselect.object_types.set([object_type])
|
||||
|
||||
cf_object = CustomField.objects.create(
|
||||
name='object',
|
||||
type=CustomFieldTypeChoices.TYPE_OBJECT,
|
||||
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(
|
||||
name='multiobject',
|
||||
type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
|
||||
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):
|
||||
"""
|
||||
|
@ -5,6 +5,7 @@ from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.urls import reverse
|
||||
|
||||
from core.models import ObjectType
|
||||
from dcim.models import DeviceType, Manufacturer, Site
|
||||
from extras.choices import *
|
||||
from extras.models import *
|
||||
@ -19,7 +20,7 @@ class CustomFieldTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
site_ct = ContentType.objects.get_for_model(Site)
|
||||
site_type = ObjectType.objects.get_for_model(Site)
|
||||
CustomFieldChoiceSet.objects.create(
|
||||
name='Choice Set 1',
|
||||
extra_choices=(
|
||||
@ -36,13 +37,13 @@ class CustomFieldTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
)
|
||||
for customfield in custom_fields:
|
||||
customfield.save()
|
||||
customfield.content_types.add(site_ct)
|
||||
customfield.object_types.add(site_type)
|
||||
|
||||
cls.form_data = {
|
||||
'name': 'field_x',
|
||||
'label': 'Field X',
|
||||
'type': 'text',
|
||||
'content_types': [site_ct.pk],
|
||||
'object_types': [site_type.pk],
|
||||
'search_weight': 2000,
|
||||
'filter_logic': CustomFieldFilterLogicChoices.FILTER_EXACT,
|
||||
'default': None,
|
||||
@ -53,7 +54,7 @@ class CustomFieldTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
}
|
||||
|
||||
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',
|
||||
'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',
|
||||
|
@ -46,9 +46,9 @@ class CustomFieldView(generic.ObjectView):
|
||||
def get_extra_context(self, request, instance):
|
||||
related_models = ()
|
||||
|
||||
for content_type in instance.content_types.all():
|
||||
for object_type in instance.object_types.all():
|
||||
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}': None})
|
||||
),
|
||||
|
@ -5,6 +5,7 @@ from django.http import Http404
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
|
||||
from core.models import ObjectType
|
||||
from extras.models import ExportTemplate
|
||||
from netbox.api.serializers import BulkOperationSerializer
|
||||
|
||||
@ -26,9 +27,9 @@ class CustomFieldsMixin:
|
||||
context = super().get_serializer_context()
|
||||
|
||||
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({
|
||||
'custom_fields': content_type.custom_fields.all(),
|
||||
'custom_fields': object_type.custom_fields.all(),
|
||||
})
|
||||
|
||||
return context
|
||||
|
@ -281,7 +281,7 @@ class NetBoxModelFilterSet(ChangeLoggedModelFilterSet):
|
||||
|
||||
# Dynamically add a Filter for each CustomField applicable to the parent model
|
||||
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(
|
||||
filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED
|
||||
)
|
||||
|
@ -88,7 +88,7 @@ class NetBoxModelImportForm(CSVModelForm, NetBoxModelForm):
|
||||
|
||||
def _get_custom_fields(self, content_type):
|
||||
return CustomField.objects.filter(
|
||||
content_types=content_type,
|
||||
object_types=content_type,
|
||||
ui_editable=CustomFieldUIEditableChoices.YES
|
||||
)
|
||||
|
||||
|
@ -2,6 +2,7 @@ from django import forms
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from core.models import ObjectType
|
||||
from extras.choices import *
|
||||
from extras.models import *
|
||||
from utilities.forms.fields import DynamicModelMultipleChoiceField
|
||||
@ -32,16 +33,16 @@ class CustomFieldsMixin:
|
||||
|
||||
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):
|
||||
raise NotImplementedError(_("{class_name} must specify a model class.").format(
|
||||
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):
|
||||
return CustomField.objects.filter(content_types=content_type).exclude(
|
||||
return CustomField.objects.filter(object_types=content_type).exclude(
|
||||
ui_editable=CustomFieldUIEditableChoices.HIDDEN
|
||||
)
|
||||
|
||||
|
@ -70,8 +70,8 @@ class CoreMiddleware:
|
||||
return
|
||||
|
||||
# Cleanly handle exceptions that occur from REST API requests
|
||||
if is_api_request(request):
|
||||
return rest_api_server_error(request)
|
||||
# if is_api_request(request):
|
||||
# return rest_api_server_error(request)
|
||||
|
||||
# Ignore Http404s (defer to Django's built-in 404 handling)
|
||||
if isinstance(exception, Http404):
|
||||
|
@ -11,6 +11,7 @@ from django.utils.module_loading import import_string
|
||||
import netaddr
|
||||
from netaddr.core import AddrFormatError
|
||||
|
||||
from core.models import ObjectType
|
||||
from extras.models import CachedValue, CustomField
|
||||
from netbox.registry import registry
|
||||
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
|
||||
# a RawQuerySet.
|
||||
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
|
||||
# user has permission to view.
|
||||
@ -153,7 +154,7 @@ class CachedValueSearchBackend(SearchBackend):
|
||||
|
||||
# Iterate through each ContentType represented in the search results and prefetch any
|
||||
# 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()
|
||||
indexer = registry['search'].get(content_type_identifier(ct))
|
||||
if not (display_attrs := getattr(indexer, 'display_attrs', None)):
|
||||
@ -182,7 +183,7 @@ class CachedValueSearchBackend(SearchBackend):
|
||||
return ret
|
||||
|
||||
def cache(self, instances, indexer=None, remove_existing=True):
|
||||
content_type = None
|
||||
object_type = None
|
||||
custom_fields = None
|
||||
|
||||
# Convert a single instance to an iterable
|
||||
@ -204,8 +205,8 @@ class CachedValueSearchBackend(SearchBackend):
|
||||
break
|
||||
|
||||
# Prefetch any associated custom fields
|
||||
content_type = ContentType.objects.get_for_model(indexer.model)
|
||||
custom_fields = CustomField.objects.filter(content_types=content_type).exclude(search_weight=0)
|
||||
object_type = ObjectType.objects.get_for_model(indexer.model)
|
||||
custom_fields = CustomField.objects.filter(object_types=object_type).exclude(search_weight=0)
|
||||
|
||||
# Wipe out any previously cached values for the object
|
||||
if remove_existing:
|
||||
@ -215,7 +216,7 @@ class CachedValueSearchBackend(SearchBackend):
|
||||
for field in indexer.to_cache(instance, custom_fields=custom_fields):
|
||||
buffer.append(
|
||||
CachedValue(
|
||||
object_type=content_type,
|
||||
object_type=object_type,
|
||||
object_id=instance.pk,
|
||||
field=field.name,
|
||||
type=field.type,
|
||||
|
@ -3,7 +3,6 @@ from copy import deepcopy
|
||||
import django_tables2 as tables
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import FieldDoesNotExist
|
||||
from django.db.models.fields.related import RelatedField
|
||||
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_tables2.data import TableQuerysetData
|
||||
|
||||
from core.models import ObjectType
|
||||
from extras.choices import *
|
||||
from extras.models import CustomField, CustomLink
|
||||
from netbox.registry import registry
|
||||
@ -201,14 +201,14 @@ class NetBoxTable(BaseTable):
|
||||
])
|
||||
|
||||
# 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(
|
||||
content_types=content_type
|
||||
object_types=object_type
|
||||
).exclude(ui_visible=CustomFieldUIVisibleChoices.HIDDEN)
|
||||
extra_columns.extend([
|
||||
(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([
|
||||
(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 taggit.managers import TaggableManager
|
||||
|
||||
from core.models import ObjectType
|
||||
from users.models import ObjectPermission
|
||||
from utilities.permissions import resolve_permission_ct
|
||||
from utilities.utils import content_type_identifier
|
||||
@ -112,7 +113,7 @@ class ModelTestCase(TestCase):
|
||||
# Handle ManyToManyFields
|
||||
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])
|
||||
else:
|
||||
model_dict[key] = sorted([obj.pk for obj in value])
|
||||
@ -120,8 +121,8 @@ class ModelTestCase(TestCase):
|
||||
elif api:
|
||||
|
||||
# Replace ContentType numeric IDs with <app_label>.<model>
|
||||
if type(getattr(instance, key)) is ContentType:
|
||||
ct = ContentType.objects.get(pk=value)
|
||||
if type(getattr(instance, key)) in (ContentType, ObjectType):
|
||||
ct = ObjectType.objects.get(pk=value)
|
||||
model_dict[key] = content_type_identifier(ct)
|
||||
|
||||
# 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.urls import reverse
|
||||
from rest_framework import status
|
||||
|
||||
from core.models import ObjectType
|
||||
from dcim.models import Region, Site
|
||||
from extras.choices import CustomFieldTypeChoices
|
||||
from extras.models import CustomField
|
||||
@ -240,10 +238,10 @@ class APIDocsTestCase(TestCase):
|
||||
self.client = Client()
|
||||
|
||||
# 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.save()
|
||||
self.cf_text.content_types.set([content_type])
|
||||
self.cf_text.object_types.set([object_type])
|
||||
self.cf_text.save()
|
||||
|
||||
def test_api_docs(self):
|
||||
|
Loading…
Reference in New Issue
Block a user