mirror of
				https://github.com/netbox-community/netbox.git
				synced 2024-05-10 07:54:54 +00:00 
			
		
		
		
	Enable attaching circuit terminations to clouds
This commit is contained in:
		@@ -63,12 +63,13 @@ class CircuitTypeSerializer(OrganizationalModelSerializer):
 | 
			
		||||
class CircuitCircuitTerminationSerializer(WritableNestedSerializer, ConnectedEndpointSerializer):
 | 
			
		||||
    url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittermination-detail')
 | 
			
		||||
    site = NestedSiteSerializer()
 | 
			
		||||
    cloud = NestedCloudSerializer()
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = CircuitTermination
 | 
			
		||||
        fields = [
 | 
			
		||||
            'id', 'url', 'display', 'site', 'port_speed', 'upstream_speed', 'xconnect_id', 'connected_endpoint',
 | 
			
		||||
            'connected_endpoint_type', 'connected_endpoint_reachable',
 | 
			
		||||
            'id', 'url', 'display', 'site', 'cloud', 'port_speed', 'upstream_speed', 'xconnect_id',
 | 
			
		||||
            'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable',
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -93,13 +94,14 @@ class CircuitSerializer(PrimaryModelSerializer):
 | 
			
		||||
class CircuitTerminationSerializer(BaseModelSerializer, CableTerminationSerializer, ConnectedEndpointSerializer):
 | 
			
		||||
    url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittermination-detail')
 | 
			
		||||
    circuit = NestedCircuitSerializer()
 | 
			
		||||
    site = NestedSiteSerializer()
 | 
			
		||||
    site = NestedSiteSerializer(required=False)
 | 
			
		||||
    cloud = NestedCloudSerializer(required=False)
 | 
			
		||||
    cable = NestedCableSerializer(read_only=True)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = CircuitTermination
 | 
			
		||||
        fields = [
 | 
			
		||||
            'id', 'url', 'display', 'circuit', 'term_side', 'site', 'port_speed', 'upstream_speed', 'xconnect_id',
 | 
			
		||||
            'pp_info', 'description', 'mark_connected', 'cable', 'cable_peer', 'cable_peer_type', 'connected_endpoint',
 | 
			
		||||
            'connected_endpoint_type', 'connected_endpoint_reachable', '_occupied',
 | 
			
		||||
            'id', 'url', 'display', 'circuit', 'term_side', 'site', 'cloud', 'port_speed', 'upstream_speed',
 | 
			
		||||
            'xconnect_id', 'pp_info', 'description', 'mark_connected', 'cable', 'cable_peer', 'cable_peer_type',
 | 
			
		||||
            'connected_endpoint', 'connected_endpoint_type', 'connected_endpoint_reachable', '_occupied',
 | 
			
		||||
        ]
 | 
			
		||||
 
 | 
			
		||||
@@ -221,6 +221,10 @@ class CircuitTerminationFilterSet(BaseFilterSet, CableTerminationFilterSet, Path
 | 
			
		||||
        to_field_name='slug',
 | 
			
		||||
        label='Site (slug)',
 | 
			
		||||
    )
 | 
			
		||||
    cloud_id = django_filters.ModelMultipleChoiceFilter(
 | 
			
		||||
        queryset=Cloud.objects.all(),
 | 
			
		||||
        label='Cloud (ID)',
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = CircuitTermination
 | 
			
		||||
 
 | 
			
		||||
@@ -423,13 +423,18 @@ class CircuitTerminationForm(BootstrapMixin, forms.ModelForm):
 | 
			
		||||
        query_params={
 | 
			
		||||
            'region_id': '$region',
 | 
			
		||||
            'group_id': '$site_group',
 | 
			
		||||
        }
 | 
			
		||||
        },
 | 
			
		||||
        required=False
 | 
			
		||||
    )
 | 
			
		||||
    cloud = DynamicModelChoiceField(
 | 
			
		||||
        queryset=Cloud.objects.all(),
 | 
			
		||||
        required=False
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = CircuitTermination
 | 
			
		||||
        fields = [
 | 
			
		||||
            'term_side', 'region', 'site_group', 'site', 'mark_connected', 'port_speed', 'upstream_speed',
 | 
			
		||||
            'term_side', 'region', 'site_group', 'site', 'cloud', 'mark_connected', 'port_speed', 'upstream_speed',
 | 
			
		||||
            'xconnect_id', 'pp_info', 'description',
 | 
			
		||||
        ]
 | 
			
		||||
        help_texts = {
 | 
			
		||||
@@ -442,3 +447,8 @@ class CircuitTerminationForm(BootstrapMixin, forms.ModelForm):
 | 
			
		||||
            'port_speed': SelectSpeedWidget(),
 | 
			
		||||
            'upstream_speed': SelectSpeedWidget(),
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
        self.fields['cloud'].widget.add_query_param('provider_id', self.instance.circuit.provider_id)
 | 
			
		||||
 
 | 
			
		||||
@@ -37,4 +37,14 @@ class Migration(migrations.Migration):
 | 
			
		||||
            name='cloud',
 | 
			
		||||
            unique_together={('provider', 'name')},
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='circuittermination',
 | 
			
		||||
            name='cloud',
 | 
			
		||||
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuit_terminations', to='circuits.cloud'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='circuittermination',
 | 
			
		||||
            name='site',
 | 
			
		||||
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuit_terminations', to='dcim.site'),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
from django.core.exceptions import ValidationError
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
@@ -300,7 +301,16 @@ class CircuitTermination(ChangeLoggedModel, PathEndpoint, CableTermination):
 | 
			
		||||
    site = models.ForeignKey(
 | 
			
		||||
        to='dcim.Site',
 | 
			
		||||
        on_delete=models.PROTECT,
 | 
			
		||||
        related_name='circuit_terminations'
 | 
			
		||||
        related_name='circuit_terminations',
 | 
			
		||||
        blank=True,
 | 
			
		||||
        null=True
 | 
			
		||||
    )
 | 
			
		||||
    cloud = models.ForeignKey(
 | 
			
		||||
        to=Cloud,
 | 
			
		||||
        on_delete=models.PROTECT,
 | 
			
		||||
        related_name='circuit_terminations',
 | 
			
		||||
        blank=True,
 | 
			
		||||
        null=True
 | 
			
		||||
    )
 | 
			
		||||
    port_speed = models.PositiveIntegerField(
 | 
			
		||||
        verbose_name='Port speed (Kbps)',
 | 
			
		||||
@@ -335,7 +345,16 @@ class CircuitTermination(ChangeLoggedModel, PathEndpoint, CableTermination):
 | 
			
		||||
        unique_together = ['circuit', 'term_side']
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return 'Side {}'.format(self.get_term_side_display())
 | 
			
		||||
        return f"Side {self.get_term_side_display()}"
 | 
			
		||||
 | 
			
		||||
    def clean(self):
 | 
			
		||||
        super().clean()
 | 
			
		||||
 | 
			
		||||
        # Must define either site *or* cloud
 | 
			
		||||
        if self.site is None and self.cloud is None:
 | 
			
		||||
            raise ValidationError("A circuit termination must attach to either a site or a cloud.")
 | 
			
		||||
        if self.site and self.cloud:
 | 
			
		||||
            raise ValidationError("A circuit termination cannot attach to both a site and a cloud.")
 | 
			
		||||
 | 
			
		||||
    def to_objectchange(self, action):
 | 
			
		||||
        # Annotate the parent Circuit
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
 | 
			
		||||
{% block form %}
 | 
			
		||||
  <div class="panel panel-default">
 | 
			
		||||
      <div class="panel-heading"><strong>Location</strong></div>
 | 
			
		||||
      <div class="panel-heading"><strong>Termination</strong></div>
 | 
			
		||||
      <div class="panel-body">
 | 
			
		||||
          <div class="form-group">
 | 
			
		||||
              <label class="col-md-3 control-label">Provider</label>
 | 
			
		||||
@@ -26,9 +26,22 @@
 | 
			
		||||
                  <p class="form-control-static">{{ form.term_side.value }}</p>
 | 
			
		||||
              </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          {% render_field form.region %}
 | 
			
		||||
          {% render_field form.site_group %}
 | 
			
		||||
          {% render_field form.site %}
 | 
			
		||||
          {% with cloud_tab_active=form.initial.cloud %}
 | 
			
		||||
              <ul class="nav nav-tabs" role="tablist">
 | 
			
		||||
                  <li role="presentation"{% if not cloud_tab_active %} class="active"{% endif %}><a href="#site" role="tab" data-toggle="tab">Site</a></li>
 | 
			
		||||
                  <li role="presentation"{% if cloud_tab_active %} class="active"{% endif %}><a href="#cloud" role="tab" data-toggle="tab">Cloud</a></li>
 | 
			
		||||
              </ul>
 | 
			
		||||
              <div class="tab-content">
 | 
			
		||||
                  <div class="tab-pane{% if not cloud_tab_active %} active{% endif %}" id="site">
 | 
			
		||||
                      {% render_field form.region %}
 | 
			
		||||
                      {% render_field form.site_group %}
 | 
			
		||||
                      {% render_field form.site %}
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div class="tab-pane{% if cloud_tab_active %} active{% endif %}" id="cloud">
 | 
			
		||||
                      {% render_field form.cloud %}
 | 
			
		||||
                  </div>
 | 
			
		||||
              </div>
 | 
			
		||||
          {% endwith %}
 | 
			
		||||
          {% render_field form.mark_connected %}
 | 
			
		||||
      </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,12 @@
 | 
			
		||||
                <strong>Cloud</strong>
 | 
			
		||||
            </div>
 | 
			
		||||
            <table class="table table-hover panel-body attr-table">
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>Provider</td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <a href="{{ object.provider.get_absolute_url }}">{{ object.provider }}</a>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>Name</td>
 | 
			
		||||
                    <td>{{ object.name }}</td>
 | 
			
		||||
 
 | 
			
		||||
@@ -26,62 +26,71 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    {% if termination %}
 | 
			
		||||
        <table class="table table-hover panel-body attr-table">
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td>Site</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                    {% if termination.site.region %}
 | 
			
		||||
                        <a href="{{ termination.site.region.get_absolute_url }}">{{ termination.site.region }}</a> /
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                    <a href="{{ termination.site.get_absolute_url }}">{{ termination.site }}</a>
 | 
			
		||||
                </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td>Termination</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                    {% if termination.mark_connected %}
 | 
			
		||||
                        <span class="text-success"><i class="mdi mdi-check-bold"></i></span>
 | 
			
		||||
                        <span class="text-muted">Marked as connected</span>
 | 
			
		||||
                    {% elif termination.cable %}
 | 
			
		||||
                        {% if perms.dcim.delete_cable %}
 | 
			
		||||
                            <div class="pull-right">
 | 
			
		||||
                                <a href="{% url 'dcim:cable_delete' pk=termination.cable.pk %}?return_url={{ termination.circuit.get_absolute_url }}" title="Remove cable" class="btn btn-danger btn-xs">
 | 
			
		||||
                                    <i class="mdi mdi-ethernet-cable-off" aria-hidden="true"></i> Disconnect
 | 
			
		||||
                                </a>
 | 
			
		||||
                            </div>
 | 
			
		||||
            {% if termination.site %}
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>Site</td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        {% if termination.site.region %}
 | 
			
		||||
                            <a href="{{ termination.site.region.get_absolute_url }}">{{ termination.site.region }}</a> /
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                        <a href="{{ termination.cable.get_absolute_url }}">{{ termination.cable }}</a>
 | 
			
		||||
                        <a href="{% url 'circuits:circuittermination_trace' pk=termination.pk %}" class="btn btn-primary btn-xs" title="Trace">
 | 
			
		||||
                            <i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
 | 
			
		||||
                        </a>
 | 
			
		||||
                        {% with peer=termination.get_cable_peer %}
 | 
			
		||||
                            to
 | 
			
		||||
                            {% if peer.device %}
 | 
			
		||||
                                <a href="{{ peer.device.get_absolute_url }}">{{ peer.device }}</a>
 | 
			
		||||
                            {% elif peer.circuit %}
 | 
			
		||||
                                <a href="{{ peer.circuit.get_absolute_url }}">{{ peer.circuit }}</a>
 | 
			
		||||
                        <a href="{{ termination.site.get_absolute_url }}">{{ termination.site }}</a>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>Termination</td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        {% if termination.mark_connected %}
 | 
			
		||||
                            <span class="text-success"><i class="mdi mdi-check-bold"></i></span>
 | 
			
		||||
                            <span class="text-muted">Marked as connected</span>
 | 
			
		||||
                        {% elif termination.cable %}
 | 
			
		||||
                            {% if perms.dcim.delete_cable %}
 | 
			
		||||
                                <div class="pull-right">
 | 
			
		||||
                                    <a href="{% url 'dcim:cable_delete' pk=termination.cable.pk %}?return_url={{ termination.circuit.get_absolute_url }}" title="Remove cable" class="btn btn-danger btn-xs">
 | 
			
		||||
                                        <i class="mdi mdi-ethernet-cable-off" aria-hidden="true"></i> Disconnect
 | 
			
		||||
                                    </a>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                            ({{ peer }})
 | 
			
		||||
                        {% endwith %}
 | 
			
		||||
                    {% else %}
 | 
			
		||||
                        {% if perms.dcim.add_cable %}
 | 
			
		||||
                            <div class="pull-right">
 | 
			
		||||
                                <span class="dropdown">
 | 
			
		||||
                                    <button type="button" class="btn btn-success btn-xs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
 | 
			
		||||
                                        <span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect
 | 
			
		||||
                                    </button>
 | 
			
		||||
                                    <ul class="dropdown-menu dropdown-menu-right">
 | 
			
		||||
                                        <li><a href="{% url 'circuits:circuittermination_connect' termination_a_id=termination.pk termination_b_type='interface' %}?termination_b_site={{ termination.site.pk }}&return_url={{ object.get_absolute_url }}">Interface</a></li>
 | 
			
		||||
                                        <li><a href="{% url 'circuits:circuittermination_connect' termination_a_id=termination.pk termination_b_type='front-port' %}?termination_b_site={{ termination.site.pk }}&return_url={{ object.get_absolute_url }}">Front Port</a></li>
 | 
			
		||||
                                        <li><a href="{% url 'circuits:circuittermination_connect' termination_a_id=termination.pk termination_b_type='rear-port' %}?termination_b_site={{ termination.site.pk }}&return_url={{ object.get_absolute_url }}">Rear Port</a></li>
 | 
			
		||||
                                        <li><a href="{% url 'circuits:circuittermination_connect' termination_a_id=termination.pk termination_b_type='circuit-termination' %}?termination_b_site={{ termination.site.pk }}&return_url={{ object.get_absolute_url }}">Circuit Termination</a></li>
 | 
			
		||||
                                    </ul>
 | 
			
		||||
                                </span>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <a href="{{ termination.cable.get_absolute_url }}">{{ termination.cable }}</a>
 | 
			
		||||
                            <a href="{% url 'circuits:circuittermination_trace' pk=termination.pk %}" class="btn btn-primary btn-xs" title="Trace">
 | 
			
		||||
                                <i class="mdi mdi-transit-connection-variant" aria-hidden="true"></i>
 | 
			
		||||
                            </a>
 | 
			
		||||
                            {% with peer=termination.get_cable_peer %}
 | 
			
		||||
                                to
 | 
			
		||||
                                {% if peer.device %}
 | 
			
		||||
                                    <a href="{{ peer.device.get_absolute_url }}">{{ peer.device }}</a>
 | 
			
		||||
                                {% elif peer.circuit %}
 | 
			
		||||
                                    <a href="{{ peer.circuit.get_absolute_url }}">{{ peer.circuit }}</a>
 | 
			
		||||
                                {% endif %}
 | 
			
		||||
                                ({{ peer }})
 | 
			
		||||
                            {% endwith %}
 | 
			
		||||
                        {% else %}
 | 
			
		||||
                            {% if perms.dcim.add_cable %}
 | 
			
		||||
                                <div class="pull-right">
 | 
			
		||||
                                    <span class="dropdown">
 | 
			
		||||
                                        <button type="button" class="btn btn-success btn-xs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
 | 
			
		||||
                                            <span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect
 | 
			
		||||
                                        </button>
 | 
			
		||||
                                        <ul class="dropdown-menu dropdown-menu-right">
 | 
			
		||||
                                            <li><a href="{% url 'circuits:circuittermination_connect' termination_a_id=termination.pk termination_b_type='interface' %}?termination_b_site={{ termination.site.pk }}&return_url={{ object.get_absolute_url }}">Interface</a></li>
 | 
			
		||||
                                            <li><a href="{% url 'circuits:circuittermination_connect' termination_a_id=termination.pk termination_b_type='front-port' %}?termination_b_site={{ termination.site.pk }}&return_url={{ object.get_absolute_url }}">Front Port</a></li>
 | 
			
		||||
                                            <li><a href="{% url 'circuits:circuittermination_connect' termination_a_id=termination.pk termination_b_type='rear-port' %}?termination_b_site={{ termination.site.pk }}&return_url={{ object.get_absolute_url }}">Rear Port</a></li>
 | 
			
		||||
                                            <li><a href="{% url 'circuits:circuittermination_connect' termination_a_id=termination.pk termination_b_type='circuit-termination' %}?termination_b_site={{ termination.site.pk }}&return_url={{ object.get_absolute_url }}">Circuit Termination</a></li>
 | 
			
		||||
                                        </ul>
 | 
			
		||||
                                    </span>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                            <span class="text-muted">Not defined</span>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                        <span class="text-muted">Not defined</span>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
            {% else %}
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>Cloud</td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <a href="{{ termination.cloud.get_absolute_url }}">{{ termination.cloud }}</a>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td>Speed</td>
 | 
			
		||||
                <td>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user