diff --git a/horizon/forms/__init__.py b/horizon/forms/__init__.py index 594a2adb72..478b3f3ad8 100644 --- a/horizon/forms/__init__.py +++ b/horizon/forms/__init__.py @@ -33,6 +33,7 @@ from horizon.forms.fields import ExternalUploadMeta from horizon.forms.fields import IPField from horizon.forms.fields import IPv4 from horizon.forms.fields import IPv6 +from horizon.forms.fields import MACAddressField from horizon.forms.fields import MultiIPField from horizon.forms.fields import SelectWidget from horizon.forms.fields import ThemableCheckboxInput @@ -64,6 +65,7 @@ __all__ = [ "IPField", "IPv4", "IPv6", + "MACAddressField", "MultiIPField", "SelectWidget", diff --git a/horizon/forms/fields.py b/horizon/forms/fields.py index d9a5d693f0..7192f5a7f0 100644 --- a/horizon/forms/fields.py +++ b/horizon/forms/fields.py @@ -129,6 +129,33 @@ class MultiIPField(IPField): return str(','.join(getattr(self, "addresses", []))) +class MACAddressField(fields.Field): + """Form field for entering a MAC address with validation. + + Supports all formats known by netaddr.EUI(), for example: + .. xx:xx:xx:xx:xx:xx + .. xx-xx-xx-xx-xx-xx + .. xxxx.xxxx.xxxx + """ + def validate(self, value): + super(MACAddressField, self).validate(value) + + if not value: + return + + try: + self.mac_address = netaddr.EUI(value) + # NOTE(rubasov): Normalize MAC address to the most usual format. + self.mac_address.dialect = netaddr.mac_unix_expanded + except Exception: + raise ValidationError(_("Invalid MAC Address format"), + code="invalid_mac") + + def clean(self, value): + super(MACAddressField, self).clean(value) + return str(getattr(self, "mac_address", "")) + + class SelectWidget(widgets.Select): """Customizable select widget, that allows to render data-xxx attributes from choices. This widget also diff --git a/horizon/test/tests/utils.py b/horizon/test/tests/utils.py index 45e92919bd..2372727f27 100644 --- a/horizon/test/tests/utils.py +++ b/horizon/test/tests/utils.py @@ -194,6 +194,30 @@ class ValidatorsTests(test.TestCase): for cidr in BAD_CIDRS_INPUT: self.assertRaises(ValidationError, ip.validate, cidr) + def test_mac_address_validator(self): + GOOD_MAC_ADDRESSES = ( + "00:11:88:99:Aa:Ff", + "00-11-88-99-Aa-Ff", + "0011.8899.AaFf", + "00118899AaFf", + ) + BAD_MAC_ADDRESSES = ( + "not a mac", + "11:22:33:44:55", + "zz:11:22:33:44:55", + ) + + field = forms.MACAddressField() + for input in GOOD_MAC_ADDRESSES: + self.assertIsNone(field.validate(input)) + for input in BAD_MAC_ADDRESSES: + self.assertRaises(ValidationError, field.validate, input) + + def test_mac_address_normal_form(self): + field = forms.MACAddressField() + field.validate("00-11-88-99-Aa-Ff") + self.assertEqual(field.mac_address, "00:11:88:99:aa:ff") + def test_port_validator(self): VALID_PORTS = (1, 65535) INVALID_PORTS = (-1, 65536) diff --git a/openstack_dashboard/dashboards/project/networks/ports/extensions/allowed_address_pairs/forms.py b/openstack_dashboard/dashboards/project/networks/ports/extensions/allowed_address_pairs/forms.py index 5494d11c15..1ef8e5a9e1 100644 --- a/openstack_dashboard/dashboards/project/networks/ports/extensions/allowed_address_pairs/forms.py +++ b/openstack_dashboard/dashboards/project/networks/ports/extensions/allowed_address_pairs/forms.py @@ -16,7 +16,6 @@ import logging from django.core.urlresolvers import reverse -from django.core import validators from django.utils.translation import ugettext_lazy as _ from horizon import exceptions @@ -27,20 +26,15 @@ from openstack_dashboard import api LOG = logging.getLogger(__name__) -validate_mac = validators.RegexValidator(r'([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}', - _("Invalid MAC Address format"), - code="invalid_mac") - class AddAllowedAddressPairForm(forms.SelfHandlingForm): ip = forms.IPField(label=_("IP Address or CIDR"), help_text=_("A single IP Address or CIDR"), version=forms.IPv4 | forms.IPv6, mask=True) - mac = forms.CharField(label=_("MAC Address"), - help_text=_("A valid MAC Address"), - validators=[validate_mac], - required=False) + mac = forms.MACAddressField(label=_("MAC Address"), + help_text=_("A valid MAC Address"), + required=False) failure_url = 'horizon:project:networks:ports:detail' def clean(self):