Automatically create ObjectTypes
Some checks are pending
CI / build (20.x, 3.10) (push) Waiting to run
CI / build (20.x, 3.11) (push) Waiting to run
CI / build (20.x, 3.12) (push) Waiting to run

This commit is contained in:
Jeremy Stretch 2025-07-23 13:00:25 -04:00
parent 68edba8c22
commit e38ba29928
3 changed files with 99 additions and 17 deletions

View File

@ -18,7 +18,8 @@ def populate_object_types(apps, schema_editor):
apps.get_model(ct.app_label, ct.model) apps.get_model(ct.app_label, ct.model)
except LookupError: except LookupError:
continue continue
ObjectType(pk=ct.pk).save_base(raw=True) # TODO assign public/features
ObjectType(pk=ct.pk, features=[]).save_base(raw=True)
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -58,8 +59,7 @@ class Migration(migrations.Migration):
'features', 'features',
django.contrib.postgres.fields.ArrayField( django.contrib.postgres.fields.ArrayField(
base_field=models.CharField(max_length=50), base_field=models.CharField(max_length=50),
blank=True, default=list,
null=True,
size=None size=None
) )
), ),

View File

@ -1,5 +1,6 @@
from django.contrib.contenttypes.models import ContentType, ContentTypeManager from django.contrib.contenttypes.models import ContentType, ContentTypeManager
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import ObjectDoesNotExist
from django.db import models from django.db import models
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -13,8 +14,57 @@ __all__ = (
) )
class ObjectTypeQuerySet(models.QuerySet):
def create(self, **kwargs):
# If attempting to create a new ObjectType for a given app_label & model, replace those kwargs
# with a reference to the ContentType (if one exists).
if (app_label := kwargs.get('app_label')) and (model := kwargs.get('model')):
try:
kwargs['contenttype_ptr'] = ContentType.objects.get(app_label=app_label, model=model)
kwargs.pop('app_label')
kwargs.pop('model')
except ObjectDoesNotExist:
pass
return super().create(**kwargs)
class ObjectTypeManager(ContentTypeManager): class ObjectTypeManager(ContentTypeManager):
def get_queryset(self):
return ObjectTypeQuerySet(self.model, using=self._db)
def create(self, **kwargs):
return self.get_queryset().create(**kwargs)
def get_for_model(self, model, for_concrete_model=True):
"""
Return the ContentType object for a given model, creating the
ContentType if necessary. Lookups are cached so that subsequent lookups
for the same model don't hit the database.
"""
opts = self._get_opts(model, for_concrete_model)
try:
return self._get_from_cache(opts)
except KeyError:
pass
# The ContentType entry was not found in the cache, therefore we
# proceed to load or create it.
try:
# Start with get() and not get_or_create() in order to use
# the db_for_read (see #20401).
ct = self.get(app_label=opts.app_label, model=opts.model_name)
except self.model.DoesNotExist:
# Not found in the database; we proceed to create it. This time
# use get_or_create to take care of any race conditions.
ct, __ = self.get_or_create(
app_label=opts.app_label,
model=opts.model_name,
)
self._add_to_cache(self.db, ct)
return ct
def public(self): def public(self):
""" """
Filter the base queryset to return only ObjectTypes corresponding to "public" models; those which are intended Filter the base queryset to return only ObjectTypes corresponding to "public" models; those which are intended
@ -53,8 +103,7 @@ class ObjectType(ContentType):
) )
features = ArrayField( features = ArrayField(
base_field=models.CharField(max_length=50), base_field=models.CharField(max_length=50),
blank=True, default=list,
null=True,
) )
objects = ObjectTypeManager() objects = ObjectTypeManager()

View File

@ -1,7 +1,8 @@
import logging import logging
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db import ProgrammingError
from django.db.models.fields.reverse_related import ManyToManyRel, ManyToOneRel from django.db.models.fields.reverse_related import ManyToManyRel, ManyToOneRel
from django.db.models.signals import m2m_changed, post_migrate, post_save, pre_delete from django.db.models.signals import m2m_changed, post_migrate, post_save, pre_delete
from django.dispatch import receiver, Signal from django.dispatch import receiver, Signal
@ -39,8 +40,18 @@ post_sync = Signal()
clear_events = Signal() clear_events = Signal()
def model_is_public(model):
return not getattr(model, '_netbox_private', False)
def get_model_features(model):
return [
feature for feature, cls in FEATURES_MAP.items() if issubclass(model, cls)
]
# #
# Model registration # Object types
# #
@receiver(post_migrate) @receiver(post_migrate)
@ -51,19 +62,41 @@ def update_object_types(sender, **kwargs):
app_label, model_name = model._meta.label_lower.split('.') app_label, model_name = model._meta.label_lower.split('.')
# Determine whether model is public # Determine whether model is public
is_public = not getattr(model, '_netbox_private', False) is_public = model_is_public(model)
# Determine NetBox features supported by the model # Determine NetBox features supported by the model
features = [ features = get_model_features(model)
feature for feature, cls in FEATURES_MAP.items() if issubclass(model, cls)
]
# TODO: Update ObjectTypes in bulk # Create/update the ObjectType for the model
# Update the ObjectType for the model try:
ObjectType.objects.filter(app_label=app_label, model=model_name).update( ot = ObjectType.objects.get_by_natural_key(app_label=app_label, model=model_name)
ot.public = is_public
ot.features = features
except ObjectDoesNotExist:
ct = ContentType.objects.get_for_model(model)
ot = ObjectType(
contenttype_ptr=ct,
app_label=app_label,
model=model_name,
public=is_public, public=is_public,
features=features, features=features,
) )
ot.save()
@receiver(post_save, sender=ContentType)
def create_object_type(sender, instance, created, **kwargs):
if created:
model = instance.model_class()
try:
ObjectType.objects.create(
contenttype_ptr=instance,
public=model_is_public(model),
features=get_model_features(model),
)
except ProgrammingError:
# Will fail during migrations if ObjectType hasn't been created yet
pass
# #