Skip to content

Commit 9c29ebe

Browse files
committed
Merge branch 'feature' into unit-test-suite
2 parents 67fc8a1 + 11ce9f5 commit 9c29ebe

File tree

2 files changed

+134
-45
lines changed

2 files changed

+134
-45
lines changed

netbox_custom_objects/models.py

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,12 @@ def get_absolute_url(self):
115115
},
116116
)
117117

118+
def get_list_url(self):
119+
return reverse(
120+
"plugins:netbox_custom_objects:customobject_list",
121+
kwargs={"custom_object_type": self.custom_object_type.name.lower()},
122+
)
123+
118124

119125
class CustomObjectType(NetBoxModel):
120126
# Class-level cache for generated models
@@ -1130,7 +1136,86 @@ def save(self, *args, **kwargs):
11301136
else:
11311137
old_field = field_type.get_model_field(self.original)
11321138
old_field.contribute_to_class(model, self._original_name)
1133-
schema_editor.alter_field(model, old_field, model_field)
1139+
1140+
# Special handling for MultiObject fields when the name changes
1141+
if (self.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT and
1142+
self.name != self._original_name):
1143+
# For renamed MultiObject fields, we just need to rename the through table
1144+
old_through_table_name = self.original.through_table_name
1145+
new_through_table_name = self.through_table_name
1146+
1147+
# Check if old through table exists
1148+
with connection.cursor() as cursor:
1149+
tables = connection.introspection.table_names(cursor)
1150+
old_table_exists = old_through_table_name in tables
1151+
1152+
if old_table_exists:
1153+
# Create temporary models to represent the old and new through table states
1154+
old_through_meta = type(
1155+
"Meta",
1156+
(),
1157+
{
1158+
"db_table": old_through_table_name,
1159+
"app_label": APP_LABEL,
1160+
"managed": True,
1161+
},
1162+
)
1163+
old_through_model = type(
1164+
f"TempOld{self.original.through_model_name}",
1165+
(models.Model,),
1166+
{
1167+
"__module__": "netbox_custom_objects.models",
1168+
"Meta": old_through_meta,
1169+
"id": models.AutoField(primary_key=True),
1170+
"source": models.ForeignKey(
1171+
model, on_delete=models.CASCADE,
1172+
db_column="source_id", related_name="+"
1173+
),
1174+
"target": models.ForeignKey(
1175+
model, on_delete=models.CASCADE,
1176+
db_column="target_id", related_name="+"
1177+
),
1178+
},
1179+
)
1180+
1181+
new_through_meta = type(
1182+
"Meta",
1183+
(),
1184+
{
1185+
"db_table": new_through_table_name,
1186+
"app_label": APP_LABEL,
1187+
"managed": True,
1188+
},
1189+
)
1190+
new_through_model = type(
1191+
f"TempNew{self.through_model_name}",
1192+
(models.Model,),
1193+
{
1194+
"__module__": "netbox_custom_objects.models",
1195+
"Meta": new_through_meta,
1196+
"id": models.AutoField(primary_key=True),
1197+
"source": models.ForeignKey(
1198+
model, on_delete=models.CASCADE,
1199+
db_column="source_id", related_name="+"
1200+
),
1201+
"target": models.ForeignKey(
1202+
model, on_delete=models.CASCADE,
1203+
db_column="target_id", related_name="+"
1204+
),
1205+
},
1206+
)
1207+
1208+
# Rename the table using Django's schema editor
1209+
schema_editor.alter_db_table(old_through_model, old_through_table_name, new_through_table_name)
1210+
else:
1211+
# No old table exists, create the new through table
1212+
field_type.create_m2m_table(self, model, self.name)
1213+
1214+
# Alter the field normally (this updates the field definition)
1215+
schema_editor.alter_field(model, old_field, model_field)
1216+
else:
1217+
# Normal field alteration
1218+
schema_editor.alter_field(model, old_field, model_field)
11341219

11351220
# Clear and refresh the model cache for this CustomObjectType when a field is modified
11361221
self.custom_object_type.clear_model_cache(self.custom_object_type.id)

netbox_custom_objects/templates/netbox_custom_objects/customobject.html

Lines changed: 48 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -13,55 +13,59 @@
1313
{% block extra_controls %}
1414
{% endblock %}
1515

16-
{% block page-header %}
17-
<div class="container-fluid mt-2 d-print-none">
18-
<div class="d-flex justify-content-between align-items-center mt-2">
16+
{% block breadcrumbs %}
17+
<li class="breadcrumb-item"><a href="{{ object.get_list_url }}">{{ object.custom_object_type.get_verbose_name_plural }}</a></li>
18+
<li class="breadcrumb-item"><a href="{{ object.custom_object_type.get_absolute_url }}">{{ object.custom_object_type }}</a></li>
19+
{% endblock breadcrumbs %}
1920

20-
{# Title #}
21-
<div>
22-
<h1 class="page-title">{% block title %}{{ object }}{% endblock title %}</h1>
23-
{% block subtitle %}{% endblock %}
24-
</div>
21+
{% block object_identifier %}
22+
{{ object|meta:"app_label" }}.{{ object.custom_object_type.name }}:{{ object.pk }}
23+
{% if object.slug %}({{ object.slug }}){% endif %}
24+
{% endblock object_identifier %}
2525

26-
{# Controls #}
27-
<div class="d-print-none">
28-
{% block controls %}
29-
<div class="btn-list">
30-
{% block control-buttons %}
31-
{# Default buttons #}
32-
{% if perms.extras.add_bookmark and object.bookmarks %}
33-
{% custom_object_bookmark_button object %}
34-
{% endif %}
35-
{% if perms.extras.add_subscription and object.subscriptions %}
36-
{% custom_object_subscribe_button object %}
37-
{% endif %}
38-
{% if request.user|can_add:object %}
39-
{% custom_object_clone_button object %}
40-
{% endif %}
41-
{% if request.user|can_change:object %}
42-
{% custom_object_edit_button object %}
43-
{% endif %}
44-
{% if request.user|can_delete:object %}
45-
{% custom_object_delete_button object %}
46-
{% endif %}
47-
{% endblock %}
48-
</div>
26+
{% block title %}{{ object }}{% endblock title %}
4927

50-
{# Custom links #}
51-
<div class="d-flex justify-content-end">
52-
<div class="btn-list">
53-
{% block custom-links %}
54-
{% custom_links object %}
55-
{% endblock custom-links %}
56-
</div>
57-
</div>
58-
59-
{% endblock controls %}
60-
</div>
28+
{% block subtitle %}
29+
<div class="text-secondary fs-5">
30+
{% trans "Created" %} {{ object.created|isodatetime:"minutes" }}
31+
{% if object.last_updated %}
32+
<span class="separator">&middot;</span>
33+
{% trans "Updated" %} {{ object.last_updated|isodatetime:"minutes" }}
34+
{% endif %}
35+
</div>
36+
{% endblock subtitle %}
6137

38+
{% block controls %}
39+
<div class="btn-list justify-content-end mb-2">
40+
{% block control-buttons %}
41+
{# Default buttons #}
42+
{% if perms.extras.add_bookmark and object.bookmarks %}
43+
{% custom_object_bookmark_button object %}
44+
{% endif %}
45+
{% if perms.extras.add_subscription and object.subscriptions %}
46+
{% custom_object_subscribe_button object %}
47+
{% endif %}
48+
{% if request.user|can_add:object %}
49+
{% custom_object_clone_button object %}
50+
{% endif %}
51+
{% if request.user|can_change:object %}
52+
{% custom_object_edit_button object %}
53+
{% endif %}
54+
{% if request.user|can_delete:object %}
55+
{% custom_object_delete_button object %}
56+
{% endif %}
57+
{% endblock %}
6258
</div>
63-
</div>
64-
{% endblock %}
59+
60+
{# Custom links #}
61+
<div class="d-flex justify-content-end">
62+
<div class="btn-list">
63+
{% block custom-links %}
64+
{% custom_links object %}
65+
{% endblock custom-links %}
66+
</div>
67+
</div>
68+
{% endblock controls %}
6569

6670
{% block tabs %}
6771
<ul class="nav nav-tabs" role="presentation">

0 commit comments

Comments
 (0)