diff --git a/docs/release-notes/version-3.3.md b/docs/release-notes/version-3.3.md
index f5cb8eee1..d158e6cf9 100644
--- a/docs/release-notes/version-3.3.md
+++ b/docs/release-notes/version-3.3.md
@@ -28,6 +28,7 @@
* [#8222](https://github.com/netbox-community/netbox/issues/8222) - Enable the assignment of a VM to a specific host device within a cluster
* [#8471](https://github.com/netbox-community/netbox/issues/8471) - Add `status` field to Cluster
* [#8495](https://github.com/netbox-community/netbox/issues/8495) - Enable custom field grouping
+* [#8511](https://github.com/netbox-community/netbox/issues/8511) - Enable custom fields and tags for circuit terminations
* [#8995](https://github.com/netbox-community/netbox/issues/8995) - Enable arbitrary ordering of REST API results
* [#9070](https://github.com/netbox-community/netbox/issues/9070) - Hide navigation menu items based on user permissions
* [#9166](https://github.com/netbox-community/netbox/issues/9166) - Add UI visibility toggle for custom fields
@@ -51,6 +52,8 @@
* circuits.Circuit
* Added optional `termination_date` field
+* circuits.CircuitTermination
+ * Added 'custom_fields' and 'tags' fields
* dcim.Device
* The `position` field has been changed from an integer to a decimal
* dcim.DeviceType
diff --git a/netbox/circuits/api/serializers.py b/netbox/circuits/api/serializers.py
index 2bb3cd266..844cfce89 100644
--- a/netbox/circuits/api/serializers.py
+++ b/netbox/circuits/api/serializers.py
@@ -98,7 +98,7 @@ class CircuitSerializer(NetBoxModelSerializer):
]
-class CircuitTerminationSerializer(ValidatedModelSerializer, LinkTerminationSerializer):
+class CircuitTerminationSerializer(NetBoxModelSerializer, LinkTerminationSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittermination-detail')
circuit = NestedCircuitSerializer()
site = NestedSiteSerializer(required=False, allow_null=True)
@@ -110,5 +110,5 @@ class CircuitTerminationSerializer(ValidatedModelSerializer, LinkTerminationSeri
fields = [
'id', 'url', 'display', 'circuit', 'term_side', 'site', 'provider_network', 'port_speed', 'upstream_speed',
'xconnect_id', 'pp_info', 'description', 'mark_connected', 'cable', 'link_peer', 'link_peer_type',
- '_occupied', 'created', 'last_updated',
+ '_occupied', 'tags', 'custom_fields', 'created', 'last_updated',
]
diff --git a/netbox/circuits/filtersets.py b/netbox/circuits/filtersets.py
index 67a0d1b02..a74ff5c5a 100644
--- a/netbox/circuits/filtersets.py
+++ b/netbox/circuits/filtersets.py
@@ -198,7 +198,7 @@ class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilte
).distinct()
-class CircuitTerminationFilterSet(ChangeLoggedModelFilterSet, CableTerminationFilterSet):
+class CircuitTerminationFilterSet(NetBoxModelFilterSet, CableTerminationFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
diff --git a/netbox/circuits/forms/models.py b/netbox/circuits/forms/models.py
index 907c39586..7bd7abbbf 100644
--- a/netbox/circuits/forms/models.py
+++ b/netbox/circuits/forms/models.py
@@ -116,7 +116,7 @@ class CircuitForm(TenancyForm, NetBoxModelForm):
}
-class CircuitTerminationForm(BootstrapMixin, forms.ModelForm):
+class CircuitTerminationForm(NetBoxModelForm):
provider = DynamicModelChoiceField(
queryset=Provider.objects.all(),
required=False,
@@ -161,7 +161,7 @@ class CircuitTerminationForm(BootstrapMixin, forms.ModelForm):
model = CircuitTermination
fields = [
'provider', 'circuit', 'term_side', 'region', 'site_group', 'site', 'provider_network', 'mark_connected',
- 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', 'description',
+ 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', 'description', 'tags',
]
help_texts = {
'port_speed': "Physical circuit speed",
diff --git a/netbox/circuits/graphql/types.py b/netbox/circuits/graphql/types.py
index 027b53203..094b78d07 100644
--- a/netbox/circuits/graphql/types.py
+++ b/netbox/circuits/graphql/types.py
@@ -1,4 +1,5 @@
from circuits import filtersets, models
+from extras.graphql.mixins import CustomFieldsMixin, TagsMixin
from netbox.graphql.types import ObjectType, OrganizationalObjectType, NetBoxObjectType
__all__ = (
@@ -10,7 +11,7 @@ __all__ = (
)
-class CircuitTerminationType(ObjectType):
+class CircuitTerminationType(CustomFieldsMixin, TagsMixin, ObjectType):
class Meta:
model = models.CircuitTermination
diff --git a/netbox/circuits/migrations/0037_circuittermination_tags_custom_fields.py b/netbox/circuits/migrations/0037_circuittermination_tags_custom_fields.py
new file mode 100644
index 000000000..c87bc4219
--- /dev/null
+++ b/netbox/circuits/migrations/0037_circuittermination_tags_custom_fields.py
@@ -0,0 +1,24 @@
+import django.core.serializers.json
+from django.db import migrations, models
+import taggit.managers
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('extras', '0076_configcontext_locations'),
+ ('circuits', '0036_circuit_termination_date'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='circuittermination',
+ name='custom_field_data',
+ field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
+ ),
+ migrations.AddField(
+ model_name='circuittermination',
+ name='tags',
+ field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
+ ),
+ ]
diff --git a/netbox/circuits/models/circuits.py b/netbox/circuits/models/circuits.py
index 5df6f1b85..cf6ffc503 100644
--- a/netbox/circuits/models/circuits.py
+++ b/netbox/circuits/models/circuits.py
@@ -5,7 +5,9 @@ from django.urls import reverse
from circuits.choices import *
from dcim.models import LinkTermination
-from netbox.models import ChangeLoggedModel, OrganizationalModel, NetBoxModel
+from netbox.models import (
+ ChangeLoggedModel, CustomFieldsMixin, CustomLinksMixin, OrganizationalModel, NetBoxModel, TagsMixin,
+)
from netbox.models.features import WebhooksMixin
__all__ = (
@@ -141,7 +143,14 @@ class Circuit(NetBoxModel):
return CircuitStatusChoices.colors.get(self.status)
-class CircuitTermination(WebhooksMixin, ChangeLoggedModel, LinkTermination):
+class CircuitTermination(
+ CustomFieldsMixin,
+ CustomLinksMixin,
+ TagsMixin,
+ WebhooksMixin,
+ ChangeLoggedModel,
+ LinkTermination
+):
circuit = models.ForeignKey(
to='circuits.Circuit',
on_delete=models.CASCADE,
diff --git a/netbox/templates/circuits/circuit.html b/netbox/templates/circuits/circuit.html
index a4c41f871..a11139032 100644
--- a/netbox/templates/circuits/circuit.html
+++ b/netbox/templates/circuits/circuit.html
@@ -8,74 +8,78 @@
{% endblock %}
{% block content %}
-
-
-
-
-
-
-
- Provider |
- {{ object.provider|linkify }} |
-
-
- Circuit ID |
- {{ object.cid }} |
-
-
- Type |
- {{ object.type|linkify }} |
-
-
- Status |
- {% badge object.get_status_display bg_color=object.get_status_color %} |
-
-
- Tenant |
-
- {% if object.tenant.group %}
- {{ object.tenant.group|linkify }} /
- {% endif %}
- {{ object.tenant|linkify|placeholder }}
- |
-
-
- Install Date |
- {{ object.install_date|annotated_date|placeholder }} |
-
-
- Termination Date |
- {{ object.termination_date|annotated_date|placeholder }} |
-
-
- Commit Rate |
- {{ object.commit_rate|humanize_speed|placeholder }} |
-
-
- Description |
- {{ object.description|placeholder }} |
-
-
-
+
+
+
+
+
+
+
+ Provider |
+ {{ object.provider|linkify }} |
+
+
+ Circuit ID |
+ {{ object.cid }} |
+
+
+ Type |
+ {{ object.type|linkify }} |
+
+
+ Status |
+ {% badge object.get_status_display bg_color=object.get_status_color %} |
+
+
+ Tenant |
+
+ {% if object.tenant.group %}
+ {{ object.tenant.group|linkify }} /
+ {% endif %}
+ {{ object.tenant|linkify|placeholder }}
+ |
+
+
+ Install Date |
+ {{ object.install_date|annotated_date|placeholder }} |
+
+
+ Termination Date |
+ {{ object.termination_date|annotated_date|placeholder }} |
+
+
+ Commit Rate |
+ {{ object.commit_rate|humanize_speed|placeholder }} |
+
+
+ Description |
+ {{ object.description|placeholder }} |
+
+
- {% include 'inc/panels/custom_fields.html' %}
- {% include 'inc/panels/tags.html' %}
- {% include 'inc/panels/comments.html' %}
- {% plugin_left_page object %}
-
-
- {% include 'circuits/inc/circuit_termination.html' with termination=object.termination_a side='A' %}
- {% include 'circuits/inc/circuit_termination.html' with termination=object.termination_z side='Z' %}
- {% include 'inc/panels/contacts.html' %}
- {% include 'inc/panels/image_attachments.html' %}
- {% plugin_right_page object %}
-
-
-
-
- {% plugin_full_width_page object %}
+
+ {% include 'inc/panels/custom_fields.html' %}
+ {% include 'inc/panels/tags.html' %}
+ {% plugin_left_page object %}
-
+
+ {% include 'inc/panels/comments.html' %}
+ {% include 'inc/panels/contacts.html' %}
+ {% include 'inc/panels/image_attachments.html' %}
+ {% plugin_right_page object %}
+
+
+
+
+ {% include 'circuits/inc/circuit_termination.html' with termination=object.termination_a side='A' %}
+
+
+ {% include 'circuits/inc/circuit_termination.html' with termination=object.termination_z side='Z' %}
+
+
+
+
+ {% plugin_full_width_page object %}
+
+
{% endblock %}
diff --git a/netbox/templates/circuits/circuittermination_edit.html b/netbox/templates/circuits/circuittermination_edit.html
index f8393f945..606e12b5e 100644
--- a/netbox/templates/circuits/circuittermination_edit.html
+++ b/netbox/templates/circuits/circuittermination_edit.html
@@ -10,6 +10,7 @@
{% render_field form.provider %}
{% render_field form.circuit %}
{% render_field form.term_side %}
+ {% render_field form.tags %}
{% render_field form.mark_connected %}
{% with providernetwork_tab_active=form.initial.provider_network %}
@@ -47,6 +48,13 @@
{% render_field form.pp_info %}
{% render_field form.description %}
+
+
+
+
Custom Fields
+
+ {% render_custom_fields form %}
+
{% endblock %}
{# Override buttons block, 'Create & Add Another'/'_addanother' is not needed on a circuit. #}
diff --git a/netbox/templates/circuits/inc/circuit_termination.html b/netbox/templates/circuits/inc/circuit_termination.html
index b673cd4a3..f6bb377ec 100644
--- a/netbox/templates/circuits/inc/circuit_termination.html
+++ b/netbox/templates/circuits/inc/circuit_termination.html
@@ -2,7 +2,6 @@
{% if termination %}
@@ -110,6 +110,33 @@
Description |
{{ termination.description|placeholder }} |
+
+ Tags |
+
+ {% for tag in termination.tags.all %}
+ {% tag tag %}
+ {% empty %}
+ {{ ''|placeholder }}
+ {% endfor %}
+ |
+
+ {% for group_name, fields in termination.get_custom_fields_by_group.items %}
+
+
+ {{ group_name|default:"Custom Fields" }}
+ |
+
+ {% for field, value in fields.items %}
+
+
+ {{ field }}
+ |
+
+ {% customfield_value field value %}
+ |
+
+ {% endfor %}
+ {% endfor %}
{% else %}
None