content: from django.core.exceptions import ValidationError
  from django.db import models
  from django.utils.translation import gettext_lazy as _
  
  from ipam.fields import ASNField
  from ipam.querysets import ASNRangeQuerySet
  from netbox.models import OrganizationalModel, PrimaryModel
  from netbox.models.features import ContactsMixin
  
  __all__ = (
      'ASN',
      'ASNRange',
  )
  
  
  class ASNRange(OrganizationalModel):
      name = models.CharField(
          verbose_name=_('name'),
          max_length=100,
          unique=True,
          db_collation="natural_sort"
      )
      slug = models.SlugField(
          verbose_name=_('slug'),
          max_length=100,
          unique=True
      )
      rir = models.ForeignKey(
          to='ipam.RIR',
          on_delete=models.PROTECT,
          related_name='asn_ranges',
          verbose_name=_('RIR')
      )
      start = ASNField(
          verbose_name=_('start'),
      )
      end = ASNField(
          verbose_name=_('end'),
      )
      tenant = models.ForeignKey(
          to='tenancy.Tenant',
          on_delete=models.PROTECT,
          related_name='asn_ranges',
          blank=True,
          null=True
      )
  
      objects = ASNRangeQuerySet.as_manager()
  
      class Meta:
          ordering = ('name',)
          verbose_name = _('ASN range')
          verbose_name_plural = _('ASN ranges')
  
      def __str__(self):
          return f'{self.name} ({self.range_as_string()})'
  
      def clean(self):
          super().clean()
  
          if self.end <= self.start:
              raise ValidationError(
                  _("Starting ASN ({start}) must be lower than ending ASN ({end}).").format(
                      start=self.start, end=self.end
                  )
              )
  
      @property
      def range(self):
          """
          Return a range of integers representing the ASN range.
          """
          return range(self.start, self.end + 1)
  
      @property
      def start_asdot(self):
          """
          Return ASDOT notation for AS numbers greater than 16 bits.
          """
          return ASNField.to_asdot(self.start)
  
      @property
      def end_asdot(self):
          """
          Return ASDOT notation for AS numbers greater than 16 bits.
          """
          return ASNField.to_asdot(self.end)
  
      def range_as_string(self):
          """
          Return a string representation of the ASN range.
          """
          return f'{self.start}-{self.end}'
  
      def range_as_string_with_asdot(self):
          """
          Return a string representation of the ASN range, including ASDOT notation.
          """
          if self.end >= 65536:
              return f'{self.range_as_string()} ({self.start_asdot}-{self.end_asdot})'
          return self.range_as_string()
  
      def get_child_asns(self):
          """
          Return all child ASNs (ASNs within the range).
          """
          return ASN.objects.filter(
              asn__gte=self.start,
              asn__lte=self.end
          )
  
      def get_available_asns(self):
          """
          Return all available ASNs within this range.
          """
          range = set(self.range)
          existing_asns = set(self.get_child_asns().values_list('asn', flat=True))
          available_asns = sorted(range - existing_asns)
  
          return available_asns
  
  
  class ASN(ContactsMixin, PrimaryModel):
      """
      An autonomous system (AS) number is typically used to represent an independent routing domain. A site can have
      one or more ASNs assigned to it.
      """
      rir = models.ForeignKey(
          to='ipam.RIR',
          on_delete=models.PROTECT,
          related_name='asns',
          verbose_name=_('RIR'),
          help_text=_("Regional Internet Registry responsible for this AS number space")
      )
      asn = ASNField(
          unique=True,
          verbose_name=_('ASN'),
          help_text=_('16- or 32-bit autonomous system number')
      )
      tenant = models.ForeignKey(
          to='tenancy.Tenant',
          on_delete=models.PROTECT,
          related_name='asns',
          blank=True,
          null=True
      )
  
      prerequisite_models = (
          'ipam.RIR',
      )
  
      class Meta:
          ordering = ['asn']
          verbose_name = _('ASN')
          verbose_name_plural = _('ASNs')
  
      def __str__(self):
          return f'AS{self.asn_with_asdot}'
  
      @property
      def asn_asdot(self):
          """
          Return ASDOT notation for AS numbers greater than 16 bits.
          """
          return ASNField.to_asdot(self.asn)
  
      @property
      def asn_with_asdot(self):
          """
          Return both plain and ASDOT notation, where applicable.
          """
          if self.asn >= 65536:
              return f'{self.asn} ({self.asn_asdot})'
          return str(self.asn)
  
      @property
      def prefixed_name(self):
          """
          Return the ASN with ASDOT notation prefixed with "AS".
          """
          return f'AS{self.asn_with_asdot}'
  