Skip to content

Commit 78994a6

Browse files
authored
NPL-374 Add Mixin Support and make CustomObject class ABC (#110)
1 parent cc6802b commit 78994a6

File tree

11 files changed

+400
-236
lines changed

11 files changed

+400
-236
lines changed

netbox_custom_objects/__init__.py

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,5 @@ class CustomObjectsPluginConfig(PluginConfig):
1313
required_settings = []
1414
template_extensions = "template_content.template_extensions"
1515

16-
# def get_model(self, model_name, require_ready=True):
17-
# if require_ready:
18-
# self.apps.check_models_ready()
19-
# else:
20-
# self.apps.check_apps_ready()
21-
#
22-
# if model_name.lower() in self.models:
23-
# return self.models[model_name.lower()]
24-
#
25-
# from .models import CustomObjectType
26-
# if "table" not in model_name.lower() or "model" not in model_name.lower():
27-
# raise LookupError(
28-
# "App '%s' doesn't have a '%s' model." % (self.label, model_name)
29-
# )
30-
#
31-
# custom_object_type_id = int(model_name.replace("table", "").replace("model", ""))
32-
#
33-
# try:
34-
# obj = CustomObjectType.objects.get(pk=custom_object_type_id)
35-
# except CustomObjectType.DoesNotExist:
36-
# raise LookupError(
37-
# "App '%s' doesn't have a '%s' model." % (self.label, model_name)
38-
# )
39-
# return obj.get_model()
40-
4116

4217
config = CustomObjectsPluginConfig

netbox_custom_objects/api/views.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from rest_framework.routers import APIRootView
33
from rest_framework.viewsets import ModelViewSet
44

5-
from netbox_custom_objects.models import CustomObject, CustomObjectType, CustomObjectTypeField
5+
from netbox_custom_objects.models import CustomObjectType, CustomObjectTypeField
66

77
from . import serializers
88

@@ -18,7 +18,6 @@ class CustomObjectTypeViewSet(ModelViewSet):
1818

1919

2020
class CustomObjectViewSet(ModelViewSet):
21-
queryset = CustomObject.objects.all()
2221
serializer_class = serializers.CustomObjectSerializer
2322
model = None
2423

netbox_custom_objects/field_types.py

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -611,16 +611,15 @@ def get_through_model(self, field, model=None):
611611
and field.custom_object_type.id == custom_object_type_id
612612
)
613613

614+
# Use the actual model if provided, otherwise use string reference
615+
source_model = model if model else "netbox_custom_objects.CustomObject"
616+
614617
attrs = {
615618
"__module__": "netbox_custom_objects.models",
616619
"Meta": meta,
617620
"id": models.AutoField(primary_key=True),
618621
"source": models.ForeignKey(
619-
(
620-
"self"
621-
if is_self_referential
622-
else (model or "netbox_custom_objects.CustomObject")
623-
),
622+
source_model,
624623
on_delete=models.CASCADE,
625624
related_name="+",
626625
db_column="source_id",
@@ -652,6 +651,8 @@ def get_model_field(self, field, **kwargs):
652651
and field.custom_object_type.id == custom_object_type_id
653652
)
654653

654+
# For now, we'll create the through model with string references
655+
# and resolve them later in after_model_generation
655656
through = self.get_through_model(field)
656657

657658
# For self-referential fields, use 'self' as the target
@@ -694,8 +695,14 @@ def after_model_generation(self, instance, model, field_name):
694695
if getattr(field, "_is_self_referential", False):
695696
field.remote_field.model = model
696697
through_model = field.remote_field.through
697-
through_model._meta.get_field("target").remote_field.model = model
698-
through_model._meta.get_field("target").related_model = model
698+
699+
# Update both source and target fields to point to the same model
700+
source_field = through_model._meta.get_field("source")
701+
target_field = through_model._meta.get_field("target")
702+
source_field.remote_field.model = model
703+
source_field.related_model = model
704+
target_field.remote_field.model = model
705+
target_field.related_model = model
699706
return
700707

701708
content_type = ContentType.objects.get(pk=instance.related_object_type_id)
@@ -713,12 +720,19 @@ def after_model_generation(self, instance, model, field_name):
713720
to_ct = f"{content_type.app_label}.{content_type.model}"
714721
to_model = apps.get_model(to_ct)
715722

716-
# Update the M2M field's model references
723+
# Update through model's fields
717724
field.remote_field.model = to_model
718725

719726
# Update through model's target field
720727
through_model = field.remote_field.through
728+
source_field = through_model._meta.get_field("source")
721729
target_field = through_model._meta.get_field("target")
730+
731+
# Source field should point to the current model
732+
source_field.remote_field.model = model
733+
source_field.related_model = model
734+
735+
# Target field should point to the related model
722736
target_field.remote_field.model = to_model
723737
target_field.related_model = to_model
724738

@@ -751,15 +765,24 @@ def create_m2m_table(self, instance, model, field_name):
751765

752766
# Create the through model with actual model references
753767
through = self.get_through_model(instance, model)
754-
through._meta.get_field("target").remote_field.model = to_model
755-
through._meta.get_field("target").related_model = to_model
768+
769+
# Update the through model's foreign key references
770+
source_field = through._meta.get_field("source")
771+
target_field = through._meta.get_field("target")
772+
773+
# Source field should point to the current model
774+
source_field.remote_field.model = model
775+
source_field.remote_field.field_name = model._meta.pk.name
776+
source_field.related_model = model
777+
778+
# Target field should point to the related model
779+
target_field.remote_field.model = to_model
780+
target_field.remote_field.field_name = to_model._meta.pk.name
781+
target_field.related_model = to_model
756782

757783
# Register the model with Django's app registry
758784
apps = model._meta.apps
759785

760-
# if app_label is None:
761-
# app_label = str(uuid.uuid4()) + "_database_table"
762-
# apps = AppsProxy(dynamic_models=None, app_label=app_label)
763786
try:
764787
through_model = apps.get_model(APP_LABEL, instance.through_model_name)
765788
except LookupError:
@@ -769,6 +792,7 @@ def create_m2m_table(self, instance, model, field_name):
769792
# Update the M2M field's through model and target model
770793
field.remote_field.through = through_model
771794
field.remote_field.model = to_model
795+
field.remote_field.field_name = to_model._meta.pk.name
772796

773797
# Create the through table
774798
with connection.schema_editor() as schema_editor:
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 5.2.2 on 2025-07-15 17:59
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("netbox_custom_objects", "0001_initial"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="customobject",
15+
name="created",
16+
field=models.DateTimeField(auto_now_add=True, null=True),
17+
),
18+
migrations.AddField(
19+
model_name="customobject",
20+
name="last_updated",
21+
field=models.DateTimeField(auto_now=True, null=True),
22+
),
23+
]
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Generated by Django 5.2.2 on 2025-07-18 20:24
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
(
10+
"netbox_custom_objects",
11+
"0002_customobject_created_customobject_last_updated",
12+
),
13+
]
14+
15+
operations = [
16+
migrations.DeleteModel(
17+
name="CustomObject",
18+
),
19+
]

0 commit comments

Comments
 (0)