|
|
@@ -46,6 +46,24 @@ class VLANGroup(OrganizationalModel):
|
|
|
ct_field='scope_type',
|
|
|
fk_field='scope_id'
|
|
|
)
|
|
|
+ min_vid = models.PositiveSmallIntegerField(
|
|
|
+ verbose_name='Minimum VLAN ID',
|
|
|
+ default=VLAN_VID_MIN,
|
|
|
+ validators=(
|
|
|
+ MinValueValidator(VLAN_VID_MIN),
|
|
|
+ MaxValueValidator(VLAN_VID_MAX)
|
|
|
+ ),
|
|
|
+ help_text='Lowest permissible ID of a child VLAN'
|
|
|
+ )
|
|
|
+ max_vid = models.PositiveSmallIntegerField(
|
|
|
+ verbose_name='Maximum VLAN ID',
|
|
|
+ default=VLAN_VID_MAX,
|
|
|
+ validators=(
|
|
|
+ MinValueValidator(VLAN_VID_MIN),
|
|
|
+ MaxValueValidator(VLAN_VID_MAX)
|
|
|
+ ),
|
|
|
+ help_text='Highest permissible ID of a child VLAN'
|
|
|
+ )
|
|
|
description = models.CharField(
|
|
|
max_length=200,
|
|
|
blank=True
|
|
|
@@ -75,24 +93,28 @@ class VLANGroup(OrganizationalModel):
|
|
|
if self.scope_id and not self.scope_type:
|
|
|
raise ValidationError("Cannot set scope_id without scope_type.")
|
|
|
|
|
|
+ # Validate min/max child VID limits
|
|
|
+ if self.max_vid < self.min_vid:
|
|
|
+ raise ValidationError({
|
|
|
+ 'max_vid': "Maximum child VID must be greater than or equal to minimum child VID"
|
|
|
+ })
|
|
|
+
|
|
|
def get_available_vids(self):
|
|
|
"""
|
|
|
Return all available VLANs within this group.
|
|
|
"""
|
|
|
- available_vlans = {vid for vid in range(VLAN_VID_MIN, VLAN_VID_MAX + 1)}
|
|
|
+ available_vlans = {vid for vid in range(self.min_vid, self.max_vid + 1)}
|
|
|
available_vlans -= set(VLAN.objects.filter(group=self).values_list('vid', flat=True))
|
|
|
|
|
|
- # TODO: Check ordering
|
|
|
- return list(available_vlans)
|
|
|
+ return sorted(available_vlans)
|
|
|
|
|
|
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
|
|
|
+ available_vids = self.get_available_vids()
|
|
|
+ if available_vids:
|
|
|
+ return available_vids[0]
|
|
|
return None
|
|
|
|
|
|
|
|
|
@@ -122,7 +144,10 @@ class VLAN(PrimaryModel):
|
|
|
)
|
|
|
vid = models.PositiveSmallIntegerField(
|
|
|
verbose_name='ID',
|
|
|
- validators=[MinValueValidator(1), MaxValueValidator(4094)]
|
|
|
+ validators=(
|
|
|
+ MinValueValidator(VLAN_VID_MIN),
|
|
|
+ MaxValueValidator(VLAN_VID_MAX)
|
|
|
+ )
|
|
|
)
|
|
|
name = models.CharField(
|
|
|
max_length=64
|
|
|
@@ -182,6 +207,13 @@ class VLAN(PrimaryModel):
|
|
|
f"site {self.site}."
|
|
|
})
|
|
|
|
|
|
+ # Validate group min/max VIDs
|
|
|
+ if self.group and not self.group.min_vid <= self.vid <= self.group.max_vid:
|
|
|
+ raise ValidationError({
|
|
|
+ 'vid': f"VID must be between {self.group.min_vid} and {self.group.max_vid} for VLANs in group "
|
|
|
+ f"{self.group}"
|
|
|
+ })
|
|
|
+
|
|
|
def get_status_class(self):
|
|
|
return VLANStatusChoices.colors.get(self.status, 'secondary')
|
|
|
|