Browse Source

Merge pull request #170 from peelman/add-mac-address-to-interface

Add mac address to interface
Jeremy Stretch 8 years ago
parent
commit
9793b406e9

+ 2 - 2
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']
 
 

+ 44 - 0
netbox/dcim/fields.py

@@ -0,0 +1,44 @@
+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)

+ 4 - 1
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
     }
 }
-]
+]

+ 26 - 0
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.")

+ 2 - 2
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):

+ 26 - 0
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'),
+        ),
+    ]

+ 2 - 0
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 .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)

+ 2 - 0
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',

+ 2 - 0
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'],
                 })
@@ -1339,6 +1340,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'],

+ 8 - 3
netbox/templates/dcim/device.html

@@ -295,9 +295,6 @@
         {% if interfaces or device.device_type.is_network_device %}
             <div class="panel panel-default">
                 <div class="panel-heading">
-                    {% if perms.dcim.add_interface %}
-                        <a href="{% url 'dcim:interface_add' pk=device.pk %}" class="btn btn-primary btn-xs pull-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add Interfaces</a>
-                    {% endif %}
                     <strong>Interfaces</strong>
                 </div>
                 <table class="table table-hover panel-body">
@@ -309,6 +306,14 @@
                         </tr>
                     {% endfor %}
                 </table>
+                {% if perms.dcim.add_interface %}
+                    <div class="panel-footer text-right">
+                      <a href="{% url 'dcim:interface_add' pk=device.pk %}" class="btn btn-xs btn-primary">
+                          <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
+                          Add interface
+                      </a>
+                     </div>
+                {% endif %}
             </div>
         {% endif %}
         {% if cs_ports or device.device_type.is_console_server %}

+ 9 - 5
netbox/templates/dcim/inc/_interface.html

@@ -6,22 +6,26 @@
         {% endif %}
     </td>
     {% if not iface.is_physical %}
-        <td colspan="2">Virtual</td>
+        <td>Virtual</td>
     {% elif iface.connection %}
         {% with iface.get_connected_interface as connected_iface %}
             <td>
                 <a href="{% url 'dcim:device' pk=connected_iface.device.pk %}">{{ connected_iface.device }}</a>
-            </td>
-            <td>
+                &Colon;
                 <span title="{{ connected_iface.get_form_factor_display }}">{{ connected_iface }}</span>
             </td>
         {% endwith %}
     {% elif iface.circuit %}
-        <td colspan="2">
+        <td>
             <a href="{% url 'circuits:circuit' pk=iface.circuit.pk %}">{{ iface.circuit }}</a>
         </td>
     {% else %}
-        <td colspan="2">Not connected</td>
+        <td>Not connected</td>
+    {% endif %}
+    {% if iface.mac_address %}
+        <td><span class="small text-muted">{{ iface.mac_address }}</span></td>
+    {% else %}
+        <td><span class="small text-muted">00:00:00:00:00:00</span></td>
     {% endif %}
     <td class="text-right">
         {% if iface.circuit or iface.connection %}