mirror of
				https://github.com/netbox-community/netbox.git
				synced 2024-05-10 07:54:54 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			194 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			194 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
from django.contrib.contenttypes.fields import GenericForeignKey
 | 
						|
from django.contrib.contenttypes.models import ContentType
 | 
						|
from django.core.exceptions import ValidationError
 | 
						|
from django.core.validators import MaxValueValidator, MinValueValidator
 | 
						|
from django.db import models
 | 
						|
from django.urls import reverse
 | 
						|
 | 
						|
from dcim.models import Interface
 | 
						|
from extras.utils import extras_features
 | 
						|
from ipam.choices import *
 | 
						|
from ipam.constants import *
 | 
						|
from ipam.querysets import VLANQuerySet
 | 
						|
from netbox.models import OrganizationalModel, PrimaryModel
 | 
						|
from utilities.querysets import RestrictedQuerySet
 | 
						|
from virtualization.models import VMInterface
 | 
						|
 | 
						|
 | 
						|
__all__ = (
 | 
						|
    'VLAN',
 | 
						|
    'VLANGroup',
 | 
						|
)
 | 
						|
 | 
						|
 | 
						|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
 | 
						|
class VLANGroup(OrganizationalModel):
 | 
						|
    """
 | 
						|
    A VLAN group is an arbitrary collection of VLANs within which VLAN IDs and names must be unique.
 | 
						|
    """
 | 
						|
    name = models.CharField(
 | 
						|
        max_length=100
 | 
						|
    )
 | 
						|
    slug = models.SlugField(
 | 
						|
        max_length=100
 | 
						|
    )
 | 
						|
    scope_type = models.ForeignKey(
 | 
						|
        to=ContentType,
 | 
						|
        on_delete=models.CASCADE,
 | 
						|
        limit_choices_to=Q(model__in=VLANGROUP_SCOPE_TYPES),
 | 
						|
        blank=True,
 | 
						|
        null=True
 | 
						|
    )
 | 
						|
    scope_id = models.PositiveBigIntegerField(
 | 
						|
        blank=True,
 | 
						|
        null=True
 | 
						|
    )
 | 
						|
    scope = GenericForeignKey(
 | 
						|
        ct_field='scope_type',
 | 
						|
        fk_field='scope_id'
 | 
						|
    )
 | 
						|
    description = models.CharField(
 | 
						|
        max_length=200,
 | 
						|
        blank=True
 | 
						|
    )
 | 
						|
 | 
						|
    objects = RestrictedQuerySet.as_manager()
 | 
						|
 | 
						|
    class Meta:
 | 
						|
        ordering = ('name', 'pk')  # Name may be non-unique
 | 
						|
        unique_together = [
 | 
						|
            ['scope_type', 'scope_id', 'name'],
 | 
						|
            ['scope_type', 'scope_id', 'slug'],
 | 
						|
        ]
 | 
						|
        verbose_name = 'VLAN group'
 | 
						|
        verbose_name_plural = 'VLAN groups'
 | 
						|
 | 
						|
    def __str__(self):
 | 
						|
        return self.name
 | 
						|
 | 
						|
    def get_absolute_url(self):
 | 
						|
        return reverse('ipam:vlangroup', args=[self.pk])
 | 
						|
 | 
						|
    def clean(self):
 | 
						|
        super().clean()
 | 
						|
 | 
						|
        # Validate scope assignment
 | 
						|
        if self.scope_type and not self.scope_id:
 | 
						|
            raise ValidationError("Cannot set scope_type without scope_id.")
 | 
						|
        if self.scope_id and not self.scope_type:
 | 
						|
            raise ValidationError("Cannot set scope_id without scope_type.")
 | 
						|
 | 
						|
    def get_next_available_vid(self):
 | 
						|
        """
 | 
						|
        Return the first available VLAN ID (1-4094) in the group.
 | 
						|
        """
 | 
						|
        vlan_ids = VLAN.objects.filter(group=self).values_list('vid', flat=True)
 | 
						|
        for i in range(1, 4095):
 | 
						|
            if i not in vlan_ids:
 | 
						|
                return i
 | 
						|
        return None
 | 
						|
 | 
						|
 | 
						|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
 | 
						|
class VLAN(PrimaryModel):
 | 
						|
    """
 | 
						|
    A VLAN is a distinct layer two forwarding domain identified by a 12-bit integer (1-4094). Each VLAN must be assigned
 | 
						|
    to a Site, however VLAN IDs need not be unique within a Site. A VLAN may optionally be assigned to a VLANGroup,
 | 
						|
    within which all VLAN IDs and names but be unique.
 | 
						|
 | 
						|
    Like Prefixes, each VLAN is assigned an operational status and optionally a user-defined Role. A VLAN can have zero
 | 
						|
    or more Prefixes assigned to it.
 | 
						|
    """
 | 
						|
    site = models.ForeignKey(
 | 
						|
        to='dcim.Site',
 | 
						|
        on_delete=models.PROTECT,
 | 
						|
        related_name='vlans',
 | 
						|
        blank=True,
 | 
						|
        null=True
 | 
						|
    )
 | 
						|
    group = models.ForeignKey(
 | 
						|
        to='ipam.VLANGroup',
 | 
						|
        on_delete=models.PROTECT,
 | 
						|
        related_name='vlans',
 | 
						|
        blank=True,
 | 
						|
        null=True
 | 
						|
    )
 | 
						|
    vid = models.PositiveSmallIntegerField(
 | 
						|
        verbose_name='ID',
 | 
						|
        validators=[MinValueValidator(1), MaxValueValidator(4094)]
 | 
						|
    )
 | 
						|
    name = models.CharField(
 | 
						|
        max_length=64
 | 
						|
    )
 | 
						|
    tenant = models.ForeignKey(
 | 
						|
        to='tenancy.Tenant',
 | 
						|
        on_delete=models.PROTECT,
 | 
						|
        related_name='vlans',
 | 
						|
        blank=True,
 | 
						|
        null=True
 | 
						|
    )
 | 
						|
    status = models.CharField(
 | 
						|
        max_length=50,
 | 
						|
        choices=VLANStatusChoices,
 | 
						|
        default=VLANStatusChoices.STATUS_ACTIVE
 | 
						|
    )
 | 
						|
    role = models.ForeignKey(
 | 
						|
        to='ipam.Role',
 | 
						|
        on_delete=models.SET_NULL,
 | 
						|
        related_name='vlans',
 | 
						|
        blank=True,
 | 
						|
        null=True
 | 
						|
    )
 | 
						|
    description = models.CharField(
 | 
						|
        max_length=200,
 | 
						|
        blank=True
 | 
						|
    )
 | 
						|
 | 
						|
    objects = VLANQuerySet.as_manager()
 | 
						|
 | 
						|
    clone_fields = [
 | 
						|
        'site', 'group', 'tenant', 'status', 'role', 'description',
 | 
						|
    ]
 | 
						|
 | 
						|
    class Meta:
 | 
						|
        ordering = ('site', 'group', 'vid', 'pk')  # (site, group, vid) may be non-unique
 | 
						|
        unique_together = [
 | 
						|
            ['group', 'vid'],
 | 
						|
            ['group', 'name'],
 | 
						|
        ]
 | 
						|
        verbose_name = 'VLAN'
 | 
						|
        verbose_name_plural = 'VLANs'
 | 
						|
 | 
						|
    def __str__(self):
 | 
						|
        return f'{self.name} ({self.vid})'
 | 
						|
 | 
						|
    def get_absolute_url(self):
 | 
						|
        return reverse('ipam:vlan', args=[self.pk])
 | 
						|
 | 
						|
    def clean(self):
 | 
						|
        super().clean()
 | 
						|
 | 
						|
        # Validate VLAN group (if assigned)
 | 
						|
        if self.group and self.site and self.group.scope != self.site:
 | 
						|
            raise ValidationError({
 | 
						|
                'group': f"VLAN is assigned to group {self.group} (scope: {self.group.scope}); cannot also assign to "
 | 
						|
                         f"site {self.site}."
 | 
						|
            })
 | 
						|
 | 
						|
    def get_status_class(self):
 | 
						|
        return VLANStatusChoices.CSS_CLASSES.get(self.status)
 | 
						|
 | 
						|
    def get_interfaces(self):
 | 
						|
        # Return all device interfaces assigned to this VLAN
 | 
						|
        return Interface.objects.filter(
 | 
						|
            Q(untagged_vlan_id=self.pk) |
 | 
						|
            Q(tagged_vlans=self.pk)
 | 
						|
        ).distinct()
 | 
						|
 | 
						|
    def get_vminterfaces(self):
 | 
						|
        # Return all VM interfaces assigned to this VLAN
 | 
						|
        return VMInterface.objects.filter(
 | 
						|
            Q(untagged_vlan_id=self.pk) |
 | 
						|
            Q(tagged_vlans=self.pk)
 | 
						|
        ).distinct()
 |