Browse Source

Merging v1.5.2

Jeremy Stretch 8 years ago
parent
commit
a9a55350df

+ 7 - 0
netbox/dcim/models.py

@@ -1034,6 +1034,13 @@ class Interface(models.Model):
     def __unicode__(self):
         return self.name
 
+    def clean(self):
+
+        if self.form_factor == IFACE_FF_VIRTUAL and self.is_connected:
+            raise ValidationError({'form_factor': "Virtual interfaces cannot be connected to another interface or "
+                                                  "circuit. Disconnect the interface or choose a physical form "
+                                                  "factor."})
+
     @property
     def is_physical(self):
         return self.form_factor != IFACE_FF_VIRTUAL

+ 17 - 0
netbox/ipam/models.py

@@ -5,6 +5,7 @@ from django.core.exceptions import ValidationError
 from django.core.urlresolvers import reverse
 from django.core.validators import MaxValueValidator, MinValueValidator
 from django.db import models
+from django.db.models.expressions import RawSQL
 
 from dcim.models import Interface
 from extras.models import CustomFieldModel
@@ -296,6 +297,20 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
         return STATUS_CHOICE_CLASSES[self.status]
 
 
+class IPAddressManager(models.Manager):
+
+    def get_queryset(self):
+        """
+        By default, PostgreSQL will order INETs with shorter (larger) prefix lengths ahead of those with longer
+        (smaller) masks. This makes no sense when ordering IPs, which should be ordered solely by family and host
+        address. We can use HOST() to extract just the host portion of the address (ignoring its mask), but we must
+        then re-cast this value to INET() so that records will be ordered properly. We are essentially re-casting each
+        IP address as a /32 or /128.
+        """
+        qs = super(IPAddressManager, self).get_queryset()
+        return qs.annotate(host=RawSQL('INET(HOST(ipam_ipaddress.address))', [])).order_by('family', 'host')
+
+
 class IPAddress(CreatedUpdatedModel, CustomFieldModel):
     """
     An IPAddress represents an individual IPv4 or IPv6 address and its mask. The mask length should match what is
@@ -318,6 +333,8 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel):
                                       null=True, verbose_name='NAT IP (inside)')
     description = models.CharField(max_length=100, blank=True)
 
+    objects = IPAddressManager()
+
     class Meta:
         ordering = ['family', 'address']
         verbose_name = 'IP address'

+ 1 - 1
netbox/netbox/settings.py

@@ -12,7 +12,7 @@ except ImportError:
                                "the documentation.")
 
 
-VERSION = '1.5.2-dev'
+VERSION = '1.5.3-dev'
 
 # Import local configuration
 for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']:

+ 4 - 0
netbox/templates/circuits/circuit_list.html

@@ -10,6 +10,10 @@
 			<span class="fa fa-plus" aria-hidden="true"></span>
 			Add a circuit
 		</a>
+        <a href="{% url 'circuits:circuit_import' %}" class="btn btn-info">
+            <span class="fa fa-download" aria-hidden="true"></span>
+            Import circuits
+        </a>
     {% endif %}
     {% include 'inc/export_button.html' with obj_type='circuits' %}
 </div>

+ 4 - 0
netbox/templates/circuits/provider_list.html

@@ -9,6 +9,10 @@
 			<span class="fa fa-plus" aria-hidden="true"></span>
 			Add a provider
 		</a>
+        <a href="{% url 'circuits:provider_import' %}" class="btn btn-info">
+            <span class="fa fa-download" aria-hidden="true"></span>
+            Import providers
+        </a>
     {% endif %}
     {% include 'inc/export_button.html' with obj_type='providers' %}
 </div>

+ 4 - 0
netbox/templates/dcim/inc/_interface.html

@@ -56,6 +56,10 @@
                     <a href="{% url 'dcim:interfaceconnection_delete' pk=iface.connection.pk %}?device={{ device.pk }}" class="btn btn-danger btn-xs" title="Delete connection">
                         <i class="glyphicon glyphicon-remove" aria-hidden="true"></i>
                     </a>
+                {% elif iface.circuit and perms.circuits.change_circuit %}
+                    <a href="{% url 'circuits:circuit_edit' pk=iface.circuit.pk %}" class="btn btn-danger btn-xs" title="Edit circuit">
+                        <i class="glyphicon glyphicon-remove" aria-hidden="true"></i>
+                    </a>
                 {% else %}
                     <a href="{% url 'dcim:interfaceconnection_add' pk=device.pk %}?interface={{ iface.pk }}" class="btn btn-success btn-xs" title="Connect">
                         <i class="glyphicon glyphicon-plus" aria-hidden="true"></i>

+ 4 - 0
netbox/templates/ipam/aggregate_list.html

@@ -11,6 +11,10 @@
 			<span class="fa fa-plus" aria-hidden="true"></span>
 			Add an aggregate
 		</a>
+        <a href="{% url 'ipam:aggregate_import' %}" class="btn btn-info">
+            <span class="fa fa-download" aria-hidden="true"></span>
+            Import aggregates
+        </a>
     {% endif %}
     {% include 'inc/export_button.html' with obj_type='aggregates' %}
 </div>

+ 4 - 0
netbox/templates/tenancy/tenant_list.html

@@ -10,6 +10,10 @@
 			<span class="fa fa-plus" aria-hidden="true"></span>
 			Add a tenant
 		</a>
+        <a href="{% url 'tenancy:tenant_import' %}" class="btn btn-info">
+            <span class="fa fa-download" aria-hidden="true"></span>
+            Import tenants
+        </a>
     {% endif %}
     {% include 'inc/export_button.html' with obj_type='tenants' %}
 </div>

+ 9 - 3
netbox/tenancy/views.py

@@ -1,5 +1,5 @@
 from django.contrib.auth.mixins import PermissionRequiredMixin
-from django.db.models import Count
+from django.db.models import Count, Q
 from django.shortcuts import get_object_or_404, render
 
 from circuits.models import Circuit
@@ -59,8 +59,14 @@ def tenant(request, slug):
         'rack_count': Rack.objects.filter(tenant=tenant).count(),
         'device_count': Device.objects.filter(tenant=tenant).count(),
         'vrf_count': VRF.objects.filter(tenant=tenant).count(),
-        'prefix_count': Prefix.objects.filter(tenant=tenant).count(),
-        'ipaddress_count': IPAddress.objects.filter(tenant=tenant).count(),
+        'prefix_count': Prefix.objects.filter(
+            Q(tenant=tenant) |
+            Q(tenant__isnull=True, vrf__tenant=tenant)
+        ).count(),
+        'ipaddress_count': IPAddress.objects.filter(
+            Q(tenant=tenant) |
+            Q(tenant__isnull=True, vrf__tenant=tenant)
+        ).count(),
         'vlan_count': VLAN.objects.filter(tenant=tenant).count(),
         'circuit_count': Circuit.objects.filter(tenant=tenant).count(),
     }