mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-25 08:46:10 -06:00
13132 add gettext_lazy to models
This commit is contained in:
parent
9f2ae5ffb2
commit
90c9e71682
@ -1,7 +1,7 @@
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from ipam.fields import ASNField
|
from ipam.fields import ASNField
|
||||||
from ipam.querysets import ASNRangeQuerySet
|
from ipam.querysets import ASNRangeQuerySet
|
||||||
@ -15,10 +15,12 @@ __all__ = (
|
|||||||
|
|
||||||
class ASNRange(OrganizationalModel):
|
class ASNRange(OrganizationalModel):
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
|
verbose_name=_('name'),
|
||||||
max_length=100,
|
max_length=100,
|
||||||
unique=True
|
unique=True
|
||||||
)
|
)
|
||||||
slug = models.SlugField(
|
slug = models.SlugField(
|
||||||
|
verbose_name=_('slug'),
|
||||||
max_length=100,
|
max_length=100,
|
||||||
unique=True
|
unique=True
|
||||||
)
|
)
|
||||||
@ -26,10 +28,14 @@ class ASNRange(OrganizationalModel):
|
|||||||
to='ipam.RIR',
|
to='ipam.RIR',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
related_name='asn_ranges',
|
related_name='asn_ranges',
|
||||||
verbose_name='RIR'
|
verbose_name=_('RIR')
|
||||||
|
)
|
||||||
|
start = ASNField(
|
||||||
|
verbose_name=_('start'),
|
||||||
|
)
|
||||||
|
end = ASNField(
|
||||||
|
verbose_name=_('end'),
|
||||||
)
|
)
|
||||||
start = ASNField()
|
|
||||||
end = ASNField()
|
|
||||||
tenant = models.ForeignKey(
|
tenant = models.ForeignKey(
|
||||||
to='tenancy.Tenant',
|
to='tenancy.Tenant',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
@ -62,7 +68,7 @@ class ASNRange(OrganizationalModel):
|
|||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
if self.end <= self.start:
|
if self.end <= self.start:
|
||||||
raise ValidationError(f"Starting ASN ({self.start}) must be lower than ending ASN ({self.end}).")
|
raise ValidationError(_("Starting ASN ({start}) must be lower than ending ASN ({end}).").format(start=self.start, end=self.end))
|
||||||
|
|
||||||
def get_child_asns(self):
|
def get_child_asns(self):
|
||||||
return ASN.objects.filter(
|
return ASN.objects.filter(
|
||||||
@ -90,12 +96,12 @@ class ASN(PrimaryModel):
|
|||||||
to='ipam.RIR',
|
to='ipam.RIR',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
related_name='asns',
|
related_name='asns',
|
||||||
verbose_name='RIR',
|
verbose_name=_('RIR'),
|
||||||
help_text=_("Regional Internet Registry responsible for this AS number space")
|
help_text=_("Regional Internet Registry responsible for this AS number space")
|
||||||
)
|
)
|
||||||
asn = ASNField(
|
asn = ASNField(
|
||||||
unique=True,
|
unique=True,
|
||||||
verbose_name='ASN',
|
verbose_name=_('ASN'),
|
||||||
help_text=_('16- or 32-bit autonomous system number')
|
help_text=_('16- or 32-bit autonomous system number')
|
||||||
)
|
)
|
||||||
tenant = models.ForeignKey(
|
tenant = models.ForeignKey(
|
||||||
|
@ -3,6 +3,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from netbox.models import ChangeLoggedModel, PrimaryModel
|
from netbox.models import ChangeLoggedModel, PrimaryModel
|
||||||
from ipam.choices import *
|
from ipam.choices import *
|
||||||
@ -19,13 +20,15 @@ class FHRPGroup(PrimaryModel):
|
|||||||
A grouping of next hope resolution protocol (FHRP) peers. (For instance, VRRP or HSRP.)
|
A grouping of next hope resolution protocol (FHRP) peers. (For instance, VRRP or HSRP.)
|
||||||
"""
|
"""
|
||||||
group_id = models.PositiveSmallIntegerField(
|
group_id = models.PositiveSmallIntegerField(
|
||||||
verbose_name='Group ID'
|
verbose_name=_('Group ID')
|
||||||
)
|
)
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
|
verbose_name=_('name'),
|
||||||
max_length=100,
|
max_length=100,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
protocol = models.CharField(
|
protocol = models.CharField(
|
||||||
|
verbose_name=_('protocol'),
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=FHRPGroupProtocolChoices
|
choices=FHRPGroupProtocolChoices
|
||||||
)
|
)
|
||||||
@ -33,12 +36,12 @@ class FHRPGroup(PrimaryModel):
|
|||||||
max_length=50,
|
max_length=50,
|
||||||
choices=FHRPGroupAuthTypeChoices,
|
choices=FHRPGroupAuthTypeChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name='Authentication type'
|
verbose_name=_('Authentication type')
|
||||||
)
|
)
|
||||||
auth_key = models.CharField(
|
auth_key = models.CharField(
|
||||||
max_length=255,
|
max_length=255,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name='Authentication key'
|
verbose_name=_('Authentication key')
|
||||||
)
|
)
|
||||||
ip_addresses = GenericRelation(
|
ip_addresses = GenericRelation(
|
||||||
to='ipam.IPAddress',
|
to='ipam.IPAddress',
|
||||||
@ -87,6 +90,7 @@ class FHRPGroupAssignment(ChangeLoggedModel):
|
|||||||
on_delete=models.CASCADE
|
on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
priority = models.PositiveSmallIntegerField(
|
priority = models.PositiveSmallIntegerField(
|
||||||
|
verbose_name=_('priority'),
|
||||||
validators=(
|
validators=(
|
||||||
MinValueValidator(FHRPGROUPASSIGNMENT_PRIORITY_MIN),
|
MinValueValidator(FHRPGROUPASSIGNMENT_PRIORITY_MIN),
|
||||||
MaxValueValidator(FHRPGROUPASSIGNMENT_PRIORITY_MAX)
|
MaxValueValidator(FHRPGROUPASSIGNMENT_PRIORITY_MAX)
|
||||||
@ -103,7 +107,7 @@ class FHRPGroupAssignment(ChangeLoggedModel):
|
|||||||
name='%(app_label)s_%(class)s_unique_interface_group'
|
name='%(app_label)s_%(class)s_unique_interface_group'
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
verbose_name = 'FHRP group assignment'
|
verbose_name = _('FHRP group assignment')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.interface}: {self.group} ({self.priority})'
|
return f'{self.interface}: {self.group} ({self.priority})'
|
||||||
|
@ -6,7 +6,7 @@ from django.db import models
|
|||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from ipam.choices import *
|
from ipam.choices import *
|
||||||
from ipam.constants import *
|
from ipam.constants import *
|
||||||
@ -59,14 +59,14 @@ class RIR(OrganizationalModel):
|
|||||||
"""
|
"""
|
||||||
is_private = models.BooleanField(
|
is_private = models.BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
verbose_name='Private',
|
verbose_name=_('Private'),
|
||||||
help_text=_('IP space managed by this RIR is considered private')
|
help_text=_('IP space managed by this RIR is considered private')
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('name',)
|
ordering = ('name',)
|
||||||
verbose_name = 'RIR'
|
verbose_name = _('RIR')
|
||||||
verbose_name_plural = 'RIRs'
|
verbose_name_plural = _('RIRs')
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('ipam:rir', args=[self.pk])
|
return reverse('ipam:rir', args=[self.pk])
|
||||||
@ -84,7 +84,7 @@ class Aggregate(GetAvailablePrefixesMixin, PrimaryModel):
|
|||||||
to='ipam.RIR',
|
to='ipam.RIR',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
related_name='aggregates',
|
related_name='aggregates',
|
||||||
verbose_name='RIR',
|
verbose_name=_('RIR'),
|
||||||
help_text=_("Regional Internet Registry responsible for this IP space")
|
help_text=_("Regional Internet Registry responsible for this IP space")
|
||||||
)
|
)
|
||||||
tenant = models.ForeignKey(
|
tenant = models.ForeignKey(
|
||||||
@ -95,6 +95,7 @@ class Aggregate(GetAvailablePrefixesMixin, PrimaryModel):
|
|||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
date_added = models.DateField(
|
date_added = models.DateField(
|
||||||
|
verbose_name=_('date added'),
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
@ -123,7 +124,7 @@ class Aggregate(GetAvailablePrefixesMixin, PrimaryModel):
|
|||||||
# /0 masks are not acceptable
|
# /0 masks are not acceptable
|
||||||
if self.prefix.prefixlen == 0:
|
if self.prefix.prefixlen == 0:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'prefix': "Cannot create aggregate with /0 mask."
|
'prefix': _("Cannot create aggregate with /0 mask.")
|
||||||
})
|
})
|
||||||
|
|
||||||
# Ensure that the aggregate being added is not covered by an existing aggregate
|
# Ensure that the aggregate being added is not covered by an existing aggregate
|
||||||
@ -134,7 +135,7 @@ class Aggregate(GetAvailablePrefixesMixin, PrimaryModel):
|
|||||||
covering_aggregates = covering_aggregates.exclude(pk=self.pk)
|
covering_aggregates = covering_aggregates.exclude(pk=self.pk)
|
||||||
if covering_aggregates:
|
if covering_aggregates:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'prefix': "Aggregates cannot overlap. {} is already covered by an existing aggregate ({}).".format(
|
'prefix': _("Aggregates cannot overlap. {} is already covered by an existing aggregate ({}).").format(
|
||||||
self.prefix, covering_aggregates[0]
|
self.prefix, covering_aggregates[0]
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -145,7 +146,7 @@ class Aggregate(GetAvailablePrefixesMixin, PrimaryModel):
|
|||||||
covered_aggregates = covered_aggregates.exclude(pk=self.pk)
|
covered_aggregates = covered_aggregates.exclude(pk=self.pk)
|
||||||
if covered_aggregates:
|
if covered_aggregates:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'prefix': "Aggregates cannot overlap. {} covers an existing aggregate ({}).".format(
|
'prefix': _("Aggregates cannot overlap. {} covers an existing aggregate ({}).").format(
|
||||||
self.prefix, covered_aggregates[0]
|
self.prefix, covered_aggregates[0]
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -179,6 +180,7 @@ class Role(OrganizationalModel):
|
|||||||
"Management."
|
"Management."
|
||||||
"""
|
"""
|
||||||
weight = models.PositiveSmallIntegerField(
|
weight = models.PositiveSmallIntegerField(
|
||||||
|
verbose_name=_('weight'),
|
||||||
default=1000
|
default=1000
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -199,6 +201,7 @@ class Prefix(GetAvailablePrefixesMixin, PrimaryModel):
|
|||||||
assigned to a VLAN where appropriate.
|
assigned to a VLAN where appropriate.
|
||||||
"""
|
"""
|
||||||
prefix = IPNetworkField(
|
prefix = IPNetworkField(
|
||||||
|
verbose_name=_('prefix'),
|
||||||
help_text=_('IPv4 or IPv6 network with mask')
|
help_text=_('IPv4 or IPv6 network with mask')
|
||||||
)
|
)
|
||||||
site = models.ForeignKey(
|
site = models.ForeignKey(
|
||||||
@ -235,7 +238,7 @@ class Prefix(GetAvailablePrefixesMixin, PrimaryModel):
|
|||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PrefixStatusChoices,
|
choices=PrefixStatusChoices,
|
||||||
default=PrefixStatusChoices.STATUS_ACTIVE,
|
default=PrefixStatusChoices.STATUS_ACTIVE,
|
||||||
verbose_name='Status',
|
verbose_name=_('Status'),
|
||||||
help_text=_('Operational status of this prefix')
|
help_text=_('Operational status of this prefix')
|
||||||
)
|
)
|
||||||
role = models.ForeignKey(
|
role = models.ForeignKey(
|
||||||
@ -247,11 +250,12 @@ class Prefix(GetAvailablePrefixesMixin, PrimaryModel):
|
|||||||
help_text=_('The primary function of this prefix')
|
help_text=_('The primary function of this prefix')
|
||||||
)
|
)
|
||||||
is_pool = models.BooleanField(
|
is_pool = models.BooleanField(
|
||||||
verbose_name='Is a pool',
|
verbose_name=_('Is a pool'),
|
||||||
default=False,
|
default=False,
|
||||||
help_text=_('All IP addresses within this prefix are considered usable')
|
help_text=_('All IP addresses within this prefix are considered usable')
|
||||||
)
|
)
|
||||||
mark_utilized = models.BooleanField(
|
mark_utilized = models.BooleanField(
|
||||||
|
verbose_name=_('mark utilized'),
|
||||||
default=False,
|
default=False,
|
||||||
help_text=_("Treat as 100% utilized")
|
help_text=_("Treat as 100% utilized")
|
||||||
)
|
)
|
||||||
@ -297,7 +301,7 @@ class Prefix(GetAvailablePrefixesMixin, PrimaryModel):
|
|||||||
# /0 masks are not acceptable
|
# /0 masks are not acceptable
|
||||||
if self.prefix.prefixlen == 0:
|
if self.prefix.prefixlen == 0:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'prefix': "Cannot create prefix with /0 mask."
|
'prefix': _("Cannot create prefix with /0 mask.")
|
||||||
})
|
})
|
||||||
|
|
||||||
# Enforce unique IP space (if applicable)
|
# Enforce unique IP space (if applicable)
|
||||||
@ -305,8 +309,8 @@ class Prefix(GetAvailablePrefixesMixin, PrimaryModel):
|
|||||||
duplicate_prefixes = self.get_duplicates()
|
duplicate_prefixes = self.get_duplicates()
|
||||||
if duplicate_prefixes:
|
if duplicate_prefixes:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'prefix': "Duplicate prefix found in {}: {}".format(
|
'prefix': _("Duplicate prefix found in {}: {}".format(
|
||||||
"VRF {}".format(self.vrf) if self.vrf else "global table",
|
"VRF {}").format(self.vrf) if self.vrf else _("global table"),
|
||||||
duplicate_prefixes.first(),
|
duplicate_prefixes.first(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -474,12 +478,15 @@ class IPRange(PrimaryModel):
|
|||||||
A range of IP addresses, defined by start and end addresses.
|
A range of IP addresses, defined by start and end addresses.
|
||||||
"""
|
"""
|
||||||
start_address = IPAddressField(
|
start_address = IPAddressField(
|
||||||
|
verbose_name=_('start address'),
|
||||||
help_text=_('IPv4 or IPv6 address (with mask)')
|
help_text=_('IPv4 or IPv6 address (with mask)')
|
||||||
)
|
)
|
||||||
end_address = IPAddressField(
|
end_address = IPAddressField(
|
||||||
|
verbose_name=_('end address'),
|
||||||
help_text=_('IPv4 or IPv6 address (with mask)')
|
help_text=_('IPv4 or IPv6 address (with mask)')
|
||||||
)
|
)
|
||||||
size = models.PositiveIntegerField(
|
size = models.PositiveIntegerField(
|
||||||
|
verbose_name=_('size'),
|
||||||
editable=False
|
editable=False
|
||||||
)
|
)
|
||||||
vrf = models.ForeignKey(
|
vrf = models.ForeignKey(
|
||||||
@ -488,7 +495,7 @@ class IPRange(PrimaryModel):
|
|||||||
related_name='ip_ranges',
|
related_name='ip_ranges',
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name='VRF'
|
verbose_name=_('VRF')
|
||||||
)
|
)
|
||||||
tenant = models.ForeignKey(
|
tenant = models.ForeignKey(
|
||||||
to='tenancy.Tenant',
|
to='tenancy.Tenant',
|
||||||
@ -498,6 +505,7 @@ class IPRange(PrimaryModel):
|
|||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
|
verbose_name=_('status'),
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=IPRangeStatusChoices,
|
choices=IPRangeStatusChoices,
|
||||||
default=IPRangeStatusChoices.STATUS_ACTIVE,
|
default=IPRangeStatusChoices.STATUS_ACTIVE,
|
||||||
@ -512,6 +520,7 @@ class IPRange(PrimaryModel):
|
|||||||
help_text=_('The primary function of this range')
|
help_text=_('The primary function of this range')
|
||||||
)
|
)
|
||||||
mark_utilized = models.BooleanField(
|
mark_utilized = models.BooleanField(
|
||||||
|
verbose_name=_('mark utilized'),
|
||||||
default=False,
|
default=False,
|
||||||
help_text=_("Treat as 100% utilized")
|
help_text=_("Treat as 100% utilized")
|
||||||
)
|
)
|
||||||
@ -539,21 +548,23 @@ class IPRange(PrimaryModel):
|
|||||||
# Check that start & end IP versions match
|
# Check that start & end IP versions match
|
||||||
if self.start_address.version != self.end_address.version:
|
if self.start_address.version != self.end_address.version:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'end_address': f"Ending address version (IPv{self.end_address.version}) does not match starting "
|
'end_address': _("Ending address version (IPv{end_address_version}) does not match starting "
|
||||||
f"address (IPv{self.start_address.version})"
|
"address (IPv{start_address_version})").format(
|
||||||
|
end_address_version=self.end_address.version, start_address_version=self.start_address.version)
|
||||||
})
|
})
|
||||||
|
|
||||||
# Check that the start & end IP prefix lengths match
|
# Check that the start & end IP prefix lengths match
|
||||||
if self.start_address.prefixlen != self.end_address.prefixlen:
|
if self.start_address.prefixlen != self.end_address.prefixlen:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'end_address': f"Ending address mask (/{self.end_address.prefixlen}) does not match starting "
|
'end_address': _("Ending address mask (/{end_address_prefixlen}) does not match starting "
|
||||||
f"address mask (/{self.start_address.prefixlen})"
|
"address mask (/{start_address_prefixlen})").format(
|
||||||
|
end_address_prefixlen=self.end_address.prefixlen, start_address_prefixlen=self.start_address.prefixlen)
|
||||||
})
|
})
|
||||||
|
|
||||||
# Check that the ending address is greater than the starting address
|
# Check that the ending address is greater than the starting address
|
||||||
if not self.end_address > self.start_address:
|
if not self.end_address > self.start_address:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'end_address': f"Ending address must be lower than the starting address ({self.start_address})"
|
'end_address': _("Ending address must be lower than the starting address ({start_address})").format(start_address=self.start_address)
|
||||||
})
|
})
|
||||||
|
|
||||||
# Check for overlapping ranges
|
# Check for overlapping ranges
|
||||||
@ -563,12 +574,13 @@ class IPRange(PrimaryModel):
|
|||||||
Q(start_address__lte=self.start_address, end_address__gte=self.end_address) # Starts & ends outside
|
Q(start_address__lte=self.start_address, end_address__gte=self.end_address) # Starts & ends outside
|
||||||
).first()
|
).first()
|
||||||
if overlapping_range:
|
if overlapping_range:
|
||||||
raise ValidationError(f"Defined addresses overlap with range {overlapping_range} in VRF {self.vrf}")
|
raise ValidationError(_("Defined addresses overlap with range {overlapping_range} in VRF {vrf}").format(
|
||||||
|
overlapping_range=overlapping_range, vrf=self.vrf))
|
||||||
|
|
||||||
# Validate maximum size
|
# Validate maximum size
|
||||||
MAX_SIZE = 2 ** 32 - 1
|
MAX_SIZE = 2 ** 32 - 1
|
||||||
if int(self.end_address.ip - self.start_address.ip) + 1 > MAX_SIZE:
|
if int(self.end_address.ip - self.start_address.ip) + 1 > MAX_SIZE:
|
||||||
raise ValidationError(f"Defined range exceeds maximum supported size ({MAX_SIZE})")
|
raise ValidationError(_("Defined range exceeds maximum supported size ({max_size})").format(max_size=MAX_SIZE))
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|
||||||
@ -679,6 +691,7 @@ class IPAddress(PrimaryModel):
|
|||||||
which has a NAT outside IP, that Interface's Device can use either the inside or outside IP as its primary IP.
|
which has a NAT outside IP, that Interface's Device can use either the inside or outside IP as its primary IP.
|
||||||
"""
|
"""
|
||||||
address = IPAddressField(
|
address = IPAddressField(
|
||||||
|
verbose_name=_('address'),
|
||||||
help_text=_('IPv4 or IPv6 address (with mask)')
|
help_text=_('IPv4 or IPv6 address (with mask)')
|
||||||
)
|
)
|
||||||
vrf = models.ForeignKey(
|
vrf = models.ForeignKey(
|
||||||
@ -687,7 +700,7 @@ class IPAddress(PrimaryModel):
|
|||||||
related_name='ip_addresses',
|
related_name='ip_addresses',
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name='VRF'
|
verbose_name=_('VRF')
|
||||||
)
|
)
|
||||||
tenant = models.ForeignKey(
|
tenant = models.ForeignKey(
|
||||||
to='tenancy.Tenant',
|
to='tenancy.Tenant',
|
||||||
@ -697,12 +710,14 @@ class IPAddress(PrimaryModel):
|
|||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
|
verbose_name=_('status'),
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=IPAddressStatusChoices,
|
choices=IPAddressStatusChoices,
|
||||||
default=IPAddressStatusChoices.STATUS_ACTIVE,
|
default=IPAddressStatusChoices.STATUS_ACTIVE,
|
||||||
help_text=_('The operational status of this IP')
|
help_text=_('The operational status of this IP')
|
||||||
)
|
)
|
||||||
role = models.CharField(
|
role = models.CharField(
|
||||||
|
verbose_name=_('role'),
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=IPAddressRoleChoices,
|
choices=IPAddressRoleChoices,
|
||||||
blank=True,
|
blank=True,
|
||||||
@ -730,14 +745,14 @@ class IPAddress(PrimaryModel):
|
|||||||
related_name='nat_outside',
|
related_name='nat_outside',
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name='NAT (Inside)',
|
verbose_name=_('NAT (Inside)'),
|
||||||
help_text=_('The IP for which this address is the "outside" IP')
|
help_text=_('The IP for which this address is the "outside" IP')
|
||||||
)
|
)
|
||||||
dns_name = models.CharField(
|
dns_name = models.CharField(
|
||||||
max_length=255,
|
max_length=255,
|
||||||
blank=True,
|
blank=True,
|
||||||
validators=[DNSValidator],
|
validators=[DNSValidator],
|
||||||
verbose_name='DNS Name',
|
verbose_name=_('DNS Name'),
|
||||||
help_text=_('Hostname or FQDN (not case-sensitive)')
|
help_text=_('Hostname or FQDN (not case-sensitive)')
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -799,7 +814,7 @@ class IPAddress(PrimaryModel):
|
|||||||
# /0 masks are not acceptable
|
# /0 masks are not acceptable
|
||||||
if self.address.prefixlen == 0:
|
if self.address.prefixlen == 0:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'address': "Cannot create IP address with /0 mask."
|
'address': _("Cannot create IP address with /0 mask.")
|
||||||
})
|
})
|
||||||
|
|
||||||
# Enforce unique IP space (if applicable)
|
# Enforce unique IP space (if applicable)
|
||||||
@ -810,8 +825,8 @@ class IPAddress(PrimaryModel):
|
|||||||
any(dip.role not in IPADDRESS_ROLES_NONUNIQUE for dip in duplicate_ips)
|
any(dip.role not in IPADDRESS_ROLES_NONUNIQUE for dip in duplicate_ips)
|
||||||
):
|
):
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'address': "Duplicate IP address found in {}: {}".format(
|
'address': _("Duplicate IP address found in {}: {}".format(
|
||||||
"VRF {}".format(self.vrf) if self.vrf else "global table",
|
"VRF {}").format(self.vrf) if self.vrf else _("global table"),
|
||||||
duplicate_ips.first(),
|
duplicate_ips.first(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -819,7 +834,7 @@ class IPAddress(PrimaryModel):
|
|||||||
# Validate IP status selection
|
# Validate IP status selection
|
||||||
if self.status == IPAddressStatusChoices.STATUS_SLAAC and self.family != 6:
|
if self.status == IPAddressStatusChoices.STATUS_SLAAC and self.family != 6:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'status': "Only IPv6 addresses can be assigned SLAAC status"
|
'status': _("Only IPv6 addresses can be assigned SLAAC status")
|
||||||
})
|
})
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
@ -4,6 +4,7 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from ipam.choices import L2VPNTypeChoices
|
from ipam.choices import L2VPNTypeChoices
|
||||||
from ipam.constants import L2VPN_ASSIGNMENT_MODELS
|
from ipam.constants import L2VPN_ASSIGNMENT_MODELS
|
||||||
@ -17,18 +18,22 @@ __all__ = (
|
|||||||
|
|
||||||
class L2VPN(PrimaryModel):
|
class L2VPN(PrimaryModel):
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
|
verbose_name=_('name'),
|
||||||
max_length=100,
|
max_length=100,
|
||||||
unique=True
|
unique=True
|
||||||
)
|
)
|
||||||
slug = models.SlugField(
|
slug = models.SlugField(
|
||||||
|
verbose_name=_('slug'),
|
||||||
max_length=100,
|
max_length=100,
|
||||||
unique=True
|
unique=True
|
||||||
)
|
)
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
|
verbose_name=_('type'),
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=L2VPNTypeChoices
|
choices=L2VPNTypeChoices
|
||||||
)
|
)
|
||||||
identifier = models.BigIntegerField(
|
identifier = models.BigIntegerField(
|
||||||
|
verbose_name=_('identifier'),
|
||||||
null=True,
|
null=True,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
@ -123,7 +128,7 @@ class L2VPNTermination(NetBoxModel):
|
|||||||
obj_type = ContentType.objects.get_for_model(self.assigned_object)
|
obj_type = ContentType.objects.get_for_model(self.assigned_object)
|
||||||
if L2VPNTermination.objects.filter(assigned_object_id=obj_id, assigned_object_type=obj_type).\
|
if L2VPNTermination.objects.filter(assigned_object_id=obj_id, assigned_object_type=obj_type).\
|
||||||
exclude(pk=self.pk).count() > 0:
|
exclude(pk=self.pk).count() > 0:
|
||||||
raise ValidationError(f'L2VPN Termination already assigned ({self.assigned_object})')
|
raise ValidationError(_('L2VPN Termination already assigned ({assigned_object})').format(assigned_object=self.assigned_object))
|
||||||
|
|
||||||
# Only check if L2VPN is set and is of type P2P
|
# Only check if L2VPN is set and is of type P2P
|
||||||
if hasattr(self, 'l2vpn') and self.l2vpn.type in L2VPNTypeChoices.P2P:
|
if hasattr(self, 'l2vpn') and self.l2vpn.type in L2VPNTypeChoices.P2P:
|
||||||
@ -131,8 +136,8 @@ class L2VPNTermination(NetBoxModel):
|
|||||||
if terminations_count >= 2:
|
if terminations_count >= 2:
|
||||||
l2vpn_type = self.l2vpn.get_type_display()
|
l2vpn_type = self.l2vpn.get_type_display()
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
f'{l2vpn_type} L2VPNs cannot have more than two terminations; found {terminations_count} already '
|
_('{l2vpn_type} L2VPNs cannot have more than two terminations; found {terminations_count} already '
|
||||||
f'defined.'
|
'defined.').format(l2vpn_type=l2vpn_type, terminations_count=terminations_count)
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -3,7 +3,7 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from ipam.choices import *
|
from ipam.choices import *
|
||||||
from ipam.constants import *
|
from ipam.constants import *
|
||||||
@ -19,6 +19,7 @@ __all__ = (
|
|||||||
|
|
||||||
class ServiceBase(models.Model):
|
class ServiceBase(models.Model):
|
||||||
protocol = models.CharField(
|
protocol = models.CharField(
|
||||||
|
verbose_name=_('protocol'),
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=ServiceProtocolChoices
|
choices=ServiceProtocolChoices
|
||||||
)
|
)
|
||||||
@ -29,7 +30,7 @@ class ServiceBase(models.Model):
|
|||||||
MaxValueValidator(SERVICE_PORT_MAX)
|
MaxValueValidator(SERVICE_PORT_MAX)
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
verbose_name='Port numbers'
|
verbose_name=_('Port numbers')
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -48,6 +49,7 @@ class ServiceTemplate(ServiceBase, PrimaryModel):
|
|||||||
A template for a Service to be applied to a device or virtual machine.
|
A template for a Service to be applied to a device or virtual machine.
|
||||||
"""
|
"""
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
|
verbose_name=_('name'),
|
||||||
max_length=100,
|
max_length=100,
|
||||||
unique=True
|
unique=True
|
||||||
)
|
)
|
||||||
@ -68,7 +70,7 @@ class Service(ServiceBase, PrimaryModel):
|
|||||||
to='dcim.Device',
|
to='dcim.Device',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name='services',
|
related_name='services',
|
||||||
verbose_name='device',
|
verbose_name=_('device'),
|
||||||
null=True,
|
null=True,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
@ -86,7 +88,7 @@ class Service(ServiceBase, PrimaryModel):
|
|||||||
to='ipam.IPAddress',
|
to='ipam.IPAddress',
|
||||||
related_name='services',
|
related_name='services',
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name='IP addresses',
|
verbose_name=_('IP addresses'),
|
||||||
help_text=_("The specific IP addresses (if any) to which this service is bound")
|
help_text=_("The specific IP addresses (if any) to which this service is bound")
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -107,6 +109,6 @@ class Service(ServiceBase, PrimaryModel):
|
|||||||
|
|
||||||
# A Service must belong to a Device *or* to a VirtualMachine
|
# A Service must belong to a Device *or* to a VirtualMachine
|
||||||
if self.device and self.virtual_machine:
|
if self.device and self.virtual_machine:
|
||||||
raise ValidationError("A service cannot be associated with both a device and a virtual machine.")
|
raise ValidationError(_("A service cannot be associated with both a device and a virtual machine."))
|
||||||
if not self.device and not self.virtual_machine:
|
if not self.device and not self.virtual_machine:
|
||||||
raise ValidationError("A service must be associated with either a device or a virtual machine.")
|
raise ValidationError(_("A service must be associated with either a device or a virtual machine."))
|
||||||
|
@ -4,7 +4,7 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from dcim.models import Interface
|
from dcim.models import Interface
|
||||||
from ipam.choices import *
|
from ipam.choices import *
|
||||||
@ -24,9 +24,11 @@ class VLANGroup(OrganizationalModel):
|
|||||||
A VLAN group is an arbitrary collection of VLANs within which VLAN IDs and names must be unique.
|
A VLAN group is an arbitrary collection of VLANs within which VLAN IDs and names must be unique.
|
||||||
"""
|
"""
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
|
verbose_name=_('name'),
|
||||||
max_length=100
|
max_length=100
|
||||||
)
|
)
|
||||||
slug = models.SlugField(
|
slug = models.SlugField(
|
||||||
|
verbose_name=_('slug'),
|
||||||
max_length=100
|
max_length=100
|
||||||
)
|
)
|
||||||
scope_type = models.ForeignKey(
|
scope_type = models.ForeignKey(
|
||||||
@ -45,7 +47,7 @@ class VLANGroup(OrganizationalModel):
|
|||||||
fk_field='scope_id'
|
fk_field='scope_id'
|
||||||
)
|
)
|
||||||
min_vid = models.PositiveSmallIntegerField(
|
min_vid = models.PositiveSmallIntegerField(
|
||||||
verbose_name='Minimum VLAN ID',
|
verbose_name=_('Minimum VLAN ID'),
|
||||||
default=VLAN_VID_MIN,
|
default=VLAN_VID_MIN,
|
||||||
validators=(
|
validators=(
|
||||||
MinValueValidator(VLAN_VID_MIN),
|
MinValueValidator(VLAN_VID_MIN),
|
||||||
@ -54,7 +56,7 @@ class VLANGroup(OrganizationalModel):
|
|||||||
help_text=_('Lowest permissible ID of a child VLAN')
|
help_text=_('Lowest permissible ID of a child VLAN')
|
||||||
)
|
)
|
||||||
max_vid = models.PositiveSmallIntegerField(
|
max_vid = models.PositiveSmallIntegerField(
|
||||||
verbose_name='Maximum VLAN ID',
|
verbose_name=_('Maximum VLAN ID'),
|
||||||
default=VLAN_VID_MAX,
|
default=VLAN_VID_MAX,
|
||||||
validators=(
|
validators=(
|
||||||
MinValueValidator(VLAN_VID_MIN),
|
MinValueValidator(VLAN_VID_MIN),
|
||||||
@ -88,14 +90,14 @@ class VLANGroup(OrganizationalModel):
|
|||||||
|
|
||||||
# Validate scope assignment
|
# Validate scope assignment
|
||||||
if self.scope_type and not self.scope_id:
|
if self.scope_type and not self.scope_id:
|
||||||
raise ValidationError("Cannot set scope_type without scope_id.")
|
raise ValidationError(_("Cannot set scope_type without scope_id."))
|
||||||
if self.scope_id and not self.scope_type:
|
if self.scope_id and not self.scope_type:
|
||||||
raise ValidationError("Cannot set scope_id without scope_type.")
|
raise ValidationError(_("Cannot set scope_id without scope_type."))
|
||||||
|
|
||||||
# Validate min/max child VID limits
|
# Validate min/max child VID limits
|
||||||
if self.max_vid < self.min_vid:
|
if self.max_vid < self.min_vid:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'max_vid': "Maximum child VID must be greater than or equal to minimum child VID"
|
'max_vid': _("Maximum child VID must be greater than or equal to minimum child VID")
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_available_vids(self):
|
def get_available_vids(self):
|
||||||
@ -143,7 +145,7 @@ class VLAN(PrimaryModel):
|
|||||||
help_text=_("VLAN group (optional)")
|
help_text=_("VLAN group (optional)")
|
||||||
)
|
)
|
||||||
vid = models.PositiveSmallIntegerField(
|
vid = models.PositiveSmallIntegerField(
|
||||||
verbose_name='ID',
|
verbose_name=_('ID'),
|
||||||
validators=(
|
validators=(
|
||||||
MinValueValidator(VLAN_VID_MIN),
|
MinValueValidator(VLAN_VID_MIN),
|
||||||
MaxValueValidator(VLAN_VID_MAX)
|
MaxValueValidator(VLAN_VID_MAX)
|
||||||
@ -151,6 +153,7 @@ class VLAN(PrimaryModel):
|
|||||||
help_text=_("Numeric VLAN ID (1-4094)")
|
help_text=_("Numeric VLAN ID (1-4094)")
|
||||||
)
|
)
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
|
verbose_name=_('name'),
|
||||||
max_length=64
|
max_length=64
|
||||||
)
|
)
|
||||||
tenant = models.ForeignKey(
|
tenant = models.ForeignKey(
|
||||||
@ -161,6 +164,7 @@ class VLAN(PrimaryModel):
|
|||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
|
verbose_name=_('status'),
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=VLANStatusChoices,
|
choices=VLANStatusChoices,
|
||||||
default=VLANStatusChoices.STATUS_ACTIVE,
|
default=VLANStatusChoices.STATUS_ACTIVE,
|
||||||
@ -215,15 +219,15 @@ class VLAN(PrimaryModel):
|
|||||||
# Validate VLAN group (if assigned)
|
# Validate VLAN group (if assigned)
|
||||||
if self.group and self.site and self.group.scope != self.site:
|
if self.group and self.site and self.group.scope != self.site:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'group': f"VLAN is assigned to group {self.group} (scope: {self.group.scope}); cannot also assign to "
|
'group': _("VLAN is assigned to group {group} (scope: {scope}); cannot also assign to "
|
||||||
f"site {self.site}."
|
"site {site}.").format(group=self.group, scope=self.group.scope, site=self.site)
|
||||||
})
|
})
|
||||||
|
|
||||||
# Validate group min/max VIDs
|
# Validate group min/max VIDs
|
||||||
if self.group and not self.group.min_vid <= self.vid <= self.group.max_vid:
|
if self.group and not self.group.min_vid <= self.vid <= self.group.max_vid:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'vid': f"VID must be between {self.group.min_vid} and {self.group.max_vid} for VLANs in group "
|
'vid': _("VID must be between {min_vid} and {max_vid} for VLANs in group "
|
||||||
f"{self.group}"
|
"{group}").format(min_vid=self.group.min_vid, max_vid=self.group.max_vid, group=self.group)
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_status_color(self):
|
def get_status_color(self):
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from ipam.constants import *
|
from ipam.constants import *
|
||||||
from netbox.models import PrimaryModel
|
from netbox.models import PrimaryModel
|
||||||
@ -19,6 +19,7 @@ class VRF(PrimaryModel):
|
|||||||
are said to exist in the "global" table.)
|
are said to exist in the "global" table.)
|
||||||
"""
|
"""
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
|
verbose_name=_('name'),
|
||||||
max_length=100
|
max_length=100
|
||||||
)
|
)
|
||||||
rd = models.CharField(
|
rd = models.CharField(
|
||||||
@ -26,7 +27,7 @@ class VRF(PrimaryModel):
|
|||||||
unique=True,
|
unique=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name='Route distinguisher',
|
verbose_name=_('Route distinguisher'),
|
||||||
help_text=_('Unique route distinguisher (as defined in RFC 4364)')
|
help_text=_('Unique route distinguisher (as defined in RFC 4364)')
|
||||||
)
|
)
|
||||||
tenant = models.ForeignKey(
|
tenant = models.ForeignKey(
|
||||||
@ -38,7 +39,7 @@ class VRF(PrimaryModel):
|
|||||||
)
|
)
|
||||||
enforce_unique = models.BooleanField(
|
enforce_unique = models.BooleanField(
|
||||||
default=True,
|
default=True,
|
||||||
verbose_name='Enforce unique space',
|
verbose_name=_('Enforce unique space'),
|
||||||
help_text=_('Prevent duplicate prefixes/IP addresses within this VRF')
|
help_text=_('Prevent duplicate prefixes/IP addresses within this VRF')
|
||||||
)
|
)
|
||||||
import_targets = models.ManyToManyField(
|
import_targets = models.ManyToManyField(
|
||||||
@ -75,6 +76,7 @@ class RouteTarget(PrimaryModel):
|
|||||||
A BGP extended community used to control the redistribution of routes among VRFs, as defined in RFC 4364.
|
A BGP extended community used to control the redistribution of routes among VRFs, as defined in RFC 4364.
|
||||||
"""
|
"""
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
|
verbose_name=_('name'),
|
||||||
max_length=VRF_RD_MAX_LENGTH, # Same format options as VRF RD (RFC 4360 section 4)
|
max_length=VRF_RD_MAX_LENGTH, # Same format options as VRF RD (RFC 4360 section 4)
|
||||||
unique=True,
|
unique=True,
|
||||||
help_text=_('Route target value (formatted in accordance with RFC 4360)')
|
help_text=_('Route target value (formatted in accordance with RFC 4360)')
|
||||||
|
Loading…
Reference in New Issue
Block a user