Repository: netbox-community/netbox

Issue:
IPAM: Child address list for IP prefix is garbled
### NetBox version

v3.4.0

### Python version

3.8

### Steps to Reproduce

1. View a prefix with multiple non-sequentially assigned child addresses 
2. Click on "IP Addresses"

### Expected Behavior

The list of IP address is displayed, with "xx IPs available" rows in rows between assigned IP address ranges

### Observed Behavior

All "xx IPs available" placeholders are displayed at the top of the list. 

<img width="1267" alt="Screenshot 2022-12-16 at 09 58 40" src="https://user-images.githubusercontent.com/6815386/208062314-05c0d85e-63bd-4332-a302-366376af09a4.png">

After clicking on the row header in order to sort the list, the "xx IPs available" placeholders are all gone.


Discussion:
This is caused by an incomplete fix to https://github.com/netbox-community/netbox/issues/9328:

https://github.com/netbox-community/netbox/commit/899b61264f8f1283c787920048e98854e95db169

To be clear, the now intended functionality is remove the avaliable ip rows when the table is sorted. The bug here is that they are only removed when the list has just been sorted, and not when the list was allready sorted when loaded.

A workaround to the problem of the avaliable IP's not being shown is to press the small x in the column header to reset the sort.
Thanks! It makes perfect sense to remove the available IP rows when the list is sorted by any column other than IP addresses. When sorted by IP addresses the would rows make sense, otherwise they don't - or am I missing something?

Removing the sorting by clicking on "x" indeed works around the problem. The question is, however, why the list initially came up sorted by "status" when I first opened it ... is the sorting stored somewhere? When I open the list of IPs for other prefixes there is no sorting enabled by default. 

[Edit] The list came up sorted by IP address, not by status ... I was fooled (again) by the red "x" being displayed next to "status" when actually it is referring to the column left of it. 
> When sorted by IP addresses the would rows make sense, otherwise they don't - or am I missing something?

I can't remember exactly (so don't hold me to this), but I believe it's a question of it being hard to implement with the way ordering and the addition of the available IP rows work. The solution is a compromise as the default sort is already sorted by IP. The thing that would be missing is a reversed sort by IP address, but the amount of work required is probably not worth it. 

> The question is, however, why the list initially came up sorted by "status" when I first opened it ... is the sorting stored somewhere?

Yes all sorting (and chosen columns) is stored on a user-basis. You can see them and reset them in the bottom of the preference view: /user/preferences/
That makes sense, thanks for the information. I agree, having the available IP rows as long as the sorting is by IP address would be nice, though it's certainly not worth a major effort.
> I believe it's a question of it being hard to implement with the way ordering and the addition of the available IP rows work.

Pretty much. We just don't have a great mechanism for annotating available IPs in between IPAddress records.

Fixing the bug itself is a bit tricky because we need to infer how the table is ordered to prepare the data, but we can't initialize the table until we have the data prepared. We'll probably need to introduce a utility function for determining a table's ordering, that can be used by both `prep_table_data()` under the view and by a table's `configure()` method.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. NetBox is governed by a small group of core maintainers which means not all opened issues may receive direct feedback. **Do not** attempt to circumvent this process by "bumping" the issue; doing so will result in its immediate closure and you may be barred from participating in any future discussions. Please see our [contributing guide](https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md).
This is also an issue when filtering the objects list, as non-matching objects may be incorrectly annotated as available space.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. NetBox is governed by a small group of core maintainers which means not all opened issues may receive direct feedback. **Do not** attempt to circumvent this process by "bumping" the issue; doing so will result in its immediate closure and you may be barred from participating in any future discussions. Please see our [contributing guide](https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md).

Resolving patch:
diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py
--- a/netbox/ipam/views.py
+++ b/netbox/ipam/views.py
@@ -1,7 +1,6 @@
 from django.contrib.contenttypes.models import ContentType
 from django.db.models import F, Prefetch
 from django.db.models.expressions import RawSQL
-from django.db.models.functions import Round
 from django.shortcuts import get_object_or_404, redirect, render
 from django.urls import reverse
 from django.utils.translation import gettext as _
@@ -11,6 +10,7 @@
 from dcim.models import Interface, Site
 from netbox.views import generic
 from tenancy.views import ObjectContactsView
+from utilities.tables import get_table_ordering
 from utilities.utils import count_related
 from utilities.views import ViewTab, register_model_view
 from virtualization.filtersets import VMInterfaceFilterSet
@@ -606,7 +606,7 @@ def get_children(self, request, parent):
         return parent.get_child_ips().restrict(request.user, 'view').prefetch_related('vrf', 'tenant', 'tenant__group')
 
     def prep_table_data(self, request, queryset, parent):
-        if not request.GET.get('q') and not request.GET.get('sort'):
+        if not get_table_ordering(request, self.table):
             return add_available_ipaddresses(parent.prefix, queryset, parent.is_pool)
         return queryset
 
@@ -952,7 +952,9 @@ def get_children(self, request, parent):
         )
 
     def prep_table_data(self, request, queryset, parent):
-        return add_available_vlans(parent.get_child_vlans(), parent)
+        if not get_table_ordering(request, self.table):
+            return add_available_vlans(parent.get_child_vlans(), parent)
+        return queryset
 
 
 #
diff --git a/netbox/utilities/tables.py b/netbox/utilities/tables.py
--- a/netbox/utilities/tables.py
+++ b/netbox/utilities/tables.py
@@ -1,8 +1,24 @@
 __all__ = (
+    'get_table_ordering',
     'linkify_phone',
 )
 
 
+def get_table_ordering(request, table):
+    """
+    Given a request, return the prescribed table ordering, if any. This may be necessary to determine prior to rendering
+    the table itself.
+    """
+    # Check for an explicit ordering
+    if 'sort' in request.GET:
+        return request.GET['sort'] or None
+
+    # Check for a configured preference
+    if request.user.is_authenticated:
+        if preference := request.user.config.get(f'tables.{table.__name__}.ordering'):
+            return preference
+
+
 def linkify_phone(value):
     """
     Render a telephone number as a hyperlink.


Test patch: