content: from django.apps import apps
  from django.contrib.contenttypes.fields import GenericForeignKey, 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 dcim.models import CabledObjectModel
  from netbox.models import ChangeLoggedModel, OrganizationalModel, PrimaryModel
  from netbox.models.mixins import DistanceMixin
  from netbox.models.features import (
      ContactsMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, ImageAttachmentsMixin, TagsMixin,
  )
  from .base import BaseCircuitType
  
  __all__ = (
      'Circuit',
      'CircuitGroup',
      'CircuitGroupAssignment',
      'CircuitTermination',
      'CircuitType',
  )
  
  
  class CircuitType(BaseCircuitType):
      """
      Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named
      "Long Haul," "Metro," or "Out-of-Band".
      """
      class Meta:
          ordering = ('name',)
          verbose_name = _('circuit type')
          verbose_name_plural = _('circuit types')
  
  
  class Circuit(ContactsMixin, ImageAttachmentsMixin, DistanceMixin, PrimaryModel):
      """
      A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple
      circuits. Each circuit is also assigned a CircuitType and a Site, and may optionally be assigned to a particular
      ProviderAccount. Circuit port speed and commit rate are measured in Kbps.
      """
      cid = models.CharField(
          max_length=100,
          verbose_name=_('circuit ID'),
          help_text=_('Unique circuit ID')
      )
      provider = models.ForeignKey(
          to='circuits.Provider',
          on_delete=models.PROTECT,
          related_name='circuits'
      )
      provider_account = models.ForeignKey(
          to='circuits.ProviderAccount',
          on_delete=models.PROTECT,
          related_name='circuits',
          blank=True,
          null=True
      )
      type = models.ForeignKey(
          to='circuits.CircuitType',
          on_delete=models.PROTECT,
          related_name='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='circuits',
          blank=True,
          null=True
      )
      install_date = models.DateField(
          blank=True,
          null=True,
          verbose_name=_('installed')
      )
      termination_date = models.DateField(
          blank=True,
          null=True,
          verbose_name=_('terminates')
      )
      commit_rate = models.PositiveIntegerField(
          blank=True,
          null=True,
          verbose_name=_('commit rate (Kbps)'),
          help_text=_("Committed rate")
      )
  
      # Cache associated CircuitTerminations
      termination_a = models.ForeignKey(
          to='circuits.CircuitTermination',
          on_delete=models.SET_NULL,
          related_name='+',
          editable=False,
          blank=True,
          null=True
      )
      termination_z = models.ForeignKey(
          to='circuits.CircuitTermination',
          on_delete=models.SET_NULL,
          related_name='+',
          editable=False,
          blank=True,
          null=True
      )
  
      group_assignments = GenericRelation(
          to='circuits.CircuitGroupAssignment',
          content_type_field='member_type',
          object_id_field='member_id',
          related_query_name='circuit'
      )
  
      clone_fields = (
          'provider', 'provider_account', 'type', 'status', 'tenant', 'install_date', 'termination_date', 'commit_rate',
          'description',
      )
      prerequisite_models = (
          'circuits.CircuitType',
          'circuits.Provider',
      )
  
      class Meta:
          ordering = ['provider', 'provider_account', 'cid']
          constraints = (
              models.UniqueConstraint(
                  fields=('provider', 'cid'),
                  name='%(app_label)s_%(class)s_unique_provider_cid'
              ),
              models.UniqueConstraint(
                  fields=('provider_account', 'cid'),
                  name='%(app_label)s_%(class)s_unique_provideraccount_cid'
              ),
          )
          verbose_name = _('circuit')
          verbose_name_plural = _('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 != self.provider_account.provider:
              raise ValidationError({'provider_account': "The assigned account must belong to the assigned provider."})
  
  
  class CircuitGroup(OrganizationalModel):
      """
      An administrative grouping of Circuits.
      """
      tenant = models.ForeignKey(
          to='tenancy.Tenant',
          on_delete=models.PROTECT,
          related_name='circuit_groups',
          blank=True,
          null=True
      )
  
      class Meta:
          ordering = ('name',)
          verbose_name = _('circuit group')
          verbose_name_plural = _('circuit groups')
  
      def __str__(self):
          return self.name
  
  
  class CircuitGroupAssignment(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, ChangeLoggedModel):
      """
      Assignment of a physical or virtual circuit to a CircuitGroup with an optional priority.
      """
      member_type = models.ForeignKey(
          to='contenttypes.ContentType',
          on_delete=models.PROTECT,
          related_name='+'
      )
      member_id = models.PositiveBigIntegerField(
          verbose_name=_('member ID')
      )
      member = GenericForeignKey(
          ct_field='member_type',
          fk_field='member_id'
      )
      group = models.ForeignKey(
          to='circuits.CircuitGroup',
          on_delete=models.CASCADE,
          related_name='assignments'
      )
      priority = models.CharField(
          verbose_name=_('priority'),
          max_length=50,
          choices=CircuitPriorityChoices,
          blank=True,
          null=True
      )
      prerequisite_models = (
          'circuits.CircuitGroup',
      )
  
      class Meta:
          ordering = ('group', 'member_type', 'member_id', 'priority', 'pk')
          constraints = (
              models.UniqueConstraint(
                  fields=('member_type', 'member_id', 'group'),
                  name='%(app_label)s_%(class)s_unique_member_group'
              ),
          )
          verbose_name = _('Circuit group assignment')
          verbose_name_plural = _('Circuit group assignments')
  
      def __str__(self):
          if self.priority:
              return f"{self.group} ({self.get_priority_display()})"
          return str(self.group)
  
      def get_absolute_url(self):
          return reverse('circuits:circuitgroupassignment', args=[self.pk])
  
  
  class CircuitTermination(
      CustomFieldsMixin,
      CustomLinksMixin,
      ExportTemplatesMixin,
      TagsMixin,
      ChangeLoggedModel,
      CabledObjectModel
  ):
      circuit = models.ForeignKey(
          to='circuits.Circuit',
          on_delete=models.CASCADE,
          related_name='terminations'
      )
      term_side = models.CharField(
          max_length=1,
          choices=CircuitTerminationSideChoices,
          verbose_name=_('termination side')
      )
      termination_type = models.ForeignKey(
          to='contenttypes.ContentType',
          on_delete=models.PROTECT,
          related_name='+',
          blank=True,
          null=True
      )
      termination_id = models.PositiveBigIntegerField(
          blank=True,
          null=True
      )
      termination = GenericForeignKey(
          ct_field='termination_type',
          fk_field='termination_id'
      )
      port_speed = models.PositiveIntegerField(
          verbose_name=_('port speed (Kbps)'),
          blank=True,
          null=True,
          help_text=_('Physical circuit speed')
      )
      upstream_speed = models.PositiveIntegerField(
          blank=True,
          null=True,
          verbose_name=_('upstream speed (Kbps)'),
          help_text=_('Upstream speed, if different from port speed')
      )
      xconnect_id = models.CharField(
          max_length=50,
          blank=True,
          verbose_name=_('cross-connect ID'),
          help_text=_('ID of the local cross-connect')
      )
      pp_info = models.CharField(
          max_length=100,
          blank=True,
          verbose_name=_('patch panel/port(s)'),
          help_text=_('Patch panel ID and port number(s)')
      )
      description = models.CharField(
          verbose_name=_('description'),
          max_length=200,
          blank=True
      )
  
      # Cached associations to enable efficient filtering
      _provider_network = models.ForeignKey(
          to='circuits.ProviderNetwork',
          on_delete=models.PROTECT,
          related_name='circuit_terminations',
          blank=True,
          null=True
      )
      _location = models.ForeignKey(
          to='dcim.Location',
          on_delete=models.CASCADE,
          related_name='circuit_terminations',
          blank=True,
          null=True
      )
      _site = models.ForeignKey(
          to='dcim.Site',
          on_delete=models.CASCADE,
          related_name='circuit_terminations',
          blank=True,
          null=True
      )
      _region = models.ForeignKey(
          to='dcim.Region',
          on_delete=models.CASCADE,
          related_name='circuit_terminations',
          blank=True,
          null=True
      )
      _site_group = models.ForeignKey(
          to='dcim.SiteGroup',
          on_delete=models.CASCADE,
          related_name='circuit_terminations',
          blank=True,
          null=True
      )
  
      class Meta:
          ordering = ['circuit', 'term_side']
          constraints = (
              models.UniqueConstraint(
                  fields=('circuit', 'term_side'),
                  name='%(app_label)s_%(class)s_unique_circuit_term_side'
              ),
          )
          verbose_name = _('circuit termination')
          verbose_name_plural = _('circuit terminations')
  
      def __str__(self):
          return f'{self.circuit}: Termination {self.term_side}'
  
      def get_absolute_url(self):
          return reverse('circuits:circuittermination', args=[self.pk])
  
      def clean(self):
          super().clean()
  
          if self.termination is None:
              raise ValidationError(_("A circuit termination must attach to a terminating object."))
  
      def save(self, *args, **kwargs):
          # Cache objects associated with the terminating object (for filtering)
          self.cache_related_objects()
  
          super().save(*args, **kwargs)
  
      def cache_related_objects(self):
          self._provider_network = self._region = self._site_group = self._site = self._location = None
          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._site_group = self.termination
              elif termination_type == apps.get_model('dcim', 'site'):
                  self._region = self.termination.region
                  self._site_group = self.termination.group
                  self._site = self.termination
              elif termination_type == apps.get_model('dcim', 'location'):
                  self._region = self.termination.site.region
                  self._site_group = 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):
          objectchange = super().to_objectchange(action)
          objectchange.related_object = self.circuit
          return objectchange
  
      @property
      def parent_object(self):
          return self.circuit
  
      def get_peer_termination(self):
          peer_side = 'Z' if self.term_side == 'A' else 'A'
          try:
              return CircuitTermination.objects.prefetch_related('termination').get(
                  circuit=self.circuit,
                  term_side=peer_side
              )
          except CircuitTermination.DoesNotExist:
              return None
  