diff --git a/netbox/core/migrations/0016_concrete_objecttype.py b/netbox/core/migrations/0016_concrete_objecttype.py new file mode 100644 index 000000000..e17e6f846 --- /dev/null +++ b/netbox/core/migrations/0016_concrete_objecttype.py @@ -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, + ), + ] diff --git a/netbox/core/models/contenttypes.py b/netbox/core/models/contenttypes.py index b0301848f..8180ed241 100644 --- a/netbox/core/models/contenttypes.py +++ b/netbox/core/models/contenttypes.py @@ -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') diff --git a/netbox/extras/migrations/0130_concrete_objecttype.py b/netbox/extras/migrations/0130_concrete_objecttype.py new file mode 100644 index 000000000..dcfad9272 --- /dev/null +++ b/netbox/extras/migrations/0130_concrete_objecttype.py @@ -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' + ), + ), + ] diff --git a/netbox/users/migrations/0010_concrete_objecttype.py b/netbox/users/migrations/0010_concrete_objecttype.py new file mode 100644 index 000000000..d01d9f30f --- /dev/null +++ b/netbox/users/migrations/0010_concrete_objecttype.py @@ -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'), + ), + ]