Repository: netbox-community/netbox

Issue:
Exception when clicking on an IP Address in the FHRP groups list
### NetBox version

v3.6.0

### Python version

3.11

### Steps to Reproduce

1. Open the "FHRP Groups" list view
2. Click on any IP address in the "IP Addresses" column

### Expected Behavior

The detail view for the IP address is opened

### Observed Behavior

An exception is raised:
```

Traceback (most recent call last):
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 880, in _resolve_lookup
   current = current[bit]
             ^^^^^^^^^^^^

During handling of the above exception ('IPAddress' object is not subscriptable), another exception occurred:
 File "/opt/netbox/lib64/python3.11/site-packages/django/core/handlers/exception.py", line 55, in inner
   response = get_response(request)
              ^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/core/handlers/base.py", line 197, in _get_response
   response = wrapped_callback(request, *callback_args, **callback_kwargs)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/views/generic/base.py", line 104, in view
   return self.dispatch(request, *args, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/netbox/netbox/views/generic/base.py", line 26, in dispatch
   return super().dispatch(request, *args, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/netbox/utilities/views.py", line 99, in dispatch
   return super().dispatch(request, *args, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/views/generic/base.py", line 143, in dispatch
   return handler(request, *args, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/netbox/netbox/views/generic/object_views.py", line 70, in get
   return render(request, self.get_template_name(), {

 File "/opt/netbox/lib64/python3.11/site-packages/django/shortcuts.py", line 24, in render
   content = loader.render_to_string(template_name, context, request, using=using)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/loader.py", line 62, in render_to_string
   return template.render(context, request)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/backends/django.py", line 61, in render
   return self.template.render(context)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 175, in render
   return self._render(context)
          ^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/test/utils.py", line 112, in instrumented_test_render
   return self.nodelist.render(context)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 1005, in render
   return SafeString("".join([node.render_annotated(context) for node in self]))
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 1005, in <listcomp>
   return SafeString("".join([node.render_annotated(context) for node in self]))
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 966, in render_annotated
   return self.render(context)
          ^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/loader_tags.py", line 157, in render
   return compiled_parent._render(context)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/test/utils.py", line 112, in instrumented_test_render
   return self.nodelist.render(context)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 1005, in render
   return SafeString("".join([node.render_annotated(context) for node in self]))
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 1005, in <listcomp>
   return SafeString("".join([node.render_annotated(context) for node in self]))
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 966, in render_annotated
   return self.render(context)
          ^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/loader_tags.py", line 157, in render
   return compiled_parent._render(context)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/test/utils.py", line 112, in instrumented_test_render
   return self.nodelist.render(context)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 1005, in render
   return SafeString("".join([node.render_annotated(context) for node in self]))
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 1005, in <listcomp>
   return SafeString("".join([node.render_annotated(context) for node in self]))
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 966, in render_annotated
   return self.render(context)
          ^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/loader_tags.py", line 157, in render
   return compiled_parent._render(context)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/test/utils.py", line 112, in instrumented_test_render
   return self.nodelist.render(context)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 1005, in render
   return SafeString("".join([node.render_annotated(context) for node in self]))
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 1005, in <listcomp>
   return SafeString("".join([node.render_annotated(context) for node in self]))
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 966, in render_annotated
   return self.render(context)
          ^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/loader_tags.py", line 63, in render
   result = block.nodelist.render(context)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 1005, in render
   return SafeString("".join([node.render_annotated(context) for node in self]))
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 1005, in <listcomp>
   return SafeString("".join([node.render_annotated(context) for node in self]))
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 966, in render_annotated
   return self.render(context)
          ^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/loader_tags.py", line 63, in render
   result = block.nodelist.render(context)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 1005, in render
   return SafeString("".join([node.render_annotated(context) for node in self]))
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 1005, in <listcomp>
   return SafeString("".join([node.render_annotated(context) for node in self]))
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 966, in render_annotated
   return self.render(context)
          ^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/loader_tags.py", line 63, in render
   result = block.nodelist.render(context)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 1005, in render
   return SafeString("".join([node.render_annotated(context) for node in self]))
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 1005, in <listcomp>
   return SafeString("".join([node.render_annotated(context) for node in self]))
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 966, in render_annotated
   return self.render(context)
          ^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/library.py", line 257, in render
   resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/library.py", line 221, in get_resolved_arguments
   resolved_args = [var.resolve(context) for var in self.args]
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/library.py", line 221, in <listcomp>
   resolved_args = [var.resolve(context) for var in self.args]
                    ^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 715, in resolve
   obj = self.var.resolve(context)
         ^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 847, in resolve
   value = self._resolve_lookup(context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/lib64/python3.11/site-packages/django/template/base.py", line 890, in _resolve_lookup
   current = getattr(current, bit)
             ^^^^^^^^^^^^^^^^^^^^^
 File "/opt/netbox/netbox/ipam/models/ip.py", line 905, in is_primary_ip
   if self.family == 6 and parent.primary_ip6_id == self.pk:
```


Discussion:

Resolving patch:
diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py
--- a/netbox/ipam/models/ip.py
+++ b/netbox/ipam/models/ip.py
@@ -892,7 +892,7 @@ def family(self):
     def is_oob_ip(self):
         if self.assigned_object:
             parent = getattr(self.assigned_object, 'parent_object', None)
-            if hasattr(parent, "oob_ip_id") and parent.oob_ip_id == self.pk:
+            if hasattr(parent, 'oob_ip') and parent.oob_ip_id == self.pk:
                 return True
         return False
 
@@ -900,9 +900,9 @@ def is_oob_ip(self):
     def is_primary_ip(self):
         if self.assigned_object:
             parent = getattr(self.assigned_object, 'parent_object', None)
-            if self.family == 4 and parent.primary_ip4_id == self.pk:
+            if self.family == 4 and hasattr(parent, 'primary_ip4') and parent.primary_ip4_id == self.pk:
                 return True
-            if self.family == 6 and parent.primary_ip6_id == self.pk:
+            if self.family == 6 and hasattr(parent, 'primary_ip6') and parent.primary_ip6_id == self.pk:
                 return True
         return False
 


Test patch: