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.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(

View File

@ -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})'

View File

@ -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):

View File

@ -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

View File

@ -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."))

View File

@ -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):

View File

@ -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)')