mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-30 04:16:24 -06:00
9604 scope->termination
This commit is contained in:
parent
5b7fda6075
commit
d6adebbe6b
@ -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.
|
||||
|
||||
### 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).
|
||||
|
||||
|
@ -3,7 +3,7 @@ from drf_spectacular.utils import extend_schema_field
|
||||
from rest_framework import serializers
|
||||
|
||||
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 dcim.api.serializers_.cables import CabledObjectSerializer
|
||||
from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField
|
||||
@ -38,32 +38,32 @@ class CircuitTypeSerializer(NetBoxModelSerializer):
|
||||
|
||||
|
||||
class CircuitCircuitTerminationSerializer(WritableNestedSerializer):
|
||||
scope_type = ContentTypeField(
|
||||
termination_type = ContentTypeField(
|
||||
queryset=ContentType.objects.filter(
|
||||
model__in=CIRCUIT_TERMINATION_SCOPE_TYPES
|
||||
model__in=CIRCUIT_TERMINATION_TERMINATION_TYPES
|
||||
),
|
||||
allow_null=True,
|
||||
required=False,
|
||||
default=None
|
||||
)
|
||||
scope_id = serializers.IntegerField(allow_null=True, required=False, default=None)
|
||||
scope = serializers.SerializerMethodField(read_only=True)
|
||||
termination_id = serializers.IntegerField(allow_null=True, required=False, default=None)
|
||||
termination = serializers.SerializerMethodField(read_only=True)
|
||||
provider_network = ProviderNetworkSerializer(nested=True, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = CircuitTermination
|
||||
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',
|
||||
]
|
||||
|
||||
@extend_schema_field(serializers.JSONField(allow_null=True))
|
||||
def get_scope(self, obj):
|
||||
if obj.scope_id is None:
|
||||
def get_termination(self, obj):
|
||||
if obj.termination_id is None:
|
||||
return None
|
||||
serializer = get_serializer_for_model(obj.scope)
|
||||
serializer = get_serializer_for_model(obj.termination)
|
||||
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):
|
||||
@ -117,34 +117,34 @@ class CircuitSerializer(NetBoxModelSerializer):
|
||||
|
||||
class CircuitTerminationSerializer(NetBoxModelSerializer, CabledObjectSerializer):
|
||||
circuit = CircuitSerializer(nested=True)
|
||||
scope_type = ContentTypeField(
|
||||
termination_type = ContentTypeField(
|
||||
queryset=ContentType.objects.filter(
|
||||
model__in=CIRCUIT_TERMINATION_SCOPE_TYPES
|
||||
model__in=CIRCUIT_TERMINATION_TERMINATION_TYPES
|
||||
),
|
||||
allow_null=True,
|
||||
required=False,
|
||||
default=None
|
||||
)
|
||||
scope_id = serializers.IntegerField(allow_null=True, required=False, default=None)
|
||||
scope = serializers.SerializerMethodField(read_only=True)
|
||||
termination_id = serializers.IntegerField(allow_null=True, required=False, default=None)
|
||||
termination = serializers.SerializerMethodField(read_only=True)
|
||||
provider_network = ProviderNetworkSerializer(nested=True, required=False, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = CircuitTermination
|
||||
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',
|
||||
'link_peers', 'link_peers_type', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'circuit', 'term_side', 'description', 'cable', '_occupied')
|
||||
|
||||
@extend_schema_field(serializers.JSONField(allow_null=True))
|
||||
def get_scope(self, obj):
|
||||
if obj.scope_id is None:
|
||||
def get_termination(self, obj):
|
||||
if obj.termination_id is None:
|
||||
return None
|
||||
serializer = get_serializer_for_model(obj.scope)
|
||||
serializer = get_serializer_for_model(obj.termination)
|
||||
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_):
|
||||
|
@ -1,4 +1,4 @@
|
||||
# models values for ContentTypes which may be CircuitTermination scope types
|
||||
CIRCUIT_TERMINATION_SCOPE_TYPES = (
|
||||
# models values for ContentTypes which may be CircuitTermination termination types
|
||||
CIRCUIT_TERMINATION_TERMINATION_TYPES = (
|
||||
'region', 'sitegroup', 'site', 'location', 'providernetwork',
|
||||
)
|
||||
|
@ -263,7 +263,7 @@ class CircuitTerminationFilterSet(NetBoxModelFilterSet, CabledObjectFilterSet):
|
||||
queryset=Circuit.objects.all(),
|
||||
label=_('Circuit'),
|
||||
)
|
||||
scope_type = ContentTypeFilter()
|
||||
termination_type = ContentTypeFilter()
|
||||
region_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='_region',
|
||||
@ -334,7 +334,7 @@ class CircuitTerminationFilterSet(NetBoxModelFilterSet, CabledObjectFilterSet):
|
||||
class Meta:
|
||||
model = CircuitTermination
|
||||
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',
|
||||
)
|
||||
|
||||
|
@ -3,7 +3,7 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
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 dcim.models import Site
|
||||
from ipam.models import ASN
|
||||
@ -199,14 +199,14 @@ class CircuitTerminationBulkEditForm(NetBoxModelBulkEditForm):
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
scope_type = ContentTypeChoiceField(
|
||||
queryset=ContentType.objects.filter(model__in=CIRCUIT_TERMINATION_SCOPE_TYPES),
|
||||
termination_type = ContentTypeChoiceField(
|
||||
queryset=ContentType.objects.filter(model__in=CIRCUIT_TERMINATION_TERMINATION_TYPES),
|
||||
widget=HTMXSelect(method='post', attrs={'hx-select': '#form_fields'}),
|
||||
required=False,
|
||||
label=_('Scope type')
|
||||
label=_('Termination type')
|
||||
)
|
||||
scope = DynamicModelChoiceField(
|
||||
label=_('Scope'),
|
||||
termination = DynamicModelChoiceField(
|
||||
label=_('Termination'),
|
||||
queryset=Site.objects.none(), # Initial queryset
|
||||
required=False,
|
||||
disabled=True,
|
||||
@ -230,24 +230,24 @@ class CircuitTerminationBulkEditForm(NetBoxModelBulkEditForm):
|
||||
fieldsets = (
|
||||
FieldSet(
|
||||
'description',
|
||||
'scope_type', 'scope',
|
||||
'termination_type', 'termination',
|
||||
'mark_connected', name=_('Circuit Termination')
|
||||
),
|
||||
FieldSet('port_speed', 'upstream_speed', name=_('Termination Details')),
|
||||
)
|
||||
nullable_fields = ('description', 'scope')
|
||||
nullable_fields = ('description', 'termination')
|
||||
|
||||
def __init__(self, *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:
|
||||
scope_type = ContentType.objects.get(pk=scope_type_id)
|
||||
model = scope_type.model_class()
|
||||
self.fields['scope'].queryset = model.objects.all()
|
||||
self.fields['scope'].widget.attrs['selector'] = model._meta.label_lower
|
||||
self.fields['scope'].disabled = False
|
||||
self.fields['scope'].label = _(bettertitle(model._meta.verbose_name))
|
||||
termination_type = ContentType.objects.get(pk=termination_type_id)
|
||||
model = termination_type.model_class()
|
||||
self.fields['termination'].queryset = model.objects.all()
|
||||
self.fields['termination'].widget.attrs['selector'] = model._meta.label_lower
|
||||
self.fields['termination'].disabled = False
|
||||
self.fields['termination'].label = _(bettertitle(model._meta.verbose_name))
|
||||
except ObjectDoesNotExist:
|
||||
pass
|
||||
|
||||
|
@ -128,10 +128,10 @@ class BaseCircuitTerminationImportForm(forms.ModelForm):
|
||||
label=_('Termination'),
|
||||
choices=CircuitTerminationSideChoices,
|
||||
)
|
||||
scope_type = CSVContentTypeField(
|
||||
queryset=ContentType.objects.filter(model__in=CIRCUIT_TERMINATION_SCOPE_TYPES),
|
||||
termination_type = CSVContentTypeField(
|
||||
queryset=ContentType.objects.filter(model__in=CIRCUIT_TERMINATION_TERMINATION_TYPES),
|
||||
required=False,
|
||||
label=_('Scope type (app & model)')
|
||||
label=_('Termination type (app & model)')
|
||||
)
|
||||
|
||||
|
||||
@ -139,11 +139,11 @@ class CircuitTerminationImportRelatedForm(BaseCircuitTerminationImportForm):
|
||||
class Meta:
|
||||
model = CircuitTermination
|
||||
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'
|
||||
]
|
||||
labels = {
|
||||
'scope_id': 'Scope ID',
|
||||
'termination_id': 'Termination ID',
|
||||
}
|
||||
|
||||
|
||||
@ -152,11 +152,11 @@ class CircuitTerminationImportForm(NetBoxModelImportForm, BaseCircuitTermination
|
||||
class Meta:
|
||||
model = CircuitTermination
|
||||
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'
|
||||
]
|
||||
labels = {
|
||||
'scope_id': 'Scope ID',
|
||||
'termination_id': 'Termination ID',
|
||||
}
|
||||
|
||||
|
||||
|
@ -208,7 +208,7 @@ class CircuitTerminationFilterForm(NetBoxModelFilterSetForm):
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('circuit_id', 'term_side', name=_('Circuit')),
|
||||
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(
|
||||
queryset=Region.objects.all(),
|
||||
|
@ -149,14 +149,14 @@ class CircuitTerminationForm(NetBoxModelForm):
|
||||
queryset=Circuit.objects.all(),
|
||||
selector=True
|
||||
)
|
||||
scope_type = ContentTypeChoiceField(
|
||||
queryset=ContentType.objects.filter(model__in=CIRCUIT_TERMINATION_SCOPE_TYPES),
|
||||
termination_type = ContentTypeChoiceField(
|
||||
queryset=ContentType.objects.filter(model__in=CIRCUIT_TERMINATION_TERMINATION_TYPES),
|
||||
widget=HTMXSelect(),
|
||||
required=False,
|
||||
label=_('Scope type')
|
||||
label=_('Termination type')
|
||||
)
|
||||
scope = DynamicModelChoiceField(
|
||||
label=_('Scope'),
|
||||
termination = DynamicModelChoiceField(
|
||||
label=_('Termination'),
|
||||
queryset=Site.objects.none(), # Initial queryset
|
||||
required=False,
|
||||
disabled=True,
|
||||
@ -166,7 +166,7 @@ class CircuitTerminationForm(NetBoxModelForm):
|
||||
fieldsets = (
|
||||
FieldSet(
|
||||
'circuit', 'term_side', 'description', 'tags',
|
||||
'scope_type', 'scope',
|
||||
'termination_type', 'termination',
|
||||
'mark_connected', name=_('Circuit Termination')
|
||||
),
|
||||
FieldSet('port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', name=_('Termination Details')),
|
||||
@ -175,7 +175,7 @@ class CircuitTerminationForm(NetBoxModelForm):
|
||||
class Meta:
|
||||
model = CircuitTermination
|
||||
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',
|
||||
]
|
||||
widgets = {
|
||||
@ -191,31 +191,31 @@ class CircuitTerminationForm(NetBoxModelForm):
|
||||
instance = kwargs.get('instance')
|
||||
initial = kwargs.get('initial', {})
|
||||
|
||||
if instance is not None and instance.scope:
|
||||
initial['scope'] = instance.scope
|
||||
if instance is not None and instance.termination:
|
||||
initial['termination'] = instance.termination
|
||||
kwargs['initial'] = initial
|
||||
|
||||
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:
|
||||
scope_type = ContentType.objects.get(pk=scope_type_id)
|
||||
model = scope_type.model_class()
|
||||
self.fields['scope'].queryset = model.objects.all()
|
||||
self.fields['scope'].widget.attrs['selector'] = model._meta.label_lower
|
||||
self.fields['scope'].disabled = False
|
||||
self.fields['scope'].label = _(bettertitle(model._meta.verbose_name))
|
||||
termination_type = ContentType.objects.get(pk=termination_type_id)
|
||||
model = termination_type.model_class()
|
||||
self.fields['termination'].queryset = model.objects.all()
|
||||
self.fields['termination'].widget.attrs['selector'] = model._meta.label_lower
|
||||
self.fields['termination'].disabled = False
|
||||
self.fields['termination'].label = _(bettertitle(model._meta.verbose_name))
|
||||
except ObjectDoesNotExist:
|
||||
pass
|
||||
|
||||
if self.instance and scope_type_id != self.instance.scope_type_id:
|
||||
self.initial['scope'] = None
|
||||
if self.instance and termination_type_id != self.instance.termination_type_id:
|
||||
self.initial['termination'] = None
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
|
||||
# Assign the selected scope (if any)
|
||||
self.instance.scope = self.cleaned_data.get('scope')
|
||||
# Assign the selected termination (if any)
|
||||
self.instance.termination = self.cleaned_data.get('termination')
|
||||
|
||||
|
||||
class CircuitGroupForm(TenancyForm, NetBoxModelForm):
|
||||
|
@ -59,21 +59,21 @@ class ProviderNetworkType(NetBoxObjectType):
|
||||
|
||||
@strawberry_django.type(
|
||||
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
|
||||
)
|
||||
class CircuitTerminationType(CustomFieldsMixin, TagsMixin, CabledObjectMixin, ObjectType):
|
||||
circuit: Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]
|
||||
|
||||
@strawberry_django.field
|
||||
def scope(self) -> Annotated[Union[
|
||||
def termination(self) -> Annotated[Union[
|
||||
Annotated["LocationType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["RegionType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["SiteType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["ProviderNetworkType", strawberry.lazy('circuits.graphql.types')],
|
||||
], strawberry.union("CircuitTerminationScopeType")] | None:
|
||||
return self.scope
|
||||
], strawberry.union("CircuitTerminationTerminationType")] | None:
|
||||
return self.termination
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
|
@ -4,21 +4,21 @@ from django.db import migrations, models
|
||||
|
||||
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')
|
||||
CircuitTermination = apps.get_model('circuits', 'CircuitTermination')
|
||||
Site = apps.get_model('dcim', 'Site')
|
||||
|
||||
CircuitTermination.objects.filter(site__isnull=False).update(
|
||||
scope_type=ContentType.objects.get_for_model(Site),
|
||||
scope_id=models.F('site_id')
|
||||
termination_type=ContentType.objects.get_for_model(Site),
|
||||
termination_id=models.F('site_id')
|
||||
)
|
||||
|
||||
ProviderNetwork = apps.get_model('circuits', 'ProviderNetwork')
|
||||
CircuitTermination.objects.filter(provider_network__isnull=False).update(
|
||||
scope_type=ContentType.objects.get_for_model(ProviderNetwork),
|
||||
scope_id=models.F('provider_network_id')
|
||||
termination_type=ContentType.objects.get_for_model(ProviderNetwork),
|
||||
termination_id=models.F('provider_network_id')
|
||||
)
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
@ -32,12 +32,12 @@ class Migration(migrations.Migration):
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='circuittermination',
|
||||
name='scope_id',
|
||||
name='termination_id',
|
||||
field=models.PositiveBigIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='circuittermination',
|
||||
name='scope_type',
|
||||
name='termination_type',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
limit_choices_to=models.Q(('model__in', ('region', 'sitegroup', 'site', 'location', 'providernetwork'))),
|
@ -5,7 +5,7 @@ from django.db import migrations, models
|
||||
|
||||
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')
|
||||
|
||||
@ -22,7 +22,7 @@ def populate_denormalized_fields(apps, schema_editor):
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('circuits', '0047_circuittermination__scope'),
|
||||
('circuits', '0047_circuittermination__termination'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@ -33,7 +33,7 @@ class Migration(migrations.Migration):
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='_circuit_terminations',
|
||||
related_name='circuit_terminations',
|
||||
to='dcim.location',
|
||||
),
|
||||
),
|
||||
@ -44,7 +44,7 @@ class Migration(migrations.Migration):
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='_circuit_terminations',
|
||||
related_name='circuit_terminations',
|
||||
to='dcim.region',
|
||||
),
|
||||
),
|
||||
@ -55,7 +55,7 @@ class Migration(migrations.Migration):
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='_circuit_terminations',
|
||||
related_name='circuit_terminations',
|
||||
to='dcim.site',
|
||||
),
|
||||
),
|
||||
@ -66,7 +66,7 @@ class Migration(migrations.Migration):
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='_circuit_terminations',
|
||||
related_name='circuit_terminations',
|
||||
to='dcim.sitegroup',
|
||||
),
|
||||
),
|
||||
|
@ -236,21 +236,21 @@ class CircuitTermination(
|
||||
choices=CircuitTerminationSideChoices,
|
||||
verbose_name=_('termination')
|
||||
)
|
||||
scope_type = models.ForeignKey(
|
||||
termination_type = models.ForeignKey(
|
||||
to='contenttypes.ContentType',
|
||||
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='+',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
scope_id = models.PositiveBigIntegerField(
|
||||
termination_id = models.PositiveBigIntegerField(
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
scope = GenericForeignKey(
|
||||
ct_field='scope_type',
|
||||
fk_field='scope_id'
|
||||
termination = GenericForeignKey(
|
||||
ct_field='termination_type',
|
||||
fk_field='termination_id'
|
||||
)
|
||||
port_speed = models.PositiveIntegerField(
|
||||
verbose_name=_('port speed (Kbps)'),
|
||||
@ -293,28 +293,28 @@ class CircuitTermination(
|
||||
_location = models.ForeignKey(
|
||||
to='dcim.Location',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='_circuit_terminations',
|
||||
related_name='circuit_terminations',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
_site = models.ForeignKey(
|
||||
to='dcim.Site',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='_circuit_terminations',
|
||||
related_name='circuit_terminations',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
_region = models.ForeignKey(
|
||||
to='dcim.Region',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='_circuit_terminations',
|
||||
related_name='circuit_terminations',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
_sitegroup = models.ForeignKey(
|
||||
to='dcim.SiteGroup',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='_circuit_terminations',
|
||||
related_name='circuit_terminations',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
@ -340,8 +340,8 @@ class CircuitTermination(
|
||||
super().clean()
|
||||
|
||||
# Must define either site *or* provider network
|
||||
if self.scope is None:
|
||||
raise ValidationError(_("A circuit termination must attach to either a scope or a provider network."))
|
||||
if self.termination is None:
|
||||
raise ValidationError(_("A circuit termination must attach to termination."))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# Cache objects associated with the terminating object (for filtering)
|
||||
@ -351,23 +351,23 @@ class CircuitTermination(
|
||||
|
||||
def cache_related_objects(self):
|
||||
self._provider_network = self._region = self._sitegroup = self._site = self._location = None
|
||||
if self.scope_type:
|
||||
scope_type = self.scope_type.model_class()
|
||||
if scope_type == apps.get_model('dcim', 'region'):
|
||||
self._region = self.scope
|
||||
elif scope_type == apps.get_model('dcim', 'sitegroup'):
|
||||
self._sitegroup = self.scope
|
||||
elif scope_type == apps.get_model('dcim', 'site'):
|
||||
self._region = self.scope.region
|
||||
self._sitegroup = self.scope.group
|
||||
self._site = self.scope
|
||||
elif scope_type == apps.get_model('dcim', 'location'):
|
||||
self._region = self.scope.site.region
|
||||
self._sitegroup = self.scope.site.group
|
||||
self._site = self.scope.site
|
||||
self._location = self.scope
|
||||
elif scope_type == apps.get_model('circuits', 'providernetwork'):
|
||||
self._provider_network = self.scope
|
||||
if self.termination_type:
|
||||
termination_type = self.termination_type.model_class()
|
||||
if termination_type == apps.get_model('dcim', 'region'):
|
||||
self._region = self.termination
|
||||
elif termination_type == apps.get_model('dcim', 'sitegroup'):
|
||||
self._sitegroup = self.termination
|
||||
elif termination_type == apps.get_model('dcim', 'site'):
|
||||
self._region = self.termination.region
|
||||
self._sitegroup = self.termination.group
|
||||
self._site = self.termination
|
||||
elif termination_type == apps.get_model('dcim', 'location'):
|
||||
self._region = self.termination.site.region
|
||||
self._sitegroup = self.termination.site.group
|
||||
self._site = self.termination.site
|
||||
self._location = self.termination
|
||||
elif termination_type == apps.get_model('circuits', 'providernetwork'):
|
||||
self._provider_network = self.termination
|
||||
cache_related_objects.alters_data = True
|
||||
|
||||
def to_objectchange(self, action):
|
||||
@ -382,7 +382,7 @@ class CircuitTermination(
|
||||
def get_peer_termination(self):
|
||||
peer_side = 'Z' if self.term_side == 'A' else 'A'
|
||||
try:
|
||||
return CircuitTermination.objects.prefetch_related('scope').get(
|
||||
return CircuitTermination.objects.prefetch_related('termination').get(
|
||||
circuit=self.circuit,
|
||||
term_side=peer_side
|
||||
)
|
||||
|
@ -181,10 +181,10 @@ class CircuitTerminationTest(APIViewTestCases.APIViewTestCase):
|
||||
Circuit.objects.bulk_create(circuits)
|
||||
|
||||
circuit_terminations = (
|
||||
CircuitTermination(circuit=circuits[0], term_side=SIDE_A, scope=sites[0]),
|
||||
CircuitTermination(circuit=circuits[0], term_side=SIDE_Z, scope=provider_networks[0]),
|
||||
CircuitTermination(circuit=circuits[1], term_side=SIDE_A, scope=sites[1]),
|
||||
CircuitTermination(circuit=circuits[1], term_side=SIDE_Z, scope=provider_networks[1]),
|
||||
CircuitTermination(circuit=circuits[0], term_side=SIDE_A, termination=sites[0]),
|
||||
CircuitTermination(circuit=circuits[0], term_side=SIDE_Z, termination=provider_networks[0]),
|
||||
CircuitTermination(circuit=circuits[1], term_side=SIDE_A, termination=sites[1]),
|
||||
CircuitTermination(circuit=circuits[1], term_side=SIDE_Z, termination=provider_networks[1]),
|
||||
)
|
||||
CircuitTermination.objects.bulk_create(circuit_terminations)
|
||||
|
||||
@ -192,15 +192,15 @@ class CircuitTerminationTest(APIViewTestCases.APIViewTestCase):
|
||||
{
|
||||
'circuit': circuits[2].pk,
|
||||
'term_side': SIDE_A,
|
||||
'scope_type': 'dcim.site',
|
||||
'scope_id': sites[0].pk,
|
||||
'termination_type': 'dcim.site',
|
||||
'termination_id': sites[0].pk,
|
||||
'port_speed': 200000,
|
||||
},
|
||||
{
|
||||
'circuit': circuits[2].pk,
|
||||
'term_side': SIDE_Z,
|
||||
'scope_type': 'circuits.providernetwork',
|
||||
'scope_id': provider_networks[0].pk,
|
||||
'termination_type': 'circuits.providernetwork',
|
||||
'termination_id': provider_networks[0].pk,
|
||||
'port_speed': 200000,
|
||||
},
|
||||
]
|
||||
|
@ -71,8 +71,8 @@ class ProviderTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
Circuit.objects.bulk_create(circuits)
|
||||
|
||||
circuit_terminations = (
|
||||
CircuitTermination(circuit=circuits[0], scope=sites[0], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[1], scope=sites[0], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[0], termination=sites[0], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[1], termination=sites[0], term_side='A'),
|
||||
)
|
||||
for ct in circuit_terminations:
|
||||
ct.save()
|
||||
@ -235,12 +235,12 @@ class CircuitTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
Circuit.objects.bulk_create(circuits)
|
||||
|
||||
circuit_terminations = ((
|
||||
CircuitTermination(circuit=circuits[0], scope=sites[0], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[1], scope=sites[1], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[2], scope=sites[2], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[3], scope=provider_networks[0], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[4], scope=provider_networks[1], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[5], scope=provider_networks[2], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[0], termination=sites[0], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[1], termination=sites[1], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[2], termination=sites[2], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[3], termination=provider_networks[0], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[4], termination=provider_networks[1], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[5], termination=provider_networks[2], term_side='A'),
|
||||
))
|
||||
for ct in circuit_terminations:
|
||||
ct.save()
|
||||
@ -387,16 +387,16 @@ class CircuitTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
Circuit.objects.bulk_create(circuits)
|
||||
|
||||
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], scope=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], scope=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], scope=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[4], scope=provider_networks[1], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[5], scope=provider_networks[2], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[6], scope=provider_networks[0], term_side='A', mark_connected=True),
|
||||
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], termination=sites[1], term_side='Z', port_speed=1000, upstream_speed=1000, xconnect_id='DEF', description='foobar2'),
|
||||
CircuitTermination(circuit=circuits[1], termination=sites[1], term_side='A', port_speed=2000, upstream_speed=2000, xconnect_id='GHI'),
|
||||
CircuitTermination(circuit=circuits[1], termination=sites[2], term_side='Z', port_speed=2000, upstream_speed=2000, xconnect_id='JKL'),
|
||||
CircuitTermination(circuit=circuits[2], termination=sites[2], term_side='A', port_speed=3000, upstream_speed=3000, xconnect_id='MNO'),
|
||||
CircuitTermination(circuit=circuits[2], termination=sites[0], term_side='Z', port_speed=3000, upstream_speed=3000, xconnect_id='PQR'),
|
||||
CircuitTermination(circuit=circuits[3], termination=provider_networks[0], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[4], termination=provider_networks[1], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[5], termination=provider_networks[2], term_side='A'),
|
||||
CircuitTermination(circuit=circuits[6], termination=provider_networks[0], term_side='A', mark_connected=True),
|
||||
))
|
||||
for ct in circuit_terminations:
|
||||
ct.save()
|
||||
|
@ -203,23 +203,23 @@ class CircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
"terminations": [
|
||||
{
|
||||
"term_side": "A",
|
||||
"scope_type": "dcim.site",
|
||||
"scope_id": "1"
|
||||
"termination_type": "dcim.site",
|
||||
"termination_id": "1"
|
||||
},
|
||||
{
|
||||
"term_side": "Z",
|
||||
"scope_type": "dcim.site",
|
||||
"scope_id": "1"
|
||||
"termination_type": "dcim.site",
|
||||
"termination_id": "1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
"""
|
||||
|
||||
# Fix up the scope site id
|
||||
# Fix up the termination site id
|
||||
site = Site.objects.first()
|
||||
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)
|
||||
|
||||
initial_count = self._get_queryset().count()
|
||||
@ -370,10 +370,10 @@ class TestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
Circuit.objects.bulk_create(circuits)
|
||||
|
||||
circuit_terminations = (
|
||||
CircuitTermination(circuit=circuits[0], term_side='A', scope=sites[0]),
|
||||
CircuitTermination(circuit=circuits[0], term_side='Z', scope=sites[1]),
|
||||
CircuitTermination(circuit=circuits[1], term_side='A', scope=sites[0]),
|
||||
CircuitTermination(circuit=circuits[1], term_side='Z', scope=sites[1]),
|
||||
CircuitTermination(circuit=circuits[0], term_side='A', termination=sites[0]),
|
||||
CircuitTermination(circuit=circuits[0], term_side='Z', termination=sites[1]),
|
||||
CircuitTermination(circuit=circuits[1], term_side='A', termination=sites[0]),
|
||||
CircuitTermination(circuit=circuits[1], term_side='Z', termination=sites[1]),
|
||||
)
|
||||
for ct in circuit_terminations:
|
||||
ct.save()
|
||||
@ -381,14 +381,14 @@ class TestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
cls.form_data = {
|
||||
'circuit': circuits[2].pk,
|
||||
'term_side': 'A',
|
||||
'scope_type': ContentType.objects.get_for_model(Site).pk,
|
||||
'scope': sites[2].pk,
|
||||
'termination_type': ContentType.objects.get_for_model(Site).pk,
|
||||
'termination': sites[2].pk,
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
site = sites[0].pk
|
||||
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,Z,dcim.site,{site},Bar",
|
||||
)
|
||||
|
@ -1167,7 +1167,7 @@ class CablePathTestCase(TestCase):
|
||||
[IF1] --C1-- [CT1]
|
||||
"""
|
||||
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
|
||||
cable1 = Cable(
|
||||
@ -1198,7 +1198,7 @@ class CablePathTestCase(TestCase):
|
||||
"""
|
||||
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
|
||||
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
|
||||
cable1 = Cable(
|
||||
@ -1214,7 +1214,7 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
|
||||
# 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
|
||||
self.assertPathExists(
|
||||
@ -1266,7 +1266,7 @@ class CablePathTestCase(TestCase):
|
||||
interface2 = Interface.objects.create(device=self.device, name='Interface 2')
|
||||
interface3 = Interface.objects.create(device=self.device, name='Interface 3')
|
||||
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
|
||||
cable1 = Cable(
|
||||
@ -1282,7 +1282,7 @@ class CablePathTestCase(TestCase):
|
||||
)
|
||||
|
||||
# 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
|
||||
self.assertPathExists(
|
||||
@ -1335,8 +1335,8 @@ class CablePathTestCase(TestCase):
|
||||
"""
|
||||
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
|
||||
site2 = Site.objects.create(name='Site 2', slug='site-2')
|
||||
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, scope=self.site, term_side='A')
|
||||
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, scope=site2, term_side='Z')
|
||||
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='A')
|
||||
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, termination=site2, term_side='Z')
|
||||
|
||||
# Create cable 1
|
||||
cable1 = Cable(
|
||||
@ -1365,8 +1365,8 @@ class CablePathTestCase(TestCase):
|
||||
"""
|
||||
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
|
||||
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')
|
||||
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, scope=providernetwork, term_side='Z')
|
||||
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='A')
|
||||
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, termination=providernetwork, term_side='Z')
|
||||
|
||||
# Create cable 1
|
||||
cable1 = Cable(
|
||||
@ -1413,8 +1413,8 @@ class CablePathTestCase(TestCase):
|
||||
frontport2_2 = FrontPort.objects.create(
|
||||
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')
|
||||
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, scope=self.site, term_side='Z')
|
||||
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='A')
|
||||
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='Z')
|
||||
|
||||
# Create cables
|
||||
cable1 = Cable(
|
||||
@ -1499,10 +1499,10 @@ class CablePathTestCase(TestCase):
|
||||
interface1 = Interface.objects.create(device=self.device, name='Interface 1')
|
||||
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')
|
||||
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, scope=self.site, term_side='A')
|
||||
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, scope=self.site, term_side='Z')
|
||||
circuittermination3 = CircuitTermination.objects.create(circuit=circuit2, scope=self.site, term_side='A')
|
||||
circuittermination4 = CircuitTermination.objects.create(circuit=circuit2, scope=self.site, term_side='Z')
|
||||
circuittermination1 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='A')
|
||||
circuittermination2 = CircuitTermination.objects.create(circuit=self.circuit, termination=self.site, term_side='Z')
|
||||
circuittermination3 = CircuitTermination.objects.create(circuit=circuit2, termination=self.site, term_side='A')
|
||||
circuittermination4 = CircuitTermination.objects.create(circuit=circuit2, termination=self.site, term_side='Z')
|
||||
|
||||
# Create cables
|
||||
cable1 = Cable(
|
||||
|
@ -5117,7 +5117,7 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
provider = Provider.objects.create(name='Provider 1', slug='provider-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_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 = (
|
||||
|
@ -762,9 +762,9 @@ class CableTestCase(TestCase):
|
||||
circuittype = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
|
||||
circuit1 = Circuit.objects.create(provider=provider, type=circuittype, cid='1')
|
||||
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, scope=site, term_side='Z')
|
||||
CircuitTermination.objects.create(circuit=circuit2, scope=provider_network, term_side='A')
|
||||
CircuitTermination.objects.create(circuit=circuit1, termination=site, term_side='A')
|
||||
CircuitTermination.objects.create(circuit=circuit1, termination=site, term_side='Z')
|
||||
CircuitTermination.objects.create(circuit=circuit2, termination=provider_network, term_side='A')
|
||||
|
||||
def test_cable_creation(self):
|
||||
"""
|
||||
|
@ -1,11 +1,11 @@
|
||||
{% load helpers %}
|
||||
{% load i18n %}
|
||||
|
||||
{% if termination.scope %}
|
||||
{% if termination.termination %}
|
||||
<tr>
|
||||
<th scope="row">{% trans "Scope" %}</th>
|
||||
{% if termination.scope %}
|
||||
<td>{{ termination.scope|linkify }} ({% trans termination.scope_type.name %})</td>
|
||||
<th scope="row">{% trans "Termination" %}</th>
|
||||
{% if termination.termination %}
|
||||
<td>{{ termination.termination|linkify }} ({% trans termination.termination_type.name %})</td>
|
||||
{% else %}
|
||||
<td>{{ ''|placeholder }}</td>
|
||||
{% endif %}
|
||||
|
Loading…
Reference in New Issue
Block a user