Closes #19924: Record model features on ObjectType (#19939)
Some checks failed
CI / build (20.x, 3.10) (push) Has been cancelled
CI / build (20.x, 3.11) (push) Has been cancelled
CI / build (20.x, 3.12) (push) Has been cancelled

* Convert ObjectType to a concrete child model of ContentType

* Add public flag to ObjectType

* Catch post_migrate signal to update ObjectTypes

* Reference ObjectType records instead of registry for feature support

* Automatically create ObjectTypes

* Introduce has_feature() utility function

* ObjectTypeManager should not inherit from ContentTypeManager

* Misc cleanup

* Don't populate ObjectTypes during migration

* Don't automatically create ObjectTypes when a ContentType is created

* Fix test

* Extend has_feature() to accept a model or OT/CT

* Misc cleanup

* Deprecate get_for_id() on ObjectTypeManager

* Rename contenttypes.py to object_types.py

* Add index to features ArrayField

* Keep FK & M2M fields pointing to ContentType

* Add get_for_models() to ObjectTypeManager

* Add tests for manager methods & utility functions

* Fix migrations for M2M relations to ObjectType

* model_is_public() should return False for non-core & non-plugin models

* Order ObjectType by app_label & model name

* Resolve migrations conflict
This commit is contained in:
Jeremy Stretch
2025-07-30 13:05:34 -04:00
committed by GitHub
parent 24a0e1907a
commit b610cf37cf
30 changed files with 633 additions and 154 deletions

View File

@@ -1,7 +1,10 @@
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from django.test import TestCase
from core.models import DataSource
from core.models import DataSource, ObjectType
from core.choices import ObjectChangeActionChoices
from dcim.models import Site, Location, Device
from netbox.constants import CENSOR_TOKEN, CENSOR_TOKEN_CHANGED
@@ -120,3 +123,80 @@ class DataSourceChangeLoggingTestCase(TestCase):
self.assertEqual(objectchange.prechange_data['parameters']['password'], CENSOR_TOKEN)
self.assertEqual(objectchange.postchange_data['parameters']['username'], 'username2')
self.assertEqual(objectchange.postchange_data['parameters']['password'], CENSOR_TOKEN)
class ObjectTypeTest(TestCase):
def test_create(self):
"""
Test that an ObjectType created for a given app_label & model name will be automatically assigned to
the appropriate ContentType.
"""
kwargs = {
'app_label': 'foo',
'model': 'bar',
}
ct = ContentType.objects.create(**kwargs)
ot = ObjectType.objects.create(**kwargs)
self.assertEqual(ot.contenttype_ptr, ct)
def test_get_by_natural_key(self):
"""
Test that get_by_natural_key() returns the appropriate ObjectType.
"""
self.assertEqual(
ObjectType.objects.get_by_natural_key('dcim', 'site'),
ObjectType.objects.get(app_label='dcim', model='site')
)
with self.assertRaises(ObjectDoesNotExist):
ObjectType.objects.get_by_natural_key('foo', 'bar')
def test_get_for_id(self):
"""
Test that get_by_id() returns the appropriate ObjectType.
"""
ot = ObjectType.objects.get_by_natural_key('dcim', 'site')
self.assertEqual(
ObjectType.objects.get_for_id(ot.pk),
ObjectType.objects.get(pk=ot.pk)
)
with self.assertRaises(ObjectDoesNotExist):
ObjectType.objects.get_for_id(0)
def test_get_for_model(self):
"""
Test that get_by_model() returns the appropriate ObjectType.
"""
self.assertEqual(
ObjectType.objects.get_for_model(Site),
ObjectType.objects.get_by_natural_key('dcim', 'site')
)
def test_get_for_models(self):
"""
Test that get_by_models() returns the appropriate ObjectType mapping.
"""
self.assertEqual(
ObjectType.objects.get_for_models(Site, Location, Device),
{
Site: ObjectType.objects.get_by_natural_key('dcim', 'site'),
Location: ObjectType.objects.get_by_natural_key('dcim', 'location'),
Device: ObjectType.objects.get_by_natural_key('dcim', 'device'),
}
)
def test_public(self):
"""
Test that public() returns only ObjectTypes for public models.
"""
public_ots = ObjectType.objects.public()
self.assertIn(ObjectType.objects.get_by_natural_key('dcim', 'site'), public_ots)
self.assertNotIn(ObjectType.objects.get_by_natural_key('extras', 'taggeditem'), public_ots)
def test_with_feature(self):
"""
Test that with_feature() returns only ObjectTypes for models which support the specified feature.
"""
bookmarks_ots = ObjectType.objects.with_feature('bookmarks')
self.assertIn(ObjectType.objects.get_by_natural_key('dcim', 'site'), bookmarks_ots)
self.assertNotIn(ObjectType.objects.get_by_natural_key('dcim', 'cabletermination'), bookmarks_ots)