9604 scope->termination

This commit is contained in:
Arthur Hanson 2024-10-30 09:10:57 -07:00
parent 5b7fda6075
commit d6adebbe6b
19 changed files with 176 additions and 176 deletions

View File

@ -21,7 +21,7 @@ Designates the termination as forming either the A or Z end of the circuit.
If selected, the circuit termination will be considered "connected" even if no cable has been connected to it in NetBox. If selected, the circuit termination will be considered "connected" even if no cable has been connected to it in NetBox.
### Scope ### Termination
The [region](../dcim/region.md), [site group](../dcim/sitegroup.md), [site](../dcim/site.md) or [location](../dcim/location.md) with which this circuit termination is associated. Once created, a cable can be connected between the circuit termination and a device interface (or similar component). The [region](../dcim/region.md), [site group](../dcim/sitegroup.md), [site](../dcim/site.md) or [location](../dcim/location.md) with which this circuit termination is associated. Once created, a cable can be connected between the circuit termination and a device interface (or similar component).

View File

@ -3,7 +3,7 @@ from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers from rest_framework import serializers
from circuits.choices import CircuitPriorityChoices, CircuitStatusChoices from circuits.choices import CircuitPriorityChoices, CircuitStatusChoices
from circuits.constants import CIRCUIT_TERMINATION_SCOPE_TYPES from circuits.constants import CIRCUIT_TERMINATION_TERMINATION_TYPES
from circuits.models import Circuit, CircuitGroup, CircuitGroupAssignment, CircuitTermination, CircuitType from circuits.models import Circuit, CircuitGroup, CircuitGroupAssignment, CircuitTermination, CircuitType
from dcim.api.serializers_.cables import CabledObjectSerializer from dcim.api.serializers_.cables import CabledObjectSerializer
from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField
@ -38,32 +38,32 @@ class CircuitTypeSerializer(NetBoxModelSerializer):
class CircuitCircuitTerminationSerializer(WritableNestedSerializer): class CircuitCircuitTerminationSerializer(WritableNestedSerializer):
scope_type = ContentTypeField( termination_type = ContentTypeField(
queryset=ContentType.objects.filter( queryset=ContentType.objects.filter(
model__in=CIRCUIT_TERMINATION_SCOPE_TYPES model__in=CIRCUIT_TERMINATION_TERMINATION_TYPES
), ),
allow_null=True, allow_null=True,
required=False, required=False,
default=None default=None
) )
scope_id = serializers.IntegerField(allow_null=True, required=False, default=None) termination_id = serializers.IntegerField(allow_null=True, required=False, default=None)
scope = serializers.SerializerMethodField(read_only=True) termination = serializers.SerializerMethodField(read_only=True)
provider_network = ProviderNetworkSerializer(nested=True, allow_null=True) provider_network = ProviderNetworkSerializer(nested=True, allow_null=True)
class Meta: class Meta:
model = CircuitTermination model = CircuitTermination
fields = [ fields = [
'id', 'url', 'display_url', 'display', 'scope_type', 'scope_id', 'scope', 'provider_network', 'port_speed', 'upstream_speed', 'id', 'url', 'display_url', 'display', 'termination_type', 'termination_id', 'termination', 'provider_network', 'port_speed', 'upstream_speed',
'xconnect_id', 'description', 'xconnect_id', 'description',
] ]
@extend_schema_field(serializers.JSONField(allow_null=True)) @extend_schema_field(serializers.JSONField(allow_null=True))
def get_scope(self, obj): def get_termination(self, obj):
if obj.scope_id is None: if obj.termination_id is None:
return None return None
serializer = get_serializer_for_model(obj.scope) serializer = get_serializer_for_model(obj.termination)
context = {'request': self.context['request']} context = {'request': self.context['request']}
return serializer(obj.scope, nested=True, context=context).data return serializer(obj.termination, nested=True, context=context).data
class CircuitGroupSerializer(NetBoxModelSerializer): class CircuitGroupSerializer(NetBoxModelSerializer):
@ -117,34 +117,34 @@ class CircuitSerializer(NetBoxModelSerializer):
class CircuitTerminationSerializer(NetBoxModelSerializer, CabledObjectSerializer): class CircuitTerminationSerializer(NetBoxModelSerializer, CabledObjectSerializer):
circuit = CircuitSerializer(nested=True) circuit = CircuitSerializer(nested=True)
scope_type = ContentTypeField( termination_type = ContentTypeField(
queryset=ContentType.objects.filter( queryset=ContentType.objects.filter(
model__in=CIRCUIT_TERMINATION_SCOPE_TYPES model__in=CIRCUIT_TERMINATION_TERMINATION_TYPES
), ),
allow_null=True, allow_null=True,
required=False, required=False,
default=None default=None
) )
scope_id = serializers.IntegerField(allow_null=True, required=False, default=None) termination_id = serializers.IntegerField(allow_null=True, required=False, default=None)
scope = serializers.SerializerMethodField(read_only=True) termination = serializers.SerializerMethodField(read_only=True)
provider_network = ProviderNetworkSerializer(nested=True, required=False, allow_null=True) provider_network = ProviderNetworkSerializer(nested=True, required=False, allow_null=True)
class Meta: class Meta:
model = CircuitTermination model = CircuitTermination
fields = [ fields = [
'id', 'url', 'display_url', 'display', 'circuit', 'term_side', 'scope_type', 'scope_id', 'scope', 'provider_network', 'port_speed', 'id', 'url', 'display_url', 'display', 'circuit', 'term_side', 'termination_type', 'termination_id', 'termination', 'provider_network', 'port_speed',
'upstream_speed', 'xconnect_id', 'pp_info', 'description', 'mark_connected', 'cable', 'cable_end', 'upstream_speed', 'xconnect_id', 'pp_info', 'description', 'mark_connected', 'cable', 'cable_end',
'link_peers', 'link_peers_type', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied', 'link_peers', 'link_peers_type', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
] ]
brief_fields = ('id', 'url', 'display', 'circuit', 'term_side', 'description', 'cable', '_occupied') brief_fields = ('id', 'url', 'display', 'circuit', 'term_side', 'description', 'cable', '_occupied')
@extend_schema_field(serializers.JSONField(allow_null=True)) @extend_schema_field(serializers.JSONField(allow_null=True))
def get_scope(self, obj): def get_termination(self, obj):
if obj.scope_id is None: if obj.termination_id is None:
return None return None
serializer = get_serializer_for_model(obj.scope) serializer = get_serializer_for_model(obj.termination)
context = {'request': self.context['request']} context = {'request': self.context['request']}
return serializer(obj.scope, nested=True, context=context).data return serializer(obj.termination, nested=True, context=context).data
class CircuitGroupAssignmentSerializer(CircuitGroupAssignmentSerializer_): class CircuitGroupAssignmentSerializer(CircuitGroupAssignmentSerializer_):

View File

@ -1,4 +1,4 @@
# models values for ContentTypes which may be CircuitTermination scope types # models values for ContentTypes which may be CircuitTermination termination types
CIRCUIT_TERMINATION_SCOPE_TYPES = ( CIRCUIT_TERMINATION_TERMINATION_TYPES = (
'region', 'sitegroup', 'site', 'location', 'providernetwork', 'region', 'sitegroup', 'site', 'location', 'providernetwork',
) )

View File

@ -263,7 +263,7 @@ class CircuitTerminationFilterSet(NetBoxModelFilterSet, CabledObjectFilterSet):
queryset=Circuit.objects.all(), queryset=Circuit.objects.all(),
label=_('Circuit'), label=_('Circuit'),
) )
scope_type = ContentTypeFilter() termination_type = ContentTypeFilter()
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='_region', field_name='_region',
@ -334,7 +334,7 @@ class CircuitTerminationFilterSet(NetBoxModelFilterSet, CabledObjectFilterSet):
class Meta: class Meta:
model = CircuitTermination model = CircuitTermination
fields = ( fields = (
'id', 'scope_id', 'term_side', 'port_speed', 'upstream_speed', 'xconnect_id', 'description', 'mark_connected', 'id', 'termination_id', 'term_side', 'port_speed', 'upstream_speed', 'xconnect_id', 'description', 'mark_connected',
'pp_info', 'cable_end', 'pp_info', 'cable_end',
) )

View File

@ -3,7 +3,7 @@ from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from circuits.choices import CircuitCommitRateChoices, CircuitPriorityChoices, CircuitStatusChoices from circuits.choices import CircuitCommitRateChoices, CircuitPriorityChoices, CircuitStatusChoices
from circuits.constants import CIRCUIT_TERMINATION_SCOPE_TYPES from circuits.constants import CIRCUIT_TERMINATION_TERMINATION_TYPES
from circuits.models import * from circuits.models import *
from dcim.models import Site from dcim.models import Site
from ipam.models import ASN from ipam.models import ASN
@ -199,14 +199,14 @@ class CircuitTerminationBulkEditForm(NetBoxModelBulkEditForm):
max_length=200, max_length=200,
required=False required=False
) )
scope_type = ContentTypeChoiceField( termination_type = ContentTypeChoiceField(
queryset=ContentType.objects.filter(model__in=CIRCUIT_TERMINATION_SCOPE_TYPES), queryset=ContentType.objects.filter(model__in=CIRCUIT_TERMINATION_TERMINATION_TYPES),
widget=HTMXSelect(method='post', attrs={'hx-select': '#form_fields'}), widget=HTMXSelect(method='post', attrs={'hx-select': '#form_fields'}),
required=False, required=False,
label=_('Scope type') label=_('Termination type')
) )
scope = DynamicModelChoiceField( termination = DynamicModelChoiceField(
label=_('Scope'), label=_('Termination'),
queryset=Site.objects.none(), # Initial queryset queryset=Site.objects.none(), # Initial queryset
required=False, required=False,
disabled=True, disabled=True,
@ -230,24 +230,24 @@ class CircuitTerminationBulkEditForm(NetBoxModelBulkEditForm):
fieldsets = ( fieldsets = (
FieldSet( FieldSet(
'description', 'description',
'scope_type', 'scope', 'termination_type', 'termination',
'mark_connected', name=_('Circuit Termination') 'mark_connected', name=_('Circuit Termination')
), ),
FieldSet('port_speed', 'upstream_speed', name=_('Termination Details')), FieldSet('port_speed', 'upstream_speed', name=_('Termination Details')),
) )
nullable_fields = ('description', 'scope') nullable_fields = ('description', 'termination')
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if scope_type_id := get_field_value(self, 'scope_type'): if termination_type_id := get_field_value(self, 'termination_type'):
try: try:
scope_type = ContentType.objects.get(pk=scope_type_id) termination_type = ContentType.objects.get(pk=termination_type_id)
model = scope_type.model_class() model = termination_type.model_class()
self.fields['scope'].queryset = model.objects.all() self.fields['termination'].queryset = model.objects.all()
self.fields['scope'].widget.attrs['selector'] = model._meta.label_lower self.fields['termination'].widget.attrs['selector'] = model._meta.label_lower
self.fields['scope'].disabled = False self.fields['termination'].disabled = False
self.fields['scope'].label = _(bettertitle(model._meta.verbose_name)) self.fields['termination'].label = _(bettertitle(model._meta.verbose_name))
except ObjectDoesNotExist: except ObjectDoesNotExist:
pass pass

View File

@ -128,10 +128,10 @@ class BaseCircuitTerminationImportForm(forms.ModelForm):
label=_('Termination'), label=_('Termination'),
choices=CircuitTerminationSideChoices, choices=CircuitTerminationSideChoices,
) )
scope_type = CSVContentTypeField( termination_type = CSVContentTypeField(
queryset=ContentType.objects.filter(model__in=CIRCUIT_TERMINATION_SCOPE_TYPES), queryset=ContentType.objects.filter(model__in=CIRCUIT_TERMINATION_TERMINATION_TYPES),
required=False, required=False,
label=_('Scope type (app & model)') label=_('Termination type (app & model)')
) )
@ -139,11 +139,11 @@ class CircuitTerminationImportRelatedForm(BaseCircuitTerminationImportForm):
class Meta: class Meta:
model = CircuitTermination model = CircuitTermination
fields = [ fields = [
'circuit', 'term_side', 'scope_type', 'scope_id', 'port_speed', 'upstream_speed', 'xconnect_id', 'circuit', 'term_side', 'termination_type', 'termination_id', 'port_speed', 'upstream_speed', 'xconnect_id',
'pp_info', 'description' 'pp_info', 'description'
] ]
labels = { labels = {
'scope_id': 'Scope ID', 'termination_id': 'Termination ID',
} }
@ -152,11 +152,11 @@ class CircuitTerminationImportForm(NetBoxModelImportForm, BaseCircuitTermination
class Meta: class Meta:
model = CircuitTermination model = CircuitTermination
fields = [ fields = [
'circuit', 'term_side', 'scope_type', 'scope_id', 'port_speed', 'upstream_speed', 'xconnect_id', 'circuit', 'term_side', 'termination_type', 'termination_id', 'port_speed', 'upstream_speed', 'xconnect_id',
'pp_info', 'description', 'tags' 'pp_info', 'description', 'tags'
] ]
labels = { labels = {
'scope_id': 'Scope ID', 'termination_id': 'Termination ID',
} }

View File

@ -208,7 +208,7 @@ class CircuitTerminationFilterForm(NetBoxModelFilterSetForm):
FieldSet('q', 'filter_id', 'tag'), FieldSet('q', 'filter_id', 'tag'),
FieldSet('circuit_id', 'term_side', name=_('Circuit')), FieldSet('circuit_id', 'term_side', name=_('Circuit')),
FieldSet('provider_id', name=_('Provider')), FieldSet('provider_id', name=_('Provider')),
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', name=_('Scope')), FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', name=_('Termination')),
) )
region_id = DynamicModelMultipleChoiceField( region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),

View File

@ -149,14 +149,14 @@ class CircuitTerminationForm(NetBoxModelForm):
queryset=Circuit.objects.all(), queryset=Circuit.objects.all(),
selector=True selector=True
) )
scope_type = ContentTypeChoiceField( termination_type = ContentTypeChoiceField(
queryset=ContentType.objects.filter(model__in=CIRCUIT_TERMINATION_SCOPE_TYPES), queryset=ContentType.objects.filter(model__in=CIRCUIT_TERMINATION_TERMINATION_TYPES),
widget=HTMXSelect(), widget=HTMXSelect(),
required=False, required=False,
label=_('Scope type') label=_('Termination type')
) )
scope = DynamicModelChoiceField( termination = DynamicModelChoiceField(
label=_('Scope'), label=_('Termination'),
queryset=Site.objects.none(), # Initial queryset queryset=Site.objects.none(), # Initial queryset
required=False, required=False,
disabled=True, disabled=True,
@ -166,7 +166,7 @@ class CircuitTerminationForm(NetBoxModelForm):
fieldsets = ( fieldsets = (
FieldSet( FieldSet(
'circuit', 'term_side', 'description', 'tags', 'circuit', 'term_side', 'description', 'tags',
'scope_type', 'scope', 'termination_type', 'termination',
'mark_connected', name=_('Circuit Termination') 'mark_connected', name=_('Circuit Termination')
), ),
FieldSet('port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', name=_('Termination Details')), FieldSet('port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', name=_('Termination Details')),
@ -175,7 +175,7 @@ class CircuitTerminationForm(NetBoxModelForm):
class Meta: class Meta:
model = CircuitTermination model = CircuitTermination
fields = [ fields = [
'circuit', 'term_side', 'scope_type', 'mark_connected', 'port_speed', 'upstream_speed', 'circuit', 'term_side', 'termination_type', 'mark_connected', 'port_speed', 'upstream_speed',
'xconnect_id', 'pp_info', 'description', 'tags', 'xconnect_id', 'pp_info', 'description', 'tags',
] ]
widgets = { widgets = {
@ -191,31 +191,31 @@ class CircuitTerminationForm(NetBoxModelForm):
instance = kwargs.get('instance') instance = kwargs.get('instance')
initial = kwargs.get('initial', {}) initial = kwargs.get('initial', {})
if instance is not None and instance.scope: if instance is not None and instance.termination:
initial['scope'] = instance.scope initial['termination'] = instance.termination
kwargs['initial'] = initial kwargs['initial'] = initial
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if scope_type_id := get_field_value(self, 'scope_type'): if termination_type_id := get_field_value(self, 'termination_type'):
try: try:
scope_type = ContentType.objects.get(pk=scope_type_id) termination_type = ContentType.objects.get(pk=termination_type_id)
model = scope_type.model_class() model = termination_type.model_class()
self.fields['scope'].queryset = model.objects.all() self.fields['termination'].queryset = model.objects.all()
self.fields['scope'].widget.attrs['selector'] = model._meta.label_lower self.fields['termination'].widget.attrs['selector'] = model._meta.label_lower
self.fields['scope'].disabled = False self.fields['termination'].disabled = False
self.fields['scope'].label = _(bettertitle(model._meta.verbose_name)) self.fields['termination'].label = _(bettertitle(model._meta.verbose_name))
except ObjectDoesNotExist: except ObjectDoesNotExist:
pass pass
if self.instance and scope_type_id != self.instance.scope_type_id: if self.instance and termination_type_id != self.instance.termination_type_id:
self.initial['scope'] = None self.initial['termination'] = None
def clean(self): def clean(self):
super().clean() super().clean()
# Assign the selected scope (if any) # Assign the selected termination (if any)
self.instance.scope = self.cleaned_data.get('scope') self.instance.termination = self.cleaned_data.get('termination')
class CircuitGroupForm(TenancyForm, NetBoxModelForm): class CircuitGroupForm(TenancyForm, NetBoxModelForm):

View File

@ -59,21 +59,21 @@ class ProviderNetworkType(NetBoxObjectType):
@strawberry_django.type( @strawberry_django.type(
models.CircuitTermination, models.CircuitTermination,
exclude=('scope_type', 'scope_id', '_location', '_region', '_site', '_sitegroup', '_provider_network'), exclude=('termination_type', 'termination_id', '_location', '_region', '_site', '_sitegroup', '_provider_network'),
filters=CircuitTerminationFilter filters=CircuitTerminationFilter
) )
class CircuitTerminationType(CustomFieldsMixin, TagsMixin, CabledObjectMixin, ObjectType): class CircuitTerminationType(CustomFieldsMixin, TagsMixin, CabledObjectMixin, ObjectType):
circuit: Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')] circuit: Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]
@strawberry_django.field @strawberry_django.field
def scope(self) -> Annotated[Union[ def termination(self) -> Annotated[Union[
Annotated["LocationType", strawberry.lazy('dcim.graphql.types')], Annotated["LocationType", strawberry.lazy('dcim.graphql.types')],
Annotated["RegionType", strawberry.lazy('dcim.graphql.types')], Annotated["RegionType", strawberry.lazy('dcim.graphql.types')],
Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')], Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')],
Annotated["SiteType", strawberry.lazy('dcim.graphql.types')], Annotated["SiteType", strawberry.lazy('dcim.graphql.types')],
Annotated["ProviderNetworkType", strawberry.lazy('circuits.graphql.types')], Annotated["ProviderNetworkType", strawberry.lazy('circuits.graphql.types')],
], strawberry.union("CircuitTerminationScopeType")] | None: ], strawberry.union("CircuitTerminationTerminationType")] | None:
return self.scope return self.termination
@strawberry_django.type( @strawberry_django.type(

View File

@ -4,21 +4,21 @@ from django.db import migrations, models
def copy_site_assignments(apps, schema_editor): def copy_site_assignments(apps, schema_editor):
""" """
Copy site ForeignKey values to the scope GFK. Copy site ForeignKey values to the Termination GFK.
""" """
ContentType = apps.get_model('contenttypes', 'ContentType') ContentType = apps.get_model('contenttypes', 'ContentType')
CircuitTermination = apps.get_model('circuits', 'CircuitTermination') CircuitTermination = apps.get_model('circuits', 'CircuitTermination')
Site = apps.get_model('dcim', 'Site') Site = apps.get_model('dcim', 'Site')
CircuitTermination.objects.filter(site__isnull=False).update( CircuitTermination.objects.filter(site__isnull=False).update(
scope_type=ContentType.objects.get_for_model(Site), termination_type=ContentType.objects.get_for_model(Site),
scope_id=models.F('site_id') termination_id=models.F('site_id')
) )
ProviderNetwork = apps.get_model('circuits', 'ProviderNetwork') ProviderNetwork = apps.get_model('circuits', 'ProviderNetwork')
CircuitTermination.objects.filter(provider_network__isnull=False).update( CircuitTermination.objects.filter(provider_network__isnull=False).update(
scope_type=ContentType.objects.get_for_model(ProviderNetwork), termination_type=ContentType.objects.get_for_model(ProviderNetwork),
scope_id=models.F('provider_network_id') termination_id=models.F('provider_network_id')
) )
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -32,12 +32,12 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='circuittermination', model_name='circuittermination',
name='scope_id', name='termination_id',
field=models.PositiveBigIntegerField(blank=True, null=True), field=models.PositiveBigIntegerField(blank=True, null=True),
), ),
migrations.AddField( migrations.AddField(
model_name='circuittermination', model_name='circuittermination',
name='scope_type', name='termination_type',
field=models.ForeignKey( field=models.ForeignKey(
blank=True, blank=True,
limit_choices_to=models.Q(('model__in', ('region', 'sitegroup', 'site', 'location', 'providernetwork'))), limit_choices_to=models.Q(('model__in', ('region', 'sitegroup', 'site', 'location', 'providernetwork'))),

View File

@ -5,7 +5,7 @@ from django.db import migrations, models
def populate_denormalized_fields(apps, schema_editor): def populate_denormalized_fields(apps, schema_editor):
""" """
Copy site ForeignKey values to the scope GFK. Copy site ForeignKey values to the Termination GFK.
""" """
CircuitTermination = apps.get_model('circuits', 'CircuitTermination') CircuitTermination = apps.get_model('circuits', 'CircuitTermination')
@ -22,7 +22,7 @@ def populate_denormalized_fields(apps, schema_editor):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('circuits', '0047_circuittermination__scope'), ('circuits', '0047_circuittermination__termination'),
] ]
operations = [ operations = [
@ -33,7 +33,7 @@ class Migration(migrations.Migration):
blank=True, blank=True,
null=True, null=True,
on_delete=django.db.models.deletion.CASCADE, on_delete=django.db.models.deletion.CASCADE,
related_name='_circuit_terminations', related_name='circuit_terminations',
to='dcim.location', to='dcim.location',
), ),
), ),
@ -44,7 +44,7 @@ class Migration(migrations.Migration):
blank=True, blank=True,
null=True, null=True,
on_delete=django.db.models.deletion.CASCADE, on_delete=django.db.models.deletion.CASCADE,
related_name='_circuit_terminations', related_name='circuit_terminations',
to='dcim.region', to='dcim.region',
), ),
), ),
@ -55,7 +55,7 @@ class Migration(migrations.Migration):
blank=True, blank=True,
null=True, null=True,
on_delete=django.db.models.deletion.CASCADE, on_delete=django.db.models.deletion.CASCADE,
related_name='_circuit_terminations', related_name='circuit_terminations',
to='dcim.site', to='dcim.site',
), ),
), ),
@ -66,7 +66,7 @@ class Migration(migrations.Migration):
blank=True, blank=True,
null=True, null=True,
on_delete=django.db.models.deletion.CASCADE, on_delete=django.db.models.deletion.CASCADE,
related_name='_circuit_terminations', related_name='circuit_terminations',
to='dcim.sitegroup', to='dcim.sitegroup',
), ),
), ),

View File

@ -236,21 +236,21 @@ class CircuitTermination(
choices=CircuitTerminationSideChoices, choices=CircuitTerminationSideChoices,
verbose_name=_('termination') verbose_name=_('termination')
) )
scope_type = models.ForeignKey( termination_type = models.ForeignKey(
to='contenttypes.ContentType', to='contenttypes.ContentType',
on_delete=models.PROTECT, on_delete=models.PROTECT,
limit_choices_to=Q(model__in=CIRCUIT_TERMINATION_SCOPE_TYPES), limit_choices_to=Q(model__in=CIRCUIT_TERMINATION_TERMINATION_TYPES),
related_name='+', related_name='+',
blank=True, blank=True,
null=True null=True
) )
scope_id = models.PositiveBigIntegerField( termination_id = models.PositiveBigIntegerField(
blank=True, blank=True,
null=True null=True
) )
scope = GenericForeignKey( termination = GenericForeignKey(
ct_field='scope_type', ct_field='termination_type',
fk_field='scope_id' fk_field='termination_id'
) )
port_speed = models.PositiveIntegerField( port_speed = models.PositiveIntegerField(
verbose_name=_('port speed (Kbps)'), verbose_name=_('port speed (Kbps)'),
@ -293,28 +293,28 @@ class CircuitTermination(
_location = models.ForeignKey( _location = models.ForeignKey(
to='dcim.Location', to='dcim.Location',
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='_circuit_terminations', related_name='circuit_terminations',
blank=True, blank=True,
null=True null=True
) )
_site = models.ForeignKey( _site = models.ForeignKey(
to='dcim.Site', to='dcim.Site',
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='_circuit_terminations', related_name='circuit_terminations',
blank=True, blank=True,
null=True null=True
) )
_region = models.ForeignKey( _region = models.ForeignKey(
to='dcim.Region', to='dcim.Region',
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='_circuit_terminations', related_name='circuit_terminations',
blank=True, blank=True,
null=True null=True
) )
_sitegroup = models.ForeignKey( _sitegroup = models.ForeignKey(
to='dcim.SiteGroup', to='dcim.SiteGroup',
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='_circuit_terminations', related_name='circuit_terminations',
blank=True, blank=True,
null=True null=True
) )
@ -340,8 +340,8 @@ class CircuitTermination(
super().clean() super().clean()
# Must define either site *or* provider network # Must define either site *or* provider network
if self.scope is None: if self.termination is None:
raise ValidationError(_("A circuit termination must attach to either a scope or a provider network.")) raise ValidationError(_("A circuit termination must attach to termination."))
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
# Cache objects associated with the terminating object (for filtering) # Cache objects associated with the terminating object (for filtering)
@ -351,23 +351,23 @@ class CircuitTermination(
def cache_related_objects(self): def cache_related_objects(self):
self._provider_network = self._region = self._sitegroup = self._site = self._location = None self._provider_network = self._region = self._sitegroup = self._site = self._location = None
if self.scope_type: if self.termination_type:
scope_type = self.scope_type.model_class() termination_type = self.termination_type.model_class()
if scope_type == apps.get_model('dcim', 'region'): if termination_type == apps.get_model('dcim', 'region'):
self._region = self.scope self._region = self.termination
elif scope_type == apps.get_model('dcim', 'sitegroup'): elif termination_type == apps.get_model('dcim', 'sitegroup'):
self._sitegroup = self.scope self._sitegroup = self.termination
elif scope_type == apps.get_model('dcim', 'site'): elif termination_type == apps.get_model('dcim', 'site'):
self._region = self.scope.region self._region = self.termination.region
self._sitegroup = self.scope.group self._sitegroup = self.termination.group
self._site = self.scope self._site = self.termination
elif scope_type == apps.get_model('dcim', 'location'): elif termination_type == apps.get_model('dcim', 'location'):
self._region = self.scope.site.region self._region = self.termination.site.region
self._sitegroup = self.scope.site.group self._sitegroup = self.termination.site.group
self._site = self.scope.site self._site = self.termination.site
self._location = self.scope self._location = self.termination
elif scope_type == apps.get_model('circuits', 'providernetwork'): elif termination_type == apps.get_model('circuits', 'providernetwork'):
self._provider_network = self.scope self._provider_network = self.termination
cache_related_objects.alters_data = True cache_related_objects.alters_data = True
def to_objectchange(self, action): def to_objectchange(self, action):
@ -382,7 +382,7 @@ class CircuitTermination(
def get_peer_termination(self): def get_peer_termination(self):
peer_side = 'Z' if self.term_side == 'A' else 'A' peer_side = 'Z' if self.term_side == 'A' else 'A'
try: try:
return CircuitTermination.objects.prefetch_related('scope').get( return CircuitTermination.objects.prefetch_related('termination').get(
circuit=self.circuit, circuit=self.circuit,
term_side=peer_side term_side=peer_side
) )

View File

@ -181,10 +181,10 @@ class CircuitTerminationTest(APIViewTestCases.APIViewTestCase):
Circuit.objects.bulk_create(circuits) Circuit.objects.bulk_create(circuits)
circuit_terminations = ( circuit_terminations = (
CircuitTermination(circuit=circuits[0], term_side=SIDE_A, scope=sites[0]), CircuitTermination(circuit=circuits[0], term_side=SIDE_A, termination=sites[0]),
CircuitTermination(circuit=circuits[0], term_side=SIDE_Z, scope=provider_networks[0]), CircuitTermination(circuit=circuits[0], term_side=SIDE_Z, termination=provider_networks[0]),
CircuitTermination(circuit=circuits[1], term_side=SIDE_A, scope=sites[1]), CircuitTermination(circuit=circuits[1], term_side=SIDE_A, termination=sites[1]),
CircuitTermination(circuit=circuits[1], term_side=SIDE_Z, scope=provider_networks[1]), CircuitTermination(circuit=circuits[1], term_side=SIDE_Z, termination=provider_networks[1]),
) )
CircuitTermination.objects.bulk_create(circuit_terminations) CircuitTermination.objects.bulk_create(circuit_terminations)
@ -192,15 +192,15 @@ class CircuitTerminationTest(APIViewTestCases.APIViewTestCase):
{ {
'circuit': circuits[2].pk, 'circuit': circuits[2].pk,
'term_side': SIDE_A, 'term_side': SIDE_A,
'scope_type': 'dcim.site', 'termination_type': 'dcim.site',
'scope_id': sites[0].pk, 'termination_id': sites[0].pk,
'port_speed': 200000, 'port_speed': 200000,
}, },
{ {
'circuit': circuits[2].pk, 'circuit': circuits[2].pk,
'term_side': SIDE_Z, 'term_side': SIDE_Z,
'scope_type': 'circuits.providernetwork', 'termination_type': 'circuits.providernetwork',
'scope_id': provider_networks[0].pk, 'termination_id': provider_networks[0].pk,
'port_speed': 200000, 'port_speed': 200000,
}, },
] ]

View File

@ -71,8 +71,8 @@ class ProviderTestCase(TestCase, ChangeLoggedFilterSetTests):
Circuit.objects.bulk_create(circuits) Circuit.objects.bulk_create(circuits)
circuit_terminations = ( circuit_terminations = (
CircuitTermination(circuit=circuits[0], scope=sites[0], term_side='A'), CircuitTermination(circuit=circuits[0], termination=sites[0], term_side='A'),
CircuitTermination(circuit=circuits[1], scope=sites[0], term_side='A'), CircuitTermination(circuit=circuits[1], termination=sites[0], term_side='A'),
) )
for ct in circuit_terminations: for ct in circuit_terminations:
ct.save() ct.save()
@ -235,12 +235,12 @@ class CircuitTestCase(TestCase, ChangeLoggedFilterSetTests):
Circuit.objects.bulk_create(circuits) Circuit.objects.bulk_create(circuits)
circuit_terminations = (( circuit_terminations = ((
CircuitTermination(circuit=circuits[0], scope=sites[0], term_side='A'), CircuitTermination(circuit=circuits[0], termination=sites[0], term_side='A'),
CircuitTermination(circuit=circuits[1], scope=sites[1], term_side='A'), CircuitTermination(circuit=circuits[1], termination=sites[1], term_side='A'),
CircuitTermination(circuit=circuits[2], scope=sites[2], term_side='A'), CircuitTermination(circuit=circuits[2], termination=sites[2], term_side='A'),
CircuitTermination(circuit=circuits[3], scope=provider_networks[0], term_side='A'), CircuitTermination(circuit=circuits[3], termination=provider_networks[0], term_side='A'),
CircuitTermination(circuit=circuits[4], scope=provider_networks[1], term_side='A'), CircuitTermination(circuit=circuits[4], termination=provider_networks[1], term_side='A'),
CircuitTermination(circuit=circuits[5], scope=provider_networks[2], term_side='A'), CircuitTermination(circuit=circuits[5], termination=provider_networks[2], term_side='A'),
)) ))
for ct in circuit_terminations: for ct in circuit_terminations:
ct.save() ct.save()
@ -387,16 +387,16 @@ class CircuitTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
Circuit.objects.bulk_create(circuits) Circuit.objects.bulk_create(circuits)
circuit_terminations = (( circuit_terminations = ((
CircuitTermination(circuit=circuits[0], scope=sites[0], term_side='A', port_speed=1000, upstream_speed=1000, xconnect_id='ABC', description='foobar1'), CircuitTermination(circuit=circuits[0], termination=sites[0], term_side='A', port_speed=1000, upstream_speed=1000, xconnect_id='ABC', description='foobar1'),
CircuitTermination(circuit=circuits[0], scope=sites[1], term_side='Z', port_speed=1000, upstream_speed=1000, xconnect_id='DEF', description='foobar2'), CircuitTermination(circuit=circuits[0], termination=sites[1], term_side='Z', port_speed=1000, upstream_speed=1000, xconnect_id='DEF', description='foobar2'),
CircuitTermination(circuit=circuits[1], scope=sites[1], term_side='A', port_speed=2000, upstream_speed=2000, xconnect_id='GHI'), CircuitTermination(circuit=circuits[1], termination=sites[1], term_side='A', port_speed=2000, upstream_speed=2000, xconnect_id='GHI'),
CircuitTermination(circuit=circuits[1], scope=sites[2], term_side='Z', port_speed=2000, upstream_speed=2000, xconnect_id='JKL'), CircuitTermination(circuit=circuits[1], termination=sites[2], term_side='Z', port_speed=2000, upstream_speed=2000, xconnect_id='JKL'),
CircuitTermination(circuit=circuits[2], scope=sites[2], term_side='A', port_speed=3000, upstream_speed=3000, xconnect_id='MNO'), CircuitTermination(circuit=circuits[2], termination=sites[2], term_side='A', port_speed=3000, upstream_speed=3000, xconnect_id='MNO'),
CircuitTermination(circuit=circuits[2], scope=sites[0], term_side='Z', port_speed=3000, upstream_speed=3000, xconnect_id='PQR'), CircuitTermination(circuit=circuits[2], termination=sites[0], term_side='Z', port_speed=3000, upstream_speed=3000, xconnect_id='PQR'),
CircuitTermination(circuit=circuits[3], scope=provider_networks[0], term_side='A'), CircuitTermination(circuit=circuits[3], termination=provider_networks[0], term_side='A'),
CircuitTermination(circuit=circuits[4], scope=provider_networks[1], term_side='A'), CircuitTermination(circuit=circuits[4], termination=provider_networks[1], term_side='A'),
CircuitTermination(circuit=circuits[5], scope=provider_networks[2], term_side='A'), CircuitTermination(circuit=circuits[5], termination=provider_networks[2], term_side='A'),
CircuitTermination(circuit=circuits[6], scope=provider_networks[0], term_side='A', mark_connected=True), CircuitTermination(circuit=circuits[6], termination=provider_networks[0], term_side='A', mark_connected=True),
)) ))
for ct in circuit_terminations: for ct in circuit_terminations:
ct.save() ct.save()

View File

@ -203,23 +203,23 @@ class CircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase):
"terminations": [ "terminations": [
{ {
"term_side": "A", "term_side": "A",
"scope_type": "dcim.site", "termination_type": "dcim.site",
"scope_id": "1" "termination_id": "1"
}, },
{ {
"term_side": "Z", "term_side": "Z",
"scope_type": "dcim.site", "termination_type": "dcim.site",
"scope_id": "1" "termination_id": "1"
} }
] ]
} }
] ]
""" """
# Fix up the scope site id # Fix up the termination site id
site = Site.objects.first() site = Site.objects.first()
data = json.loads(json_data) data = json.loads(json_data)
data[0]["terminations"][0]["scope_id"] = data[0]["terminations"][1]["scope_id"] = site.id data[0]["terminations"][0]["termination_id"] = data[0]["terminations"][1]["termination_id"] = site.id
json_data = json.dumps(data) json_data = json.dumps(data)
initial_count = self._get_queryset().count() initial_count = self._get_queryset().count()
@ -370,10 +370,10 @@ class TestCase(ViewTestCases.PrimaryObjectViewTestCase):
Circuit.objects.bulk_create(circuits) Circuit.objects.bulk_create(circuits)
circuit_terminations = ( circuit_terminations = (
CircuitTermination(circuit=circuits[0], term_side='A', scope=sites[0]), CircuitTermination(circuit=circuits[0], term_side='A', termination=sites[0]),
CircuitTermination(circuit=circuits[0], term_side='Z', scope=sites[1]), CircuitTermination(circuit=circuits[0], term_side='Z', termination=sites[1]),
CircuitTermination(circuit=circuits[1], term_side='A', scope=sites[0]), CircuitTermination(circuit=circuits[1], term_side='A', termination=sites[0]),
CircuitTermination(circuit=circuits[1], term_side='Z', scope=sites[1]), CircuitTermination(circuit=circuits[1], term_side='Z', termination=sites[1]),
) )
for ct in circuit_terminations: for ct in circuit_terminations:
ct.save() ct.save()
@ -381,14 +381,14 @@ class TestCase(ViewTestCases.PrimaryObjectViewTestCase):
cls.form_data = { cls.form_data = {
'circuit': circuits[2].pk, 'circuit': circuits[2].pk,
'term_side': 'A', 'term_side': 'A',
'scope_type': ContentType.objects.get_for_model(Site).pk, 'termination_type': ContentType.objects.get_for_model(Site).pk,
'scope': sites[2].pk, 'termination': sites[2].pk,
'description': 'New description', 'description': 'New description',
} }
site = sites[0].pk site = sites[0].pk
cls.csv_data = ( cls.csv_data = (
"circuit,term_side,scope_type,scope_id,description", "circuit,term_side,termination_type,termination_id,description",
f"Circuit 3,A,dcim.site,{site},Foo", f"Circuit 3,A,dcim.site,{site},Foo",
f"Circuit 3,Z,dcim.site,{site},Bar", f"Circuit 3,Z,dcim.site,{site},Bar",
) )

View File

@ -1167,7 +1167,7 @@ class CablePathTestCase(TestCase):
[IF1] --C1-- [CT1] [IF1] --C1-- [CT1]
""" """
interface1 = Interface.objects.create(device=self.device, name='Interface 1') interface1 = Interface.objects.create(device=self.device, name='Interface 1')
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, scope=self.site, term_side='A') circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='A')
# Create cable 1 # Create cable 1
cable1 = Cable( cable1 = Cable(
@ -1198,7 +1198,7 @@ class CablePathTestCase(TestCase):
""" """
interface1 = Interface.objects.create(device=self.device, name='Interface 1') interface1 = Interface.objects.create(device=self.device, name='Interface 1')
interface2 = Interface.objects.create(device=self.device, name='Interface 2') interface2 = Interface.objects.create(device=self.device, name='Interface 2')
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, scope=self.site, term_side='A') circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='A')
# Create cable 1 # Create cable 1
cable1 = Cable( cable1 = Cable(
@ -1214,7 +1214,7 @@ class CablePathTestCase(TestCase):
) )
# Create CT2 # Create CT2
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, scope=self.site, term_side='Z') circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='Z')
# Check for partial path to site # Check for partial path to site
self.assertPathExists( self.assertPathExists(
@ -1266,7 +1266,7 @@ class CablePathTestCase(TestCase):
interface2 = Interface.objects.create(device=self.device, name='Interface 2') interface2 = Interface.objects.create(device=self.device, name='Interface 2')
interface3 = Interface.objects.create(device=self.device, name='Interface 3') interface3 = Interface.objects.create(device=self.device, name='Interface 3')
interface4 = Interface.objects.create(device=self.device, name='Interface 4') interface4 = Interface.objects.create(device=self.device, name='Interface 4')
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, scope=self.site, term_side='A') circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='A')
# Create cable 1 # Create cable 1
cable1 = Cable( cable1 = Cable(
@ -1282,7 +1282,7 @@ class CablePathTestCase(TestCase):
) )
# Create CT2 # Create CT2
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, scope=self.site, term_side='Z') circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='Z')
# Check for partial path to site # Check for partial path to site
self.assertPathExists( self.assertPathExists(
@ -1335,8 +1335,8 @@ class CablePathTestCase(TestCase):
""" """
interface1 = Interface.objects.create(device=self.device, name='Interface 1') interface1 = Interface.objects.create(device=self.device, name='Interface 1')
site2 = Site.objects.create(name='Site 2', slug='site-2') site2 = Site.objects.create(name='Site 2', slug='site-2')
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, scope=self.site, term_side='A') circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='A')
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, scope=site2, term_side='Z') circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, termination=site2, term_side='Z')
# Create cable 1 # Create cable 1
cable1 = Cable( cable1 = Cable(
@ -1365,8 +1365,8 @@ class CablePathTestCase(TestCase):
""" """
interface1 = Interface.objects.create(device=self.device, name='Interface 1') interface1 = Interface.objects.create(device=self.device, name='Interface 1')
providernetwork = ProviderNetwork.objects.create(name='Provider Network 1', provider=self.circuit.provider) providernetwork = ProviderNetwork.objects.create(name='Provider Network 1', provider=self.circuit.provider)
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, scope=self.site, term_side='A') circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='A')
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, scope=providernetwork, term_side='Z') circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, termination=providernetwork, term_side='Z')
# Create cable 1 # Create cable 1
cable1 = Cable( cable1 = Cable(
@ -1413,8 +1413,8 @@ class CablePathTestCase(TestCase):
frontport2_2 = FrontPort.objects.create( frontport2_2 = FrontPort.objects.create(
device=self.device, name='Front Port 2:2', rear_port=rearport2, rear_port_position=2 device=self.device, name='Front Port 2:2', rear_port=rearport2, rear_port_position=2
) )
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, scope=self.site, term_side='A') circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='A')
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, scope=self.site, term_side='Z') circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='Z')
# Create cables # Create cables
cable1 = Cable( cable1 = Cable(
@ -1499,10 +1499,10 @@ class CablePathTestCase(TestCase):
interface1 = Interface.objects.create(device=self.device, name='Interface 1') interface1 = Interface.objects.create(device=self.device, name='Interface 1')
interface2 = Interface.objects.create(device=self.device, name='Interface 2') interface2 = Interface.objects.create(device=self.device, name='Interface 2')
circuit2 = Circuit.objects.create(provider=self.circuit.provider, type=self.circuit.type, cid='Circuit 2') circuit2 = Circuit.objects.create(provider=self.circuit.provider, type=self.circuit.type, cid='Circuit 2')
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, scope=self.site, term_side='A') circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='A')
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, scope=self.site, term_side='Z') circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='Z')
circuittermination3 = CircuitTermination.objects.create(circuit=circuit2, scope=self.site, term_side='A') circuittermination3 = CircuitTermination.objects.create(circuit=circuit2, termination=self.site, term_side='A')
circuittermination4 = CircuitTermination.objects.create(circuit=circuit2, scope=self.site, term_side='Z') circuittermination4 = CircuitTermination.objects.create(circuit=circuit2, termination=self.site, term_side='Z')
# Create cables # Create cables
cable1 = Cable( cable1 = Cable(

View File

@ -5117,7 +5117,7 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
provider = Provider.objects.create(name='Provider 1', slug='provider-1') provider = Provider.objects.create(name='Provider 1', slug='provider-1')
circuit_type = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1') circuit_type = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
circuit = Circuit.objects.create(cid='Circuit 1', provider=provider, type=circuit_type) circuit = Circuit.objects.create(cid='Circuit 1', provider=provider, type=circuit_type)
circuit_termination = CircuitTermination.objects.create(circuit=circuit, term_side='A', scope=sites[0]) circuit_termination = CircuitTermination.objects.create(circuit=circuit, term_side='A', termination=sites[0])
# Cables # Cables
cables = ( cables = (

View File

@ -762,9 +762,9 @@ class CableTestCase(TestCase):
circuittype = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1') circuittype = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
circuit1 = Circuit.objects.create(provider=provider, type=circuittype, cid='1') circuit1 = Circuit.objects.create(provider=provider, type=circuittype, cid='1')
circuit2 = Circuit.objects.create(provider=provider, type=circuittype, cid='2') circuit2 = Circuit.objects.create(provider=provider, type=circuittype, cid='2')
CircuitTermination.objects.create(circuit=circuit1, scope=site, term_side='A') CircuitTermination.objects.create(circuit=circuit1, termination=site, term_side='A')
CircuitTermination.objects.create(circuit=circuit1, scope=site, term_side='Z') CircuitTermination.objects.create(circuit=circuit1, termination=site, term_side='Z')
CircuitTermination.objects.create(circuit=circuit2, scope=provider_network, term_side='A') CircuitTermination.objects.create(circuit=circuit2, termination=provider_network, term_side='A')
def test_cable_creation(self): def test_cable_creation(self):
""" """

View File

@ -1,11 +1,11 @@
{% load helpers %} {% load helpers %}
{% load i18n %} {% load i18n %}
{% if termination.scope %} {% if termination.termination %}
<tr> <tr>
<th scope="row">{% trans "Scope" %}</th> <th scope="row">{% trans "Termination" %}</th>
{% if termination.scope %} {% if termination.termination %}
<td>{{ termination.scope|linkify }} ({% trans termination.scope_type.name %})</td> <td>{{ termination.termination|linkify }} ({% trans termination.termination_type.name %})</td>
{% else %} {% else %}
<td>{{ ''|placeholder }}</td> <td>{{ ''|placeholder }}</td>
{% endif %} {% endif %}