Convert ObjectType to a concrete child model of ContentType

This commit is contained in:
Jeremy Stretch 2025-07-22 10:11:11 -04:00
parent b88b5b0b1b
commit ae6d2b2edf
4 changed files with 171 additions and 5 deletions

View File

@ -0,0 +1,75 @@
import django.contrib.postgres.fields
import django.db.models.deletion
from django.db import migrations, models
import core.models.contenttypes
def populate_object_types(apps, schema_editor):
"""
Create an ObjectType record for each valid ContentType.
"""
ContentType = apps.get_model('contenttypes', 'ContentType')
ObjectType = apps.get_model('core', 'ObjectType')
for ct in ContentType.objects.all():
try:
# Validate ContentType
apps.get_model(ct.app_label, ct.model)
except LookupError:
continue
ObjectType(pk=ct.pk).save_base(raw=True)
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('core', '0015_remove_redundant_indexes'),
]
operations = [
# Delete the proxy model from the migration state
migrations.DeleteModel(
name='ObjectType',
),
# Create the new concrete model
migrations.CreateModel(
name='ObjectType',
fields=[
(
'contenttype_ptr',
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to='contenttypes.contenttype',
related_name='object_type'
)
),
(
'features',
django.contrib.postgres.fields.ArrayField(
base_field=models.CharField(max_length=50),
blank=True,
null=True,
size=None
)
),
],
options={
'verbose_name': 'object type',
'verbose_name_plural': 'object types',
},
bases=('contenttypes.contenttype',),
managers=[
('objects', core.models.contenttypes.ObjectTypeManager()),
],
),
# Create an ObjectType record for each ContentType
migrations.RunPython(
code=populate_object_types,
reverse_code=migrations.RunPython.noop,
),
]

View File

@ -1,5 +1,8 @@
from django.contrib.contenttypes.models import ContentType, ContentTypeManager
from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.db.models import Q
from django.utils.translation import gettext as _
from netbox.registry import registry
@ -17,8 +20,8 @@ class ObjectTypeManager(ContentTypeManager):
in registry['models'] and intended for reference by other objects.
"""
q = Q()
for app_label, models in registry['models'].items():
q |= Q(app_label=app_label, model__in=models)
for app_label, model_list in registry['models'].items():
q |= Q(app_label=app_label, model__in=model_list)
return self.get_queryset().filter(q)
def with_feature(self, feature):
@ -34,8 +37,8 @@ class ObjectTypeManager(ContentTypeManager):
)
q = Q()
for app_label, models in registry['model_features'][feature].items():
q |= Q(app_label=app_label, model__in=models)
for app_label, model_list in registry['model_features'][feature].items():
q |= Q(app_label=app_label, model__in=model_list)
return self.get_queryset().filter(q)
@ -44,7 +47,22 @@ class ObjectType(ContentType):
"""
Wrap Django's native ContentType model to use our custom manager.
"""
contenttype_ptr = models.OneToOneField(
on_delete=models.CASCADE,
to='contenttypes.ContentType',
parent_link=True,
primary_key=True,
serialize=False,
related_name='object_type'
)
features = ArrayField(
base_field=models.CharField(max_length=50),
blank=True,
null=True,
)
objects = ObjectTypeManager()
class Meta:
proxy = True
verbose_name = _('object type')
verbose_name_plural = _('object types')

View File

@ -0,0 +1,56 @@
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0016_concrete_objecttype'),
('extras', '0129_fix_script_paths'),
]
operations = [
migrations.AlterField(
model_name='Tag',
name='object_types',
field=models.ManyToManyField(blank=True, related_name='+', to='core.objecttype'),
),
migrations.AlterField(
model_name='CustomField',
name='related_object_type',
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='core.objecttype'
),
),
migrations.AlterField(
model_name='CustomField',
name='object_types',
field=models.ManyToManyField(related_name='custom_fields', to='core.objecttype'),
),
migrations.AlterField(
model_name='EventRule',
name='object_types',
field=models.ManyToManyField(related_name='event_rules', to='core.objecttype'),
),
migrations.AlterField(
model_name='CustomLink',
name='object_types',
field=models.ManyToManyField(related_name='custom_links', to='core.objecttype'),
),
migrations.AlterField(
model_name='ExportTemplate',
name='object_types',
field=models.ManyToManyField(related_name='export_templates', to='core.objecttype'),
),
migrations.AlterField(
model_name='SavedFilter',
name='object_types',
field=models.ManyToManyField(related_name='saved_filters', to='core.objecttype'),
),
migrations.AlterField(
model_name='TableConfig',
name='object_type',
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='table_configs', to='core.objecttype'
),
),
]

View File

@ -0,0 +1,17 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0016_concrete_objecttype'),
('users', '0009_update_group_perms'),
]
operations = [
migrations.AlterField(
model_name='ObjectPermission',
name='object_types',
field=models.ManyToManyField(related_name='object_permissions', to='core.objecttype'),
),
]