Просмотр исходного кода

Closes #8511: Enable custom fields and tags for circuit terminations

jeremystretch 3 лет назад
Родитель
Сommit
a5124ab9c8

+ 3 - 0
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

+ 2 - 2
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',
         ]

+ 1 - 1
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',

+ 2 - 2
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",

+ 2 - 1
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

+ 24 - 0
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'),
+        ),
+    ]

+ 11 - 2
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,

+ 70 - 66
netbox/templates/circuits/circuit.html

@@ -8,74 +8,78 @@
 {% endblock %}
 
 {% block content %}
-<div class="row">
-	<div class="col col-md-6">
-        <div class="card">
-            <h5 class="card-header">
-                Circuit
-            </h5>
-            <div class="card-body">
-                <table class="table table-hover attr-table">
-                    <tr>
-                        <th scope="row">Provider</th>
-                        <td>{{ object.provider|linkify }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">Circuit ID</th>
-                        <td>{{ object.cid }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">Type</th>
-                        <td>{{ object.type|linkify }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">Status</th>
-                        <td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">Tenant</th>
-                        <td>
-                            {% if object.tenant.group %}
-                                {{ object.tenant.group|linkify }} /
-                            {% endif %}
-                            {{ object.tenant|linkify|placeholder }}
-                        </td>
-                    </tr>
-                    <tr>
-                        <th scope="row">Install Date</th>
-                        <td>{{ object.install_date|annotated_date|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">Termination Date</th>
-                        <td>{{ object.termination_date|annotated_date|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">Commit Rate</th>
-                        <td>{{ object.commit_rate|humanize_speed|placeholder }}</td>
-                    </tr>
-                    <tr>
-                        <th scope="row">Description</th>
-                        <td>{{ object.description|placeholder }}</td>
-                    </tr>
-                </table>
-            </div>
+  <div class="row">
+    <div class="col col-md-6">
+      <div class="card">
+        <h5 class="card-header">Circuit</h5>
+        <div class="card-body">
+          <table class="table table-hover attr-table">
+            <tr>
+              <th scope="row">Provider</th>
+              <td>{{ object.provider|linkify }}</td>
+            </tr>
+            <tr>
+              <th scope="row">Circuit ID</th>
+              <td>{{ object.cid }}</td>
+            </tr>
+            <tr>
+              <th scope="row">Type</th>
+              <td>{{ object.type|linkify }}</td>
+            </tr>
+            <tr>
+              <th scope="row">Status</th>
+              <td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
+            </tr>
+            <tr>
+              <th scope="row">Tenant</th>
+              <td>
+                {% if object.tenant.group %}
+                  {{ object.tenant.group|linkify }} /
+                {% endif %}
+                {{ object.tenant|linkify|placeholder }}
+              </td>
+            </tr>
+            <tr>
+              <th scope="row">Install Date</th>
+              <td>{{ object.install_date|annotated_date|placeholder }}</td>
+            </tr>
+            <tr>
+              <th scope="row">Termination Date</th>
+              <td>{{ object.termination_date|annotated_date|placeholder }}</td>
+            </tr>
+            <tr>
+              <th scope="row">Commit Rate</th>
+              <td>{{ object.commit_rate|humanize_speed|placeholder }}</td>
+            </tr>
+            <tr>
+              <th scope="row">Description</th>
+              <td>{{ object.description|placeholder }}</td>
+            </tr>
+          </table>
         </div>
-        {% include 'inc/panels/custom_fields.html' %}
-        {% include 'inc/panels/tags.html' %}
-        {% include 'inc/panels/comments.html' %}
-        {% plugin_left_page object %}
-	</div>
-	<div class="col col-md-6">
-    {% 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 %}
+      </div>
+      {% include 'inc/panels/custom_fields.html' %}
+      {% include 'inc/panels/tags.html' %}
+      {% plugin_left_page object %}
+    </div>
+    <div class="col col-md-6">
+      {% include 'inc/panels/comments.html' %}
+      {% include 'inc/panels/contacts.html' %}
+      {% include 'inc/panels/image_attachments.html' %}
+      {% plugin_right_page object %}
+    </div>
+  </div>
+  <div class="row">
+    <div class="col col-md-6">
+      {% include 'circuits/inc/circuit_termination.html' with termination=object.termination_a side='A' %}
+    </div>
+    <div class="col col-md-6">
+      {% include 'circuits/inc/circuit_termination.html' with termination=object.termination_z side='Z' %}
+    </div>
   </div>
-</div>
-<div class="row">
+  <div class="row">
     <div class="col col-md-12">
-        {% plugin_full_width_page object %}
+      {% plugin_full_width_page object %}
     </div>
-</div>
+  </div>
 {% endblock %}

+ 8 - 0
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 %}
       <div class="row mb-2">
@@ -47,6 +48,13 @@
     {% render_field form.pp_info %}
     {% render_field form.description %}
   </div>
+
+  <div class="field-group my-5">
+    <div class="row mb-2">
+      <h5 class="offset-sm-3">Custom Fields</h5>
+    </div>
+    {% render_custom_fields form %}
+  </div>
 {% endblock %}
 
 {# Override buttons block, 'Create & Add Another'/'_addanother' is not needed on a circuit. #}

+ 30 - 3
netbox/templates/circuits/inc/circuit_termination.html

@@ -2,7 +2,6 @@
 
 <div class="card">
     <div class="card-header">
-        <strong class="d-block d-md-inline mb-3 mb-md-0">Termination - {{ side }} Side</strong>
         <div class="float-md-end">
             {% if not termination and perms.circuits.add_circuittermination %}
                 <a href="{% url 'circuits:circuittermination_add' %}?circuit={{ object.pk }}&term_side={{ side }}&return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-success lh-1">
@@ -10,10 +9,10 @@
                 </a>
             {% endif %}
             {% if termination and perms.circuits.change_circuittermination %}
-                <a href="{% url 'circuits:circuittermination_edit' pk=termination.pk %}" class="btn btn-sm btn-warning lh-1">
+                <a href="{% url 'circuits:circuittermination_edit' pk=termination.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-warning lh-1">
                     <span class="mdi mdi-pencil" aria-hidden="true"></span> Edit
                 </a>
-                <a href="{% url 'circuits:circuit_terminations_swap' pk=object.pk %}" class="btn btn-sm btn-primary lh-1">
+                <a href="{% url 'circuits:circuit_terminations_swap' pk=object.pk %}?return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-primary lh-1">
                     <span class="mdi mdi-swap-vertical" aria-hidden="true"></span> Swap
                 </a>
             {% endif %}
@@ -23,6 +22,7 @@
                 </a>
             {% endif %}
         </div>
+        <h5>Termination {{ side }}</h5>
     </div>
     <div class="card-body">
       {% if termination %}
@@ -110,6 +110,33 @@
                 <td>Description</td>
                 <td>{{ termination.description|placeholder }}</td>
             </tr>
+            <tr>
+              <td>Tags</td>
+              <td>
+                {% for tag in termination.tags.all %}
+                  {% tag tag %}
+                {% empty %}
+                  {{ ''|placeholder }}
+                {% endfor %}
+              </td>
+            </tr>
+          {% for group_name, fields in termination.get_custom_fields_by_group.items %}
+            <tr>
+              <td colspan="2">
+                <strong>{{ group_name|default:"Custom Fields" }}</strong>
+              </td>
+            </tr>
+            {% for field, value in fields.items %}
+              <tr>
+                <td>
+                  <span title="{{ field.description|escape }}">{{ field }}</span>
+                </td>
+                <td>
+                  {% customfield_value field value %}
+                </td>
+              </tr>
+            {% endfor %}
+          {% endfor %}
         </table>
     {% else %}
         <span class="text-muted">None</span>