content: from functools import cached_property
  
  from django.contrib.contenttypes.fields import GenericRelation
  from django.core.exceptions import ValidationError
  from django.db import models
  from django.urls import reverse
  from django.utils.translation import gettext_lazy as _
  
  from circuits.choices import *
  from netbox.models import ChangeLoggedModel, PrimaryModel
  from netbox.models.features import CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, TagsMixin
  from .base import BaseCircuitType
  
  __all__ = (
      'VirtualCircuit',
      'VirtualCircuitTermination',
      'VirtualCircuitType',
  )
  
  
  class VirtualCircuitType(BaseCircuitType):
      """
      Like physical circuits, virtual circuits can be organized by their functional role. For example, a user might wish
      to categorize virtual circuits by their technological nature or by product name.
      """
      class Meta:
          ordering = ('name',)
          verbose_name = _('virtual circuit type')
          verbose_name_plural = _('virtual circuit types')
  
  
  class VirtualCircuit(PrimaryModel):
      """
      A virtual connection between two or more endpoints, delivered across one or more physical circuits.
      """
      cid = models.CharField(
          max_length=100,
          verbose_name=_('circuit ID'),
          help_text=_('Unique circuit ID')
      )
      provider_network = models.ForeignKey(
          to='circuits.ProviderNetwork',
          on_delete=models.PROTECT,
          related_name='virtual_circuits'
      )
      provider_account = models.ForeignKey(
          to='circuits.ProviderAccount',
          on_delete=models.PROTECT,
          related_name='virtual_circuits',
          blank=True,
          null=True
      )
      type = models.ForeignKey(
          to='circuits.VirtualCircuitType',
          on_delete=models.PROTECT,
          related_name='virtual_circuits'
      )
      status = models.CharField(
          verbose_name=_('status'),
          max_length=50,
          choices=CircuitStatusChoices,
          default=CircuitStatusChoices.STATUS_ACTIVE
      )
      tenant = models.ForeignKey(
          to='tenancy.Tenant',
          on_delete=models.PROTECT,
          related_name='virtual_circuits',
          blank=True,
          null=True
      )
  
      group_assignments = GenericRelation(
          to='circuits.CircuitGroupAssignment',
          content_type_field='member_type',
          object_id_field='member_id',
          related_query_name='virtual_circuit'
      )
  
      clone_fields = (
          'provider_network', 'provider_account', 'status', 'tenant', 'description',
      )
      prerequisite_models = (
          'circuits.ProviderNetwork',
          'circuits.VirtualCircuitType',
      )
  
      class Meta:
          ordering = ['provider_network', 'provider_account', 'cid']
          constraints = (
              models.UniqueConstraint(
                  fields=('provider_network', 'cid'),
                  name='%(app_label)s_%(class)s_unique_provider_network_cid'
              ),
              models.UniqueConstraint(
                  fields=('provider_account', 'cid'),
                  name='%(app_label)s_%(class)s_unique_provideraccount_cid'
              ),
          )
          verbose_name = _('virtual circuit')
          verbose_name_plural = _('virtual circuits')
  
      def __str__(self):
          return self.cid
  
      def get_status_color(self):
          return CircuitStatusChoices.colors.get(self.status)
  
      def clean(self):
          super().clean()
  
          if self.provider_account and self.provider_network.provider != self.provider_account.provider:
              raise ValidationError({
                  'provider_account': "The assigned account must belong to the provider of the assigned network."
              })
  
      @property
      def provider(self):
          return self.provider_network.provider
  
  
  class VirtualCircuitTermination(
      CustomFieldsMixin,
      CustomLinksMixin,
      ExportTemplatesMixin,
      TagsMixin,
      ChangeLoggedModel
  ):
      virtual_circuit = models.ForeignKey(
          to='circuits.VirtualCircuit',
          on_delete=models.CASCADE,
          related_name='terminations'
      )
      role = models.CharField(
          verbose_name=_('role'),
          max_length=50,
          choices=VirtualCircuitTerminationRoleChoices,
          default=VirtualCircuitTerminationRoleChoices.ROLE_PEER
      )
      interface = models.OneToOneField(
          to='dcim.Interface',
          on_delete=models.CASCADE,
          related_name='virtual_circuit_termination'
      )
      description = models.CharField(
          verbose_name=_('description'),
          max_length=200,
          blank=True
      )
  
      class Meta:
          ordering = ['virtual_circuit', 'role', 'pk']
          verbose_name = _('virtual circuit termination')
          verbose_name_plural = _('virtual circuit terminations')
  
      def __str__(self):
          return f'{self.virtual_circuit}: {self.get_role_display()} termination'
  
      def get_absolute_url(self):
          return reverse('circuits:virtualcircuittermination', args=[self.pk])
  
      def get_role_color(self):
          return VirtualCircuitTerminationRoleChoices.colors.get(self.role)
  
      def to_objectchange(self, action):
          objectchange = super().to_objectchange(action)
          objectchange.related_object = self.virtual_circuit
          return objectchange
  
      @property
      def parent_object(self):
          return self.virtual_circuit
  
      @cached_property
      def peer_terminations(self):
          if self.role == VirtualCircuitTerminationRoleChoices.ROLE_PEER:
              return self.virtual_circuit.terminations.exclude(pk=self.pk).filter(
                  role=VirtualCircuitTerminationRoleChoices.ROLE_PEER
              )
          if self.role == VirtualCircuitTerminationRoleChoices.ROLE_HUB:
              return self.virtual_circuit.terminations.filter(
                  role=VirtualCircuitTerminationRoleChoices.ROLE_SPOKE
              )
          if self.role == VirtualCircuitTerminationRoleChoices.ROLE_SPOKE:
              return self.virtual_circuit.terminations.filter(
                  role=VirtualCircuitTerminationRoleChoices.ROLE_HUB
              )
  
      def clean(self):
          super().clean()
  
          if self.interface and not self.interface.is_virtual:
              raise ValidationError("Virtual circuits may be terminated only to virtual interfaces.")
  