Daniel Sheppard 6 месяцев назад
Родитель
Сommit
b1bc933e98

+ 16 - 4
netbox/ipam/forms/bulk_edit.py

@@ -207,7 +207,11 @@ class RoleBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class PrefixBulkEditForm(ScopedBulkEditForm, NetBoxModelBulkEditForm):
-    # TODO: Alter for parent prefix
+    parent = DynamicModelChoiceField(
+        queryset=Prefix.objects.all(),
+        required=False,
+        label=_('Parent Prefix')
+    )
     vlan_group = DynamicModelChoiceField(
         queryset=VLANGroup.objects.all(),
         required=False,
@@ -267,7 +271,7 @@ class PrefixBulkEditForm(ScopedBulkEditForm, NetBoxModelBulkEditForm):
     model = Prefix
     fieldsets = (
         FieldSet('tenant', 'status', 'role', 'description'),
-        FieldSet('vrf', 'prefix_length', 'is_pool', 'mark_utilized', name=_('Addressing')),
+        FieldSet('parent', 'vrf', 'prefix_length', 'is_pool', 'mark_utilized', name=_('Addressing')),
         FieldSet('scope_type', 'scope', name=_('Scope')),
         FieldSet('vlan_group', 'vlan', name=_('VLAN Assignment')),
     )
@@ -277,7 +281,11 @@ class PrefixBulkEditForm(ScopedBulkEditForm, NetBoxModelBulkEditForm):
 
 
 class IPRangeBulkEditForm(NetBoxModelBulkEditForm):
-    # TODO: Alter for prefix
+    prefix = DynamicModelChoiceField(
+        queryset=Prefix.objects.all(),
+        required=False,
+        label=_('Prefix')
+    )
     vrf = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         required=False,
@@ -325,7 +333,11 @@ class IPRangeBulkEditForm(NetBoxModelBulkEditForm):
 
 
 class IPAddressBulkEditForm(NetBoxModelBulkEditForm):
-    # TODO: Alter for prefix
+    prefix = DynamicModelChoiceField(
+        queryset=Prefix.objects.all(),
+        required=False,
+        label=_('Prefix')
+    )
     prefix = DynamicModelChoiceField(
         queryset=Prefix.objects.all(),
         required=False,

+ 53 - 4
netbox/ipam/forms/bulk_import.py

@@ -156,8 +156,18 @@ class RoleImportForm(NetBoxModelImportForm):
 
 
 class PrefixImportForm(ScopedImportForm, NetBoxModelImportForm):
-    # TODO: Alter for aggregate
-    # TODO: Alter for parent prefix
+    aggregate = CSVModelChoiceField(
+        label=_('Aggregate'),
+        queryset=Aggregate.objects.all(),
+        to_field_name='prefix',
+        required=False
+    )
+    parent = CSVModelChoiceField(
+        label=_('Prefix'),
+        queryset=Prefix.objects.all(),
+        to_field_name='prefix',
+        required=False
+    )
     vrf = CSVModelChoiceField(
         label=_('VRF'),
         queryset=VRF.objects.all(),
@@ -245,9 +255,26 @@ class PrefixImportForm(ScopedImportForm, NetBoxModelImportForm):
         queryset = self.fields['vlan'].queryset.filter(query)
         self.fields['vlan'].queryset = queryset
 
+        # Limit Prefix queryset by assigned vrf
+        vrf = data.get('vrf')
+        query = Q()
+        if vrf:
+            query &= Q(**{
+                f"vrf__{self.fields['vrf'].to_field_name}": vrf
+            })
+
+        queryset = self.fields['parent'].queryset.filter(query)
+        self.fields['parent'].queryset = queryset
+
 
 class IPRangeImportForm(NetBoxModelImportForm):
-    # TODO: Alter for prefix
+    prefix = CSVModelChoiceField(
+        label=_('Prefix'),
+        queryset=Prefix.objects.all(),
+        to_field_name='prefix',
+        required=True,
+        help_text=_('Assigned prefix')
+    )
     vrf = CSVModelChoiceField(
         label=_('VRF'),
         queryset=VRF.objects.all(),
@@ -282,9 +309,22 @@ class IPRangeImportForm(NetBoxModelImportForm):
             'description', 'comments', 'tags',
         )
 
+    def __init__(self, data=None, *args, **kwargs):
+        super().__init__(data, *args, **kwargs)
+
+        # Limit Prefix queryset by assigned vrf
+        vrf = data.get('vrf')
+        query = Q()
+        if vrf:
+            query &= Q(**{
+                f"vrf__{self.fields['vrf'].to_field_name}": vrf
+            })
+
+        queryset = self.fields['prefix'].queryset.filter(query)
+        self.fields['prefix'].queryset = queryset
+
 
 class IPAddressImportForm(NetBoxModelImportForm):
-    # TODO: Alter for prefix
     prefix = CSVModelChoiceField(
         label=_('Prefix'),
         queryset=Prefix.objects.all(),
@@ -368,6 +408,15 @@ class IPAddressImportForm(NetBoxModelImportForm):
 
         if data:
 
+            # Limit Prefix queryset by assigned vrf
+            vrf = data.get('vrf')
+            query = Q()
+            if vrf:
+                query &= Q(**{f"vrf__{self.fields['vrf'].to_field_name}": vrf})
+
+            queryset = self.fields['prefix'].queryset.filter(query)
+            self.fields['prefix'].queryset = queryset
+
             # Limit interface queryset by assigned device
             if data.get('device'):
                 self.fields['interface'].queryset = Interface.objects.filter(

+ 1 - 1
netbox/ipam/models/ip.py

@@ -372,7 +372,7 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
     @property
     def mask_length(self):
         return self.prefix.prefixlen if self.prefix else None
-    
+
     @property
     def ipv6_full(self):
         if self.prefix and self.prefix.version == 6:

+ 20 - 11
netbox/ipam/tables/ip.py

@@ -155,7 +155,10 @@ class PrefixUtilizationColumn(columns.UtilizationColumn):
 
 
 class PrefixTable(TenancyColumnsMixin, NetBoxTable):
-    # TODO: Alter for parent prefix
+    parent = tables.Column(
+        verbose_name=_('Parent'),
+        linkify=True
+    )
     prefix = columns.TemplateColumn(
         verbose_name=_('Prefix'),
         template_code=PREFIX_LINK_WITH_DEPTH,
@@ -237,9 +240,9 @@ class PrefixTable(TenancyColumnsMixin, NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = Prefix
         fields = (
-            'pk', 'id', 'prefix', 'prefix_flat', 'status', 'children', 'vrf', 'utilization', 'tenant', 'tenant_group',
-            'scope', 'scope_type', 'vlan_group', 'vlan', 'role', 'is_pool', 'mark_utilized', 'description', 'comments',
-            'tags', 'created', 'last_updated',
+            'pk', 'id', 'prefix', 'status', 'parent', 'parent_flat', 'children', 'vrf', 'utilization',
+            'tenant', 'tenant_group', 'scope', 'scope_type', 'vlan_group', 'vlan', 'role', 'is_pool', 'mark_utilized',
+            'description', 'comments', 'tags', 'created', 'last_updated',
         )
         default_columns = (
             'pk', 'prefix', 'status', 'children', 'vrf', 'utilization', 'tenant', 'scope', 'vlan', 'role',
@@ -254,7 +257,10 @@ class PrefixTable(TenancyColumnsMixin, NetBoxTable):
 # IP ranges
 #
 class IPRangeTable(TenancyColumnsMixin, NetBoxTable):
-    # TODO: Alter for prefix
+    prefix = tables.Column(
+        verbose_name=_('Prefix'),
+        linkify=True
+    )
     start_address = tables.Column(
         verbose_name=_('Start address'),
         linkify=True
@@ -294,9 +300,9 @@ class IPRangeTable(TenancyColumnsMixin, NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = IPRange
         fields = (
-            'pk', 'id', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'tenant_group',
-            'mark_populated', 'mark_utilized', 'utilization', 'description', 'comments', 'tags', 'created',
-            'last_updated',
+            'pk', 'id', 'start_address', 'end_address', 'prefix', 'size', 'vrf', 'status', 'role', 'tenant',
+            'tenant_group', 'mark_populated', 'mark_utilized', 'utilization', 'description', 'comments', 'tags',
+            'created', 'last_updated',
         )
         default_columns = (
             'pk', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description',
@@ -311,7 +317,10 @@ class IPRangeTable(TenancyColumnsMixin, NetBoxTable):
 #
 
 class IPAddressTable(TenancyColumnsMixin, NetBoxTable):
-    # TODO: Alter for prefix
+    prefix = tables.Column(
+        verbose_name=_('Prefix'),
+        linkify=True
+    )
     address = tables.TemplateColumn(
         template_code=IPADDRESS_LINK,
         verbose_name=_('IP Address')
@@ -371,8 +380,8 @@ class IPAddressTable(TenancyColumnsMixin, NetBoxTable):
     class Meta(NetBoxTable.Meta):
         model = IPAddress
         fields = (
-            'pk', 'id', 'address', 'vrf', 'status', 'role', 'tenant', 'tenant_group', 'nat_inside', 'nat_outside',
-            'assigned', 'dns_name', 'description', 'comments', 'tags', 'created', 'last_updated',
+            'pk', 'id', 'address', 'vrf', 'prefix', 'status', 'role', 'tenant', 'tenant_group', 'nat_inside',
+            'nat_outside', 'assigned', 'dns_name', 'description', 'comments', 'tags', 'created', 'last_updated',
         )
         default_columns = (
             'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'assigned', 'dns_name', 'description',

+ 14 - 6
netbox/ipam/tables/template_code.py

@@ -16,12 +16,20 @@ PREFIX_COPY_BUTTON = """
 
 PREFIX_LINK_WITH_DEPTH = """
 {% load helpers %}
-{% if record.depth %}
-    <div class="record-depth">
-        {% for i in record.depth|as_range %}
-            <span>•</span>
-        {% endfor %}
-    </div>
+{% if record.depth_count %}
+    {% if object %}
+        <div class="record-depth">
+            {% for i in record.depth_count|parent_depth:object|as_range %}
+                <span>•</span>
+            {% endfor %}
+        </div>
+    {% else %}
+        <div class="record-depth">
+            {% for i in record.depth_count|as_range %}
+                <span>•</span>
+            {% endfor %}
+        </div>
+    {% endif %}
 {% endif %}
 """ + PREFIX_LINK
 

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

@@ -13,6 +13,10 @@
             <th scope="row">{% trans "Family" %}</th>
             <td>IPv{{ object.family }}</td>
         </tr>
+        <tr>
+            <th scope="row">{% trans "Prefix" %}</th>
+            <td>{{ object.prefix|linkify|placeholder }}</td>
+        </tr>
         <tr>
             <th scope="row">{% trans "Starting Address" %}</th>
             <td>{{ object.start_address }}</td>

+ 20 - 0
netbox/utilities/templatetags/helpers.py

@@ -157,6 +157,26 @@ def as_range(n):
     return range(n)
 
 
+@register.filter()
+def parent_depth(n, parent=None):
+    """
+    Return the depth of a node based on the parent's depth
+    """
+    parent_depth = 0
+    if parent and hasattr(parent, 'depth_count'):
+        parent_depth = parent.depth_count + 1
+    elif parent and hasattr(parent, 'depth'):
+        try:
+            parent_depth = int(parent.depth) + 1
+        except TypeError:
+            pass
+    try:
+        depth = int(n) - int(parent_depth)
+    except TypeError:
+        return n
+    return depth
+
+
 @register.filter()
 def meters_to_feet(n):
     """