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):
     def __unicode__(self):
         return self.name
         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
     @property
     def is_physical(self):
     def is_physical(self):
         return self.form_factor != IFACE_FF_VIRTUAL
         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.urlresolvers import reverse
 from django.core.validators import MaxValueValidator, MinValueValidator
 from django.core.validators import MaxValueValidator, MinValueValidator
 from django.db import models
 from django.db import models
+from django.db.models.expressions import RawSQL
 
 
 from dcim.models import Interface
 from dcim.models import Interface
 from extras.models import CustomFieldModel
 from extras.models import CustomFieldModel
@@ -296,6 +297,20 @@ class Prefix(CreatedUpdatedModel, CustomFieldModel):
         return STATUS_CHOICE_CLASSES[self.status]
         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):
 class IPAddress(CreatedUpdatedModel, CustomFieldModel):
     """
     """
     An IPAddress represents an individual IPv4 or IPv6 address and its mask. The mask length should match what is
     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)')
                                       null=True, verbose_name='NAT IP (inside)')
     description = models.CharField(max_length=100, blank=True)
     description = models.CharField(max_length=100, blank=True)
 
 
+    objects = IPAddressManager()
+
     class Meta:
     class Meta:
         ordering = ['family', 'address']
         ordering = ['family', 'address']
         verbose_name = 'IP address'
         verbose_name = 'IP address'

+ 1 - 1
netbox/netbox/settings.py

@@ -12,7 +12,7 @@ except ImportError:
                                "the documentation.")
                                "the documentation.")
 
 
 
 
-VERSION = '1.5.2-dev'
+VERSION = '1.5.3-dev'
 
 
 # Import local configuration
 # Import local configuration
 for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']:
 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>
 			<span class="fa fa-plus" aria-hidden="true"></span>
 			Add a circuit
 			Add a circuit
 		</a>
 		</a>
+        <a href="{% url 'circuits:circuit_import' %}" class="btn btn-info">
+            <span class="fa fa-download" aria-hidden="true"></span>
+            Import circuits
+        </a>
     {% endif %}
     {% endif %}
     {% include 'inc/export_button.html' with obj_type='circuits' %}
     {% include 'inc/export_button.html' with obj_type='circuits' %}
 </div>
 </div>

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

@@ -9,6 +9,10 @@
 			<span class="fa fa-plus" aria-hidden="true"></span>
 			<span class="fa fa-plus" aria-hidden="true"></span>
 			Add a provider
 			Add a provider
 		</a>
 		</a>
+        <a href="{% url 'circuits:provider_import' %}" class="btn btn-info">
+            <span class="fa fa-download" aria-hidden="true"></span>
+            Import providers
+        </a>
     {% endif %}
     {% endif %}
     {% include 'inc/export_button.html' with obj_type='providers' %}
     {% include 'inc/export_button.html' with obj_type='providers' %}
 </div>
 </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">
                     <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>
                         <i class="glyphicon glyphicon-remove" aria-hidden="true"></i>
                     </a>
                     </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 %}
                 {% else %}
                     <a href="{% url 'dcim:interfaceconnection_add' pk=device.pk %}?interface={{ iface.pk }}" class="btn btn-success btn-xs" title="Connect">
                     <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>
                         <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>
 			<span class="fa fa-plus" aria-hidden="true"></span>
 			Add an aggregate
 			Add an aggregate
 		</a>
 		</a>
+        <a href="{% url 'ipam:aggregate_import' %}" class="btn btn-info">
+            <span class="fa fa-download" aria-hidden="true"></span>
+            Import aggregates
+        </a>
     {% endif %}
     {% endif %}
     {% include 'inc/export_button.html' with obj_type='aggregates' %}
     {% include 'inc/export_button.html' with obj_type='aggregates' %}
 </div>
 </div>

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

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

+ 9 - 3
netbox/tenancy/views.py

@@ -1,5 +1,5 @@
 from django.contrib.auth.mixins import PermissionRequiredMixin
 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 django.shortcuts import get_object_or_404, render
 
 
 from circuits.models import Circuit
 from circuits.models import Circuit
@@ -59,8 +59,14 @@ def tenant(request, slug):
         'rack_count': Rack.objects.filter(tenant=tenant).count(),
         'rack_count': Rack.objects.filter(tenant=tenant).count(),
         'device_count': Device.objects.filter(tenant=tenant).count(),
         'device_count': Device.objects.filter(tenant=tenant).count(),
         'vrf_count': VRF.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(),
         'vlan_count': VLAN.objects.filter(tenant=tenant).count(),
         'circuit_count': Circuit.objects.filter(tenant=tenant).count(),
         'circuit_count': Circuit.objects.filter(tenant=tenant).count(),
     }
     }