13132 add gettext_lazy to models

This commit is contained in:
Arthur 2023-07-11 18:49:37 +07:00 committed by Jeremy Stretch
parent 9f2ae5ffb2
commit 90c9e71682
7 changed files with 100 additions and 62 deletions

View File

@ -1,7 +1,7 @@
from django.core.exceptions import ValidationError
from django.db import models
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.querysets import ASNRangeQuerySet
@ -15,10 +15,12 @@ __all__ = (
class ASNRange(OrganizationalModel):
name = models.CharField(
verbose_name=_('name'),
max_length=100,
unique=True
)
slug = models.SlugField(
verbose_name=_('slug'),
max_length=100,
unique=True
)
@ -26,10 +28,14 @@ class ASNRange(OrganizationalModel):
to='ipam.RIR',
on_delete=models.PROTECT,
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(
to='tenancy.Tenant',
on_delete=models.PROTECT,
@ -62,7 +68,7 @@ class ASNRange(OrganizationalModel):
super().clean()
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):
return ASN.objects.filter(
@ -90,12 +96,12 @@ class ASN(PrimaryModel):
to='ipam.RIR',
on_delete=models.PROTECT,
related_name='asns',
verbose_name='RIR',
verbose_name=_('RIR'),
help_text=_("Regional Internet Registry responsible for this AS number space")
)
asn = ASNField(
unique=True,
verbose_name='ASN',
verbose_name=_('ASN'),
help_text=_('16- or 32-bit autonomous system number')
)
tenant = models.ForeignKey(

View File

@ -3,6 +3,7 @@ from django.contrib.contenttypes.models import ContentType
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from netbox.models import ChangeLoggedModel, PrimaryModel
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.)
"""
group_id = models.PositiveSmallIntegerField(
verbose_name='Group ID'
verbose_name=_('Group ID')
)
name = models.CharField(
verbose_name=_('name'),
max_length=100,
blank=True
)
protocol = models.CharField(
verbose_name=_('protocol'),
max_length=50,
choices=FHRPGroupProtocolChoices
)
@ -33,12 +36,12 @@ class FHRPGroup(PrimaryModel):
max_length=50,
choices=FHRPGroupAuthTypeChoices,
blank=True,
verbose_name='Authentication type'
verbose_name=_('Authentication type')
)
auth_key = models.CharField(
max_length=255,
blank=True,
verbose_name='Authentication key'
verbose_name=_('Authentication key')
)
ip_addresses = GenericRelation(
to='ipam.IPAddress',
@ -87,6 +90,7 @@ class FHRPGroupAssignment(ChangeLoggedModel):
on_delete=models.CASCADE
)
priority = models.PositiveSmallIntegerField(
verbose_name=_('priority'),
validators=(
MinValueValidator(FHRPGROUPASSIGNMENT_PRIORITY_MIN),
MaxValueValidator(FHRPGROUPASSIGNMENT_PRIORITY_MAX)
@ -103,7 +107,7 @@ class FHRPGroupAssignment(ChangeLoggedModel):
name='%(app_label)s_%(class)s_unique_interface_group'
),
)
verbose_name = 'FHRP group assignment'
verbose_name = _('FHRP group assignment')
def __str__(self):
return f'{self.interface}: {self.group} ({self.priority})'

View File

@ -6,7 +6,7 @@ from django.db import models
from django.db.models import F
from django.urls import reverse
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.constants import *
@ -59,14 +59,14 @@ class RIR(OrganizationalModel):
"""
is_private = models.BooleanField(
default=False,
verbose_name='Private',
verbose_name=_('Private'),
help_text=_('IP space managed by this RIR is considered private')
)
class Meta:
ordering = ('name',)
verbose_name = 'RIR'
verbose_name_plural = 'RIRs'
verbose_name = _('RIR')
verbose_name_plural = _('RIRs')
def get_absolute_url(self):
return reverse('ipam:rir', args=[self.pk])
@ -84,7 +84,7 @@ class Aggregate(GetAvailablePrefixesMixin, PrimaryModel):
to='ipam.RIR',
on_delete=models.PROTECT,
related_name='aggregates',
verbose_name='RIR',
verbose_name=_('RIR'),
help_text=_("Regional Internet Registry responsible for this IP space")
)
tenant = models.ForeignKey(
@ -95,6 +95,7 @@ class Aggregate(GetAvailablePrefixesMixin, PrimaryModel):
null=True
)
date_added = models.DateField(
verbose_name=_('date added'),
blank=True,
null=True
)
@ -123,7 +124,7 @@ class Aggregate(GetAvailablePrefixesMixin, PrimaryModel):
# /0 masks are not acceptable
if self.prefix.prefixlen == 0:
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
@ -134,7 +135,7 @@ class Aggregate(GetAvailablePrefixesMixin, PrimaryModel):
covering_aggregates = covering_aggregates.exclude(pk=self.pk)
if covering_aggregates:
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]
)
})
@ -145,7 +146,7 @@ class Aggregate(GetAvailablePrefixesMixin, PrimaryModel):
covered_aggregates = covered_aggregates.exclude(pk=self.pk)
if covered_aggregates:
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]
)
})
@ -179,6 +180,7 @@ class Role(OrganizationalModel):
"Management."
"""
weight = models.PositiveSmallIntegerField(
verbose_name=_('weight'),
default=1000
)
@ -199,6 +201,7 @@ class Prefix(GetAvailablePrefixesMixin, PrimaryModel):
assigned to a VLAN where appropriate.
"""
prefix = IPNetworkField(
verbose_name=_('prefix'),
help_text=_('IPv4 or IPv6 network with mask')
)
site = models.ForeignKey(
@ -235,7 +238,7 @@ class Prefix(GetAvailablePrefixesMixin, PrimaryModel):
max_length=50,
choices=PrefixStatusChoices,
default=PrefixStatusChoices.STATUS_ACTIVE,
verbose_name='Status',
verbose_name=_('Status'),
help_text=_('Operational status of this prefix')
)
role = models.ForeignKey(
@ -247,11 +250,12 @@ class Prefix(GetAvailablePrefixesMixin, PrimaryModel):
help_text=_('The primary function of this prefix')
)
is_pool = models.BooleanField(
verbose_name='Is a pool',
verbose_name=_('Is a pool'),
default=False,
help_text=_('All IP addresses within this prefix are considered usable')
)
mark_utilized = models.BooleanField(
verbose_name=_('mark utilized'),
default=False,
help_text=_("Treat as 100% utilized")
)
@ -297,7 +301,7 @@ class Prefix(GetAvailablePrefixesMixin, PrimaryModel):
# /0 masks are not acceptable
if self.prefix.prefixlen == 0:
raise ValidationError({
'prefix': "Cannot create prefix with /0 mask."
'prefix': _("Cannot create prefix with /0 mask.")
})
# Enforce unique IP space (if applicable)
@ -305,8 +309,8 @@ class Prefix(GetAvailablePrefixesMixin, PrimaryModel):
duplicate_prefixes = self.get_duplicates()
if duplicate_prefixes:
raise ValidationError({
'prefix': "Duplicate prefix found in {}: {}".format(
"VRF {}".format(self.vrf) if self.vrf else "global table",
'prefix': _("Duplicate prefix found in {}: {}".format(
"VRF {}").format(self.vrf) if self.vrf else _("global table"),
duplicate_prefixes.first(),
)
})
@ -474,12 +478,15 @@ class IPRange(PrimaryModel):
A range of IP addresses, defined by start and end addresses.
"""
start_address = IPAddressField(
verbose_name=_('start address'),
help_text=_('IPv4 or IPv6 address (with mask)')
)
end_address = IPAddressField(
verbose_name=_('end address'),
help_text=_('IPv4 or IPv6 address (with mask)')
)
size = models.PositiveIntegerField(
verbose_name=_('size'),
editable=False
)
vrf = models.ForeignKey(
@ -488,7 +495,7 @@ class IPRange(PrimaryModel):
related_name='ip_ranges',
blank=True,
null=True,
verbose_name='VRF'
verbose_name=_('VRF')
)
tenant = models.ForeignKey(
to='tenancy.Tenant',
@ -498,6 +505,7 @@ class IPRange(PrimaryModel):
null=True
)
status = models.CharField(
verbose_name=_('status'),
max_length=50,
choices=IPRangeStatusChoices,
default=IPRangeStatusChoices.STATUS_ACTIVE,
@ -512,6 +520,7 @@ class IPRange(PrimaryModel):
help_text=_('The primary function of this range')
)
mark_utilized = models.BooleanField(
verbose_name=_('mark utilized'),
default=False,
help_text=_("Treat as 100% utilized")
)
@ -539,21 +548,23 @@ class IPRange(PrimaryModel):
# Check that start & end IP versions match
if self.start_address.version != self.end_address.version:
raise ValidationError({
'end_address': f"Ending address version (IPv{self.end_address.version}) does not match starting "
f"address (IPv{self.start_address.version})"
'end_address': _("Ending address version (IPv{end_address_version}) does not match starting "
"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
if self.start_address.prefixlen != self.end_address.prefixlen:
raise ValidationError({
'end_address': f"Ending address mask (/{self.end_address.prefixlen}) does not match starting "
f"address mask (/{self.start_address.prefixlen})"
'end_address': _("Ending address mask (/{end_address_prefixlen}) does not match starting "
"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
if not self.end_address > self.start_address:
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
@ -563,12 +574,13 @@ class IPRange(PrimaryModel):
Q(start_address__lte=self.start_address, end_address__gte=self.end_address) # Starts & ends outside
).first()
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
MAX_SIZE = 2 ** 32 - 1
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):
@ -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.
"""
address = IPAddressField(
verbose_name=_('address'),
help_text=_('IPv4 or IPv6 address (with mask)')
)
vrf = models.ForeignKey(
@ -687,7 +700,7 @@ class IPAddress(PrimaryModel):
related_name='ip_addresses',
blank=True,
null=True,
verbose_name='VRF'
verbose_name=_('VRF')
)
tenant = models.ForeignKey(
to='tenancy.Tenant',
@ -697,12 +710,14 @@ class IPAddress(PrimaryModel):
null=True
)
status = models.CharField(
verbose_name=_('status'),
max_length=50,
choices=IPAddressStatusChoices,
default=IPAddressStatusChoices.STATUS_ACTIVE,
help_text=_('The operational status of this IP')
)
role = models.CharField(
verbose_name=_('role'),
max_length=50,
choices=IPAddressRoleChoices,
blank=True,
@ -730,14 +745,14 @@ class IPAddress(PrimaryModel):
related_name='nat_outside',
blank=True,
null=True,
verbose_name='NAT (Inside)',
verbose_name=_('NAT (Inside)'),
help_text=_('The IP for which this address is the "outside" IP')
)
dns_name = models.CharField(
max_length=255,
blank=True,
validators=[DNSValidator],
verbose_name='DNS Name',
verbose_name=_('DNS Name'),
help_text=_('Hostname or FQDN (not case-sensitive)')
)
@ -799,7 +814,7 @@ class IPAddress(PrimaryModel):
# /0 masks are not acceptable
if self.address.prefixlen == 0:
raise ValidationError({
'address': "Cannot create IP address with /0 mask."
'address': _("Cannot create IP address with /0 mask.")
})
# 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)
):
raise ValidationError({
'address': "Duplicate IP address found in {}: {}".format(
"VRF {}".format(self.vrf) if self.vrf else "global table",
'address': _("Duplicate IP address found in {}: {}".format(
"VRF {}").format(self.vrf) if self.vrf else _("global table"),
duplicate_ips.first(),
)
})
@ -819,7 +834,7 @@ class IPAddress(PrimaryModel):
# Validate IP status selection
if self.status == IPAddressStatusChoices.STATUS_SLAAC and self.family != 6:
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):

View File

@ -4,6 +4,7 @@ from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from ipam.choices import L2VPNTypeChoices
from ipam.constants import L2VPN_ASSIGNMENT_MODELS
@ -17,18 +18,22 @@ __all__ = (
class L2VPN(PrimaryModel):
name = models.CharField(
verbose_name=_('name'),
max_length=100,
unique=True
)
slug = models.SlugField(
verbose_name=_('slug'),
max_length=100,
unique=True
)
type = models.CharField(
verbose_name=_('type'),
max_length=50,
choices=L2VPNTypeChoices
)
identifier = models.BigIntegerField(
verbose_name=_('identifier'),
null=True,
blank=True
)
@ -123,7 +128,7 @@ class L2VPNTermination(NetBoxModel):
obj_type = ContentType.objects.get_for_model(self.assigned_object)
if L2VPNTermination.objects.filter(assigned_object_id=obj_id, assigned_object_type=obj_type).\
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
if hasattr(self, 'l2vpn') and self.l2vpn.type in L2VPNTypeChoices.P2P:
@ -131,8 +136,8 @@ class L2VPNTermination(NetBoxModel):
if terminations_count >= 2:
l2vpn_type = self.l2vpn.get_type_display()
raise ValidationError(
f'{l2vpn_type} L2VPNs cannot have more than two terminations; found {terminations_count} already '
f'defined.'
_('{l2vpn_type} L2VPNs cannot have more than two terminations; found {terminations_count} already '
'defined.').format(l2vpn_type=l2vpn_type, terminations_count=terminations_count)
)
@property

View File

@ -3,7 +3,7 @@ from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
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.constants import *
@ -19,6 +19,7 @@ __all__ = (
class ServiceBase(models.Model):
protocol = models.CharField(
verbose_name=_('protocol'),
max_length=50,
choices=ServiceProtocolChoices
)
@ -29,7 +30,7 @@ class ServiceBase(models.Model):
MaxValueValidator(SERVICE_PORT_MAX)
]
),
verbose_name='Port numbers'
verbose_name=_('Port numbers')
)
class Meta:
@ -48,6 +49,7 @@ class ServiceTemplate(ServiceBase, PrimaryModel):
A template for a Service to be applied to a device or virtual machine.
"""
name = models.CharField(
verbose_name=_('name'),
max_length=100,
unique=True
)
@ -68,7 +70,7 @@ class Service(ServiceBase, PrimaryModel):
to='dcim.Device',
on_delete=models.CASCADE,
related_name='services',
verbose_name='device',
verbose_name=_('device'),
null=True,
blank=True
)
@ -86,7 +88,7 @@ class Service(ServiceBase, PrimaryModel):
to='ipam.IPAddress',
related_name='services',
blank=True,
verbose_name='IP addresses',
verbose_name=_('IP addresses'),
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
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:
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."))

View File

@ -4,7 +4,7 @@ from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
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 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.
"""
name = models.CharField(
verbose_name=_('name'),
max_length=100
)
slug = models.SlugField(
verbose_name=_('slug'),
max_length=100
)
scope_type = models.ForeignKey(
@ -45,7 +47,7 @@ class VLANGroup(OrganizationalModel):
fk_field='scope_id'
)
min_vid = models.PositiveSmallIntegerField(
verbose_name='Minimum VLAN ID',
verbose_name=_('Minimum VLAN ID'),
default=VLAN_VID_MIN,
validators=(
MinValueValidator(VLAN_VID_MIN),
@ -54,7 +56,7 @@ class VLANGroup(OrganizationalModel):
help_text=_('Lowest permissible ID of a child VLAN')
)
max_vid = models.PositiveSmallIntegerField(
verbose_name='Maximum VLAN ID',
verbose_name=_('Maximum VLAN ID'),
default=VLAN_VID_MAX,
validators=(
MinValueValidator(VLAN_VID_MIN),
@ -88,14 +90,14 @@ class VLANGroup(OrganizationalModel):
# Validate scope assignment
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:
raise ValidationError("Cannot set scope_id without 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"
'max_vid': _("Maximum child VID must be greater than or equal to minimum child VID")
})
def get_available_vids(self):
@ -143,7 +145,7 @@ class VLAN(PrimaryModel):
help_text=_("VLAN group (optional)")
)
vid = models.PositiveSmallIntegerField(
verbose_name='ID',
verbose_name=_('ID'),
validators=(
MinValueValidator(VLAN_VID_MIN),
MaxValueValidator(VLAN_VID_MAX)
@ -151,6 +153,7 @@ class VLAN(PrimaryModel):
help_text=_("Numeric VLAN ID (1-4094)")
)
name = models.CharField(
verbose_name=_('name'),
max_length=64
)
tenant = models.ForeignKey(
@ -161,6 +164,7 @@ class VLAN(PrimaryModel):
null=True
)
status = models.CharField(
verbose_name=_('status'),
max_length=50,
choices=VLANStatusChoices,
default=VLANStatusChoices.STATUS_ACTIVE,
@ -215,15 +219,15 @@ class VLAN(PrimaryModel):
# 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}."
'group': _("VLAN is assigned to group {group} (scope: {scope}); cannot also assign to "
"site {site}.").format(group=self.group, scope=self.group.scope, 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}"
'vid': _("VID must be between {min_vid} and {max_vid} for VLANs in group "
"{group}").format(min_vid=self.group.min_vid, max_vid=self.group.max_vid, group=self.group)
})
def get_status_color(self):

View File

@ -1,6 +1,6 @@
from django.db import models
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 netbox.models import PrimaryModel
@ -19,6 +19,7 @@ class VRF(PrimaryModel):
are said to exist in the "global" table.)
"""
name = models.CharField(
verbose_name=_('name'),
max_length=100
)
rd = models.CharField(
@ -26,7 +27,7 @@ class VRF(PrimaryModel):
unique=True,
blank=True,
null=True,
verbose_name='Route distinguisher',
verbose_name=_('Route distinguisher'),
help_text=_('Unique route distinguisher (as defined in RFC 4364)')
)
tenant = models.ForeignKey(
@ -38,7 +39,7 @@ class VRF(PrimaryModel):
)
enforce_unique = models.BooleanField(
default=True,
verbose_name='Enforce unique space',
verbose_name=_('Enforce unique space'),
help_text=_('Prevent duplicate prefixes/IP addresses within this VRF')
)
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.
"""
name = models.CharField(
verbose_name=_('name'),
max_length=VRF_RD_MAX_LENGTH, # Same format options as VRF RD (RFC 4360 section 4)
unique=True,
help_text=_('Route target value (formatted in accordance with RFC 4360)')