|
8 | 8 | "AppsProxy", |
9 | 9 | "generate_model", |
10 | 10 | "get_viewname", |
| 11 | + "get_sub_models", |
11 | 12 | ) |
12 | 13 |
|
13 | 14 |
|
@@ -108,3 +109,67 @@ def generate_model(*args, **kwargs): |
108 | 109 | apps.clear_cache = apps.clear_cache |
109 | 110 |
|
110 | 111 | return model |
| 112 | + |
| 113 | + |
| 114 | +def get_sub_models(custom_object, visited=None): |
| 115 | + """ |
| 116 | + Recursively find all related custom object models for a given CustomObject instance. |
| 117 | + |
| 118 | + This function traverses through the custom object type's fields and finds all |
| 119 | + related custom object types that are referenced through OBJECT or MULTIOBJECT fields. |
| 120 | + |
| 121 | + Args: |
| 122 | + custom_object: An instance of a dynamically created CustomObject class |
| 123 | + visited: Set of custom object type IDs already visited (used for cycle detection) |
| 124 | + |
| 125 | + Returns: |
| 126 | + list: A list of model classes (including the original model) that are related |
| 127 | + through custom object relationships |
| 128 | + """ |
| 129 | + from netbox_custom_objects.models import CustomObjectType |
| 130 | + models = [custom_object] |
| 131 | + if visited is None: |
| 132 | + visited = set() |
| 133 | + |
| 134 | + # Get the custom object type from the instance |
| 135 | + custom_object_type = custom_object.custom_object_type |
| 136 | + |
| 137 | + # If we've already visited this type, return empty list to prevent infinite recursion |
| 138 | + if custom_object_type.id in visited: |
| 139 | + return [] |
| 140 | + |
| 141 | + # Add this type to visited set |
| 142 | + visited.add(custom_object_type.id) |
| 143 | + |
| 144 | + # Get all fields of type OBJECT or MULTIOBJECT |
| 145 | + # Using string constants to avoid import issues |
| 146 | + object_fields = custom_object_type.fields.filter( |
| 147 | + type__in=["object", "multiobject"], |
| 148 | + related_object_type__isnull=False, |
| 149 | + related_object_type__app_label=APP_LABEL |
| 150 | + ) |
| 151 | + |
| 152 | + # For each object field, check if it references another custom object |
| 153 | + for field in object_fields: |
| 154 | + related_object_type = field.related_object_type |
| 155 | + |
| 156 | + # Get the related custom object type |
| 157 | + related_custom_object_type = CustomObjectType.objects.get( |
| 158 | + object_type=related_object_type |
| 159 | + ) |
| 160 | + |
| 161 | + # Get the model for the related custom object type |
| 162 | + related_model = related_custom_object_type.get_model() |
| 163 | + |
| 164 | + # Recursively get sub-models from the related type |
| 165 | + sub_models = get_sub_models(related_model(), visited.copy()) |
| 166 | + |
| 167 | + # Add the related model and its sub-models to our list |
| 168 | + if related_model not in models: |
| 169 | + models.append(related_model) |
| 170 | + |
| 171 | + for sub_model in sub_models: |
| 172 | + if sub_model not in models: |
| 173 | + models.append(sub_model) |
| 174 | + |
| 175 | + return models |
0 commit comments