Repository: netbox-community/netbox

Issue:
Device face options with import/export inconsistent
When you export devices the face is either "Front" of "Rear" however when you import devices you need to use "front" or "rear" (case matters)



Discussion:

Resolving patch:
diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py
--- a/netbox/dcim/forms.py
+++ b/netbox/dcim/forms.py
@@ -424,7 +424,7 @@ class DeviceFromCSVForm(forms.ModelForm):
         'invalid_choice': 'Invalid site name.',
     })
     rack_name = forms.CharField()
-    face = forms.ChoiceField(choices=[('front', 'Front'), ('rear', 'Rear')])
+    face = forms.ChoiceField(choices=[('Front', 'Front'), ('Rear', 'Rear')])
 
     class Meta:
         model = Device
@@ -1036,20 +1036,29 @@ def clean(self):
             return
 
         connection_list = []
+        occupied_interfaces = []
 
         for i, record in enumerate(records, start=1):
             form = self.fields['csv'].csv_form(data=record)
             if form.is_valid():
                 interface_a = Interface.objects.get(device=form.cleaned_data['device_a'],
                                                     name=form.cleaned_data['interface_a'])
+                if interface_a in occupied_interfaces:
+                    raise forms.ValidationError("{} {} found in multiple connections"
+                                                .format(interface_a.device.name, interface_a.name))
                 interface_b = Interface.objects.get(device=form.cleaned_data['device_b'],
                                                     name=form.cleaned_data['interface_b'])
+                if interface_b in occupied_interfaces:
+                    raise forms.ValidationError("{} {} found in multiple connections"
+                                                .format(interface_b.device.name, interface_b.name))
                 connection = InterfaceConnection(interface_a=interface_a, interface_b=interface_b)
                 if form.cleaned_data['status'] == 'planned':
                     connection.connection_status = CONNECTION_STATUS_PLANNED
                 else:
                     connection.connection_status = CONNECTION_STATUS_CONNECTED
                 connection_list.append(connection)
+                occupied_interfaces.append(interface_a)
+                occupied_interfaces.append(interface_b)
             else:
                 for field, errors in form.errors.items():
                     for e in errors:
diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py
--- a/netbox/ipam/models.py
+++ b/netbox/ipam/models.py
@@ -121,6 +121,12 @@ def clean(self):
                 raise ValidationError("{} is already covered by an existing aggregate ({})"
                                       .format(self.prefix, covering_aggregates[0]))
 
+            # Ensure that the aggregate being added does not cover an existing aggregate
+            covered_aggregates = Aggregate.objects.filter(prefix__net_contained=str(self.prefix))
+            if covered_aggregates:
+                raise ValidationError("{} is overlaps with an existing aggregate ({})"
+                                      .format(self.prefix, covered_aggregates[0]))
+
     def save(self, *args, **kwargs):
         if self.prefix:
             # Infer address family from IPNetwork object


Test patch:
diff --git a/netbox/dcim/tests/test_apis.py b/netbox/dcim/tests/test_apis.py
--- a/netbox/dcim/tests/test_apis.py
+++ b/netbox/dcim/tests/test_apis.py
@@ -47,7 +47,7 @@ class SiteTest(APITestCase):
     graph_fields = [
         'name',
         'embed_url',
-        'link',
+        'embed_link',
     ]
 
     def test_get_list(self, endpoint='/api/dcim/sites/'):
diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py
--- a/netbox/dcim/tests/test_models.py
+++ b/netbox/dcim/tests/test_models.py
@@ -64,7 +64,7 @@ def test_mount_single_device(self):
             rack=rack1,
             position=10,
             face=RACK_FACE_REAR,
-            )
+        )
         device1.save()
 
         # Validate rack height