From 5034b836ea5131020cdafefe6b640f23233ae945 Mon Sep 17 00:00:00 2001 From: Nick Peelman Date: Fri, 1 Jul 2016 10:30:49 -0400 Subject: [PATCH 1/9] Add MAC address field to interfaces --- netbox/dcim/forms.py | 4 ++-- .../migrations/0004_interface_mac_address.py | 21 +++++++++++++++++++ netbox/dcim/models.py | 2 ++ netbox/dcim/views.py | 1 + netbox/templates/dcim/inc/_interface.html | 11 +++++++--- requirements.txt | 1 + 6 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 netbox/dcim/migrations/0004_interface_mac_address.py diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 4c81ae9ff..8a0008696 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -925,7 +925,7 @@ class InterfaceForm(forms.ModelForm, BootstrapMixin): class Meta: model = Interface - fields = ['device', 'name', 'form_factor', 'mgmt_only', 'description'] + fields = ['device', 'name', 'form_factor', 'mac_address', 'mgmt_only', 'description'] widgets = { 'device': forms.HiddenInput(), } @@ -936,7 +936,7 @@ class InterfaceCreateForm(forms.ModelForm, BootstrapMixin): class Meta: model = Interface - fields = ['name_pattern', 'form_factor', 'mgmt_only', 'description'] + fields = ['name_pattern', 'form_factor', 'mac_address', 'mgmt_only', 'description'] class InterfaceBulkCreateForm(InterfaceCreateForm, BootstrapMixin): diff --git a/netbox/dcim/migrations/0004_interface_mac_address.py b/netbox/dcim/migrations/0004_interface_mac_address.py new file mode 100644 index 000000000..5dd393ab1 --- /dev/null +++ b/netbox/dcim/migrations/0004_interface_mac_address.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-07-01 13:53 +from __future__ import unicode_literals + +from django.db import migrations +import macaddress.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0003_auto_20160628_1721'), + ] + + operations = [ + migrations.AddField( + model_name='interface', + name='mac_address', + field=macaddress.fields.MACAddressField(blank=True, integer=True, null=True), + ), + ] diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 21f775fd0..5670516e3 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -10,6 +10,7 @@ from extras.rpc import RPC_CLIENTS from utilities.fields import NullableCharField from utilities.models import CreatedUpdatedModel +from macaddress.fields import MACAddressField RACK_FACE_FRONT = 0 RACK_FACE_REAR = 1 @@ -856,6 +857,7 @@ class Interface(models.Model): device = models.ForeignKey('Device', related_name='interfaces', on_delete=models.CASCADE) name = models.CharField(max_length=30) form_factor = models.PositiveSmallIntegerField(choices=IFACE_FF_CHOICES, default=IFACE_FF_SFP_PLUS) + mac_address = MACAddressField(null=True, blank=True, verbose_name='MAC Address') mgmt_only = models.BooleanField(default=False, verbose_name='OOB Management', help_text="This interface is used only for out-of-band management") description = models.CharField(max_length=100, blank=True) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index e8f3e4d77..2c4526163 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1339,6 +1339,7 @@ class InterfaceBulkAddView(PermissionRequiredMixin, BulkEditView): iface_form = forms.InterfaceForm({ 'device': device.pk, 'name': name, + 'mac_address': mac_address, 'form_factor': form.cleaned_data['form_factor'], 'mgmt_only': form.cleaned_data['mgmt_only'], 'description': form.cleaned_data['description'], diff --git a/netbox/templates/dcim/inc/_interface.html b/netbox/templates/dcim/inc/_interface.html index 84af875e4..28d14946d 100644 --- a/netbox/templates/dcim/inc/_interface.html +++ b/netbox/templates/dcim/inc/_interface.html @@ -6,7 +6,7 @@ {% endif %} {% if not iface.is_physical %} - Virtual + Virtual {% elif iface.connection %} {% with iface.get_connected_interface as connected_iface %} @@ -17,11 +17,16 @@ {% endwith %} {% elif iface.circuit %} - + {{ iface.circuit }} {% else %} - Not connected + Not connected + {% endif %} + {% if iface.mac_address %} + {{ iface.mac_address }} + {% else %} + 00:00:00:00:00:00 {% endif %} {% if iface.circuit or iface.connection %} diff --git a/requirements.txt b/requirements.txt index c1afc1b21..f99ce2ddf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ django-filter==0.13.0 django-rest-swagger==0.3.7 django-tables2==1.2.1 djangorestframework==3.3.3 +django-macaddress==1.3.2 graphviz==0.4.10 Markdown==2.6.6 ncclient==0.4.7 From 6fb530b75dce673b70a8345aac43756becf50fd3 Mon Sep 17 00:00:00 2001 From: Nick Peelman Date: Fri, 1 Jul 2016 10:31:09 -0400 Subject: [PATCH 2/9] Relocate Add Interface button to match the style used in the rest of the view --- netbox/templates/dcim/device.html | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 0bf635e31..203c0b434 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -295,9 +295,6 @@ {% if interfaces or device.device_type.is_network_device %}
- {% if perms.dcim.add_interface %} - Add Interfaces - {% endif %} Interfaces
@@ -309,6 +306,14 @@ {% endfor %}
+ {% if perms.dcim.add_interface %} + + {% endif %}
{% endif %} {% if cs_ports or device.device_type.is_console_server %} From 0ce92cb2eec062b9f010f36a6d6badc23376bc6e Mon Sep 17 00:00:00 2001 From: Nick Peelman Date: Fri, 1 Jul 2016 14:00:12 -0400 Subject: [PATCH 3/9] Add fixtures for mac addresses. add mac addresses to api tests --- netbox/dcim/fixtures/dcim.json | 5 ++++- netbox/dcim/tests/test_apis.py | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/netbox/dcim/fixtures/dcim.json b/netbox/dcim/fixtures/dcim.json index ecea8eb86..89c889b65 100644 --- a/netbox/dcim/fixtures/dcim.json +++ b/netbox/dcim/fixtures/dcim.json @@ -3419,6 +3419,7 @@ "fields": { "device": 3, "name": "em0", + "mac_address": "00-00-00-AA-BB-CC", "form_factor": 800, "mgmt_only": true, "description": "" @@ -3772,6 +3773,7 @@ "device": 4, "name": "em0", "form_factor": 1000, + "mac_address": "ff-ee-dd-33-22-11", "mgmt_only": true, "description": "" } @@ -5686,6 +5688,7 @@ "device": 9, "name": "eth0", "form_factor": 1000, + "mac_address": "44-55-66-77-88-99", "mgmt_only": true, "description": "" } @@ -5865,4 +5868,4 @@ "connection_status": true } } -] \ No newline at end of file +] diff --git a/netbox/dcim/tests/test_apis.py b/netbox/dcim/tests/test_apis.py index d646f8f01..1f31fac14 100644 --- a/netbox/dcim/tests/test_apis.py +++ b/netbox/dcim/tests/test_apis.py @@ -529,6 +529,7 @@ class InterfaceTest(APITestCase): 'device', 'name', 'form_factor', + 'mac_address', 'mgmt_only', 'description', 'is_connected' @@ -541,6 +542,7 @@ class InterfaceTest(APITestCase): 'device', 'name', 'form_factor', + 'mac_address', 'mgmt_only', 'description', 'is_connected', From 9da4c28cd50cdb07cd5e2c97a56a259231d2f642 Mon Sep 17 00:00:00 2001 From: Nick Peelman Date: Fri, 1 Jul 2016 14:40:29 -0400 Subject: [PATCH 4/9] Tests pass now --- netbox/dcim/api/serializers.py | 4 ++-- netbox/dcim/views.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 2e9335069..dd3de9baf 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -334,7 +334,7 @@ class InterfaceSerializer(serializers.ModelSerializer): class Meta: model = Interface - fields = ['id', 'device', 'name', 'form_factor', 'mgmt_only', 'description', 'is_connected'] + fields = ['id', 'device', 'name', 'form_factor', 'mac_address', 'mgmt_only', 'description', 'is_connected'] class InterfaceNestedSerializer(InterfaceSerializer): @@ -348,7 +348,7 @@ class InterfaceDetailSerializer(InterfaceSerializer): connected_interface = InterfaceSerializer(source='get_connected_interface') class Meta(InterfaceSerializer.Meta): - fields = ['id', 'device', 'name', 'form_factor', 'mgmt_only', 'description', 'is_connected', + fields = ['id', 'device', 'name', 'form_factor', 'mac_address', 'mgmt_only', 'description', 'is_connected', 'connected_interface'] diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 2c4526163..220af4f47 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1252,6 +1252,7 @@ def interface_add(request, pk): 'device': device.pk, 'name': name, 'form_factor': form.cleaned_data['form_factor'], + 'mac_address': form.cleaned_data['mac_address'], 'mgmt_only': form.cleaned_data['mgmt_only'], 'description': form.cleaned_data['description'], }) From a6d41c95b85cff9780406c53f8d525ccfaabe6aa Mon Sep 17 00:00:00 2001 From: Nick Peelman Date: Tue, 5 Jul 2016 10:41:38 -0400 Subject: [PATCH 5/9] Remove external macaddress package dependency --- netbox/dcim/fields.py | 43 +++++++++++++++++++ netbox/dcim/formfields.py | 26 +++++++++++ .../migrations/0004_interface_mac_address.py | 8 ++-- netbox/dcim/models.py | 2 +- requirements.txt | 1 - 5 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 netbox/dcim/fields.py create mode 100644 netbox/dcim/formfields.py diff --git a/netbox/dcim/fields.py b/netbox/dcim/fields.py new file mode 100644 index 000000000..1d756c910 --- /dev/null +++ b/netbox/dcim/fields.py @@ -0,0 +1,43 @@ +from netaddr import EUI, mac_unix_expanded + +from django.core.exceptions import ValidationError +from django.db import models + +from .formfields import MACAddressFormField + +class mac_unix_expanded_uppercase(mac_unix_expanded): + word_fmt = '%.2X' + + +class MACAddressField(models.Field): + description = "PostgreSQL MAC Address field" + + def python_type(self): + return EUI + + def from_db_value(self, value, expression, connection, context): + return self.to_python(value) + + def to_python(self, value): + if not value: + return value + try: + return EUI(value, dialect=mac_unix_expanded_uppercase) + except ValueError as e: + raise ValidationError(e) + + def db_type(self, connection): + return 'macaddr' + + def get_prep_value(self, value): + if not value: + return None + return str(self.to_python(value)) + + def form_class(self): + return MACAddressFormField + + def formfield(self, **kwargs): + defaults = {'form_class': self.form_class()} + defaults.update(kwargs) + return super(MACAddressField, self).formfield(**defaults) diff --git a/netbox/dcim/formfields.py b/netbox/dcim/formfields.py new file mode 100644 index 000000000..e3f1ae39d --- /dev/null +++ b/netbox/dcim/formfields.py @@ -0,0 +1,26 @@ +from netaddr import EUI, AddrFormatError + +from django import forms +from django.core.exceptions import ValidationError + + +# +# Form fields +# + +class MACAddressFormField(forms.Field): + default_error_messages = { + 'invalid': "Enter a valid MAC address.", + } + + def to_python(self, value): + if not value: + return None + + if isinstance(value, EUI): + return value + + try: + return EUI(value) + except AddrFormatError: + raise ValidationError("Please specify a valid MAC address.") diff --git a/netbox/dcim/migrations/0004_interface_mac_address.py b/netbox/dcim/migrations/0004_interface_mac_address.py index 5dd393ab1..fd1ac958d 100644 --- a/netbox/dcim/migrations/0004_interface_mac_address.py +++ b/netbox/dcim/migrations/0004_interface_mac_address.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.9.7 on 2016-07-01 13:53 +# Generated by Django 1.9.7 on 2016-07-05 10:01 from __future__ import unicode_literals -from django.db import migrations -import macaddress.fields +from django.db import migrations, models +from ..fields import MACAddressField class Migration(migrations.Migration): @@ -16,6 +16,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='interface', name='mac_address', - field=macaddress.fields.MACAddressField(blank=True, integer=True, null=True), + field=MACAddressField(blank=True, null=True, verbose_name=b'MAC Address'), ), ] diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 5670516e3..2f1a62d36 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -10,7 +10,7 @@ from extras.rpc import RPC_CLIENTS from utilities.fields import NullableCharField from utilities.models import CreatedUpdatedModel -from macaddress.fields import MACAddressField +from .fields import MACAddressField RACK_FACE_FRONT = 0 RACK_FACE_REAR = 1 diff --git a/requirements.txt b/requirements.txt index f99ce2ddf..c1afc1b21 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,6 @@ django-filter==0.13.0 django-rest-swagger==0.3.7 django-tables2==1.2.1 djangorestframework==3.3.3 -django-macaddress==1.3.2 graphviz==0.4.10 Markdown==2.6.6 ncclient==0.4.7 From 9f75d5bd237faf70f7906c356deea91294b1b0d9 Mon Sep 17 00:00:00 2001 From: Nick Peelman Date: Tue, 5 Jul 2016 10:48:36 -0400 Subject: [PATCH 6/9] Fix PEP8 compliance... --- netbox/dcim/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/dcim/fields.py b/netbox/dcim/fields.py index 1d756c910..df119ec17 100644 --- a/netbox/dcim/fields.py +++ b/netbox/dcim/fields.py @@ -5,10 +5,10 @@ from django.db import models from .formfields import MACAddressFormField + class mac_unix_expanded_uppercase(mac_unix_expanded): word_fmt = '%.2X' - class MACAddressField(models.Field): description = "PostgreSQL MAC Address field" From 578013fdd2e2319a93aa69fc9d2f13eee8566b63 Mon Sep 17 00:00:00 2001 From: Nick Peelman Date: Tue, 5 Jul 2016 10:58:28 -0400 Subject: [PATCH 7/9] Fix PEP8 compliance...(again) --- netbox/dcim/fields.py | 1 + 1 file changed, 1 insertion(+) diff --git a/netbox/dcim/fields.py b/netbox/dcim/fields.py index df119ec17..dafc0ad03 100644 --- a/netbox/dcim/fields.py +++ b/netbox/dcim/fields.py @@ -9,6 +9,7 @@ from .formfields import MACAddressFormField class mac_unix_expanded_uppercase(mac_unix_expanded): word_fmt = '%.2X' + class MACAddressField(models.Field): description = "PostgreSQL MAC Address field" From dc847ce4d6f26d200717b0578a6a8921ad1e1d67 Mon Sep 17 00:00:00 2001 From: Nick Peelman Date: Tue, 5 Jul 2016 11:55:06 -0400 Subject: [PATCH 8/9] Fix connected interface template rendering... --- netbox/templates/dcim/inc/_interface.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/netbox/templates/dcim/inc/_interface.html b/netbox/templates/dcim/inc/_interface.html index 28d14946d..f1a2665ff 100644 --- a/netbox/templates/dcim/inc/_interface.html +++ b/netbox/templates/dcim/inc/_interface.html @@ -11,8 +11,7 @@ {% with iface.get_connected_interface as connected_iface %} {{ connected_iface.device }} - - + ∷ {{ connected_iface }} {% endwith %} From 7a2f6eaf34e549eebbbffd7decc15ae6abf7e8d9 Mon Sep 17 00:00:00 2001 From: Nick Peelman Date: Wed, 6 Jul 2016 13:22:41 -0400 Subject: [PATCH 9/9] Regenerate migration --- .../migrations/0004_interface_mac_address.py | 21 --------------- .../migrations/0005_auto_20160706_1722.py | 26 +++++++++++++++++++ 2 files changed, 26 insertions(+), 21 deletions(-) delete mode 100644 netbox/dcim/migrations/0004_interface_mac_address.py create mode 100644 netbox/dcim/migrations/0005_auto_20160706_1722.py diff --git a/netbox/dcim/migrations/0004_interface_mac_address.py b/netbox/dcim/migrations/0004_interface_mac_address.py deleted file mode 100644 index fd1ac958d..000000000 --- a/netbox/dcim/migrations/0004_interface_mac_address.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.7 on 2016-07-05 10:01 -from __future__ import unicode_literals - -from django.db import migrations, models -from ..fields import MACAddressField - - -class Migration(migrations.Migration): - - dependencies = [ - ('dcim', '0003_auto_20160628_1721'), - ] - - operations = [ - migrations.AddField( - model_name='interface', - name='mac_address', - field=MACAddressField(blank=True, null=True, verbose_name=b'MAC Address'), - ), - ] diff --git a/netbox/dcim/migrations/0005_auto_20160706_1722.py b/netbox/dcim/migrations/0005_auto_20160706_1722.py new file mode 100644 index 000000000..83a5cf7cb --- /dev/null +++ b/netbox/dcim/migrations/0005_auto_20160706_1722.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-07-06 17:22 +from __future__ import unicode_literals + +import dcim.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0004_auto_20160701_2049'), + ] + + operations = [ + migrations.AddField( + model_name='interface', + name='mac_address', + field=dcim.fields.MACAddressField(blank=True, null=True, verbose_name=b'MAC Address'), + ), + migrations.AlterField( + model_name='devicetype', + name='subdevice_role', + field=models.NullBooleanField(choices=[(None, b'None'), (True, b'Parent'), (False, b'Child')], default=None, help_text=b'Parent devices house child devices in device bays. Select "None" if this device type is neither a parent nor a child.', verbose_name=b'Parent/child status'), + ), + ]