diff --git a/netbox/ipam/models/asns.py b/netbox/ipam/models/asns.py index 6c0b5231b..6e8e89195 100644 --- a/netbox/ipam/models/asns.py +++ b/netbox/ipam/models/asns.py @@ -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( diff --git a/netbox/ipam/models/fhrp.py b/netbox/ipam/models/fhrp.py index 1044a5cde..4e0f20404 100644 --- a/netbox/ipam/models/fhrp.py +++ b/netbox/ipam/models/fhrp.py @@ -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})' diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index a5d6eb084..022ede704 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -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): diff --git a/netbox/ipam/models/l2vpn.py b/netbox/ipam/models/l2vpn.py index c858d1a0c..629a4bfb1 100644 --- a/netbox/ipam/models/l2vpn.py +++ b/netbox/ipam/models/l2vpn.py @@ -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 diff --git a/netbox/ipam/models/services.py b/netbox/ipam/models/services.py index 47ba3b7dc..57b4154fb 100644 --- a/netbox/ipam/models/services.py +++ b/netbox/ipam/models/services.py @@ -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.")) diff --git a/netbox/ipam/models/vlans.py b/netbox/ipam/models/vlans.py index da504ded2..ef609cbee 100644 --- a/netbox/ipam/models/vlans.py +++ b/netbox/ipam/models/vlans.py @@ -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): diff --git a/netbox/ipam/models/vrfs.py b/netbox/ipam/models/vrfs.py index a1a53b3a7..ee20f1e80 100644 --- a/netbox/ipam/models/vrfs.py +++ b/netbox/ipam/models/vrfs.py @@ -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)')