mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-23 04:22:01 -06:00
Closes #8511: Enable custom fields and tags for circuit terminations
This commit is contained in:
parent
a57398b0d6
commit
a5124ab9c8
@ -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
|
||||
|
@ -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',
|
||||
]
|
||||
|
@ -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',
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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'),
|
||||
),
|
||||
]
|
@ -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,
|
||||
|
@ -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>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
{% plugin_full_width_page object %}
|
||||
</div>
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
</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 class="row">
|
||||
<div class="col col-md-12">
|
||||
{% plugin_full_width_page object %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -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. #}
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user