Repository: nautobot/nautobot

Issue:
next: object detail view missing changelog tab for objects using slug rather than PK in url pattern
<!--
    NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED.

    This form is only for reporting reproducible bugs. If you need assistance
    with Nautobot installation, or if you have a general question, please start a
    discussion instead: https://github.com/nautobot/nautobot/discussions

    Please describe the environment in which you are running Nautobot. Be sure
    that you are running an unmodified instance of the latest stable release
    before submitting a bug report, and that any plugins have been disabled.
-->
### Environment
* Python version:
* Nautobot version: 1.2.0a1

<!--
    Describe in detail the exact steps that someone else can take to reproduce
    this bug using the current stable release of Nautobot. Begin with the
    creation of any necessary database objects and call out every operation
    being performed explicitly. If reporting a bug in the REST API, be sure to
    reconstruct the raw HTTP request(s) being made: Don't rely on a client
    library such as pynautobot.
-->
### Steps to Reproduce
1. View the object detail page for a model that uses a `slug` rather than a `pk` for its URL pattern, e.g. a Circuit Provider model.
2. Observe that the "Change Log" tab is not present on this page.
3.

<!-- What did you expect to happen? -->
### Expected Behavior

Change Log tab should be present.

<!-- What happened instead? -->
### Observed Behavior

It isn't.

This appears to be a regression due to #737.


Discussion:

Resolving patch:
diff --git a/nautobot/core/views/generic.py b/nautobot/core/views/generic.py
--- a/nautobot/core/views/generic.py
+++ b/nautobot/core/views/generic.py
@@ -95,7 +95,7 @@ def get_changelog_url(self, instance):
                 continue
 
             try:
-                return reverse(route, kwargs={"pk": getattr(instance, field)})
+                return reverse(route, kwargs={field: getattr(instance, field)})
             except NoReverseMatch:
                 continue
 
diff --git a/nautobot/utilities/templatetags/buttons.py b/nautobot/utilities/templatetags/buttons.py
--- a/nautobot/utilities/templatetags/buttons.py
+++ b/nautobot/utilities/templatetags/buttons.py
@@ -48,16 +48,16 @@ def edit_button(instance, use_pk=False, key="slug"):
 
     Args:
         instance: Model record.
-        use_pk: If True, use the primary key instead of the slug. (Deprecated, use "key" instead)
-        key: The attribute in model used in reverse lookup.
+        use_pk: If True, use the primary key instead of any specified "key" field. (Deprecated, use `key="pk"` instead)
+        key: The attribute on the model to use for reverse URL lookup.
     """
     viewname = _get_viewname(instance, "edit")
 
     # Assign kwargs
-    if use_pk:
-        kwargs = {"pk": instance.pk}
-    else:
+    if hasattr(instance, key) and not use_pk:
         kwargs = {key: getattr(instance, key)}
+    else:
+        kwargs = {"pk": instance.pk}
 
     url = reverse(viewname, kwargs=kwargs)
 
@@ -73,16 +73,16 @@ def delete_button(instance, use_pk=False, key="slug"):
 
     Args:
         instance: Model record.
-        use_pk: If True, use the primary key instead of the slug. (Deprecated, use "key" instead)
-        key: The attribute in model used in reverse lookup.
+        use_pk: If True, use the primary key instead of any specified "key" field. (Deprecated, use `key="pk"` instead)
+        key: The attribute on the model to use for reverse URL lookup.
     """
     viewname = _get_viewname(instance, "delete")
 
     # Assign kwargs
-    if use_pk:
-        kwargs = {"pk": instance.pk}
-    else:
+    if hasattr(instance, key) and not use_pk:
         kwargs = {key: getattr(instance, key)}
+    else:
+        kwargs = {"pk": instance.pk}
 
     url = reverse(viewname, kwargs=kwargs)
 


Test patch:
diff --git a/nautobot/utilities/testing/views.py b/nautobot/utilities/testing/views.py
--- a/nautobot/utilities/testing/views.py
+++ b/nautobot/utilities/testing/views.py
@@ -14,7 +14,7 @@
 from taggit.managers import TaggableManager
 
 from nautobot.extras.choices import CustomFieldTypeChoices, RelationshipSideChoices
-from nautobot.extras.models import Tag
+from nautobot.extras.models import ChangeLoggedModel, Tag
 from nautobot.users.models import ObjectPermission
 from nautobot.utilities.permissions import resolve_permission_ct
 from nautobot.utilities.fields import JSONArrayField
@@ -286,6 +286,11 @@ def test_get_object_anonymous(self):
             response = self.client.get(self._get_queryset().first().get_absolute_url())
             self.assertHttpStatus(response, 200)
 
+            # The "Change Log" tab should appear in the response since we have all exempt permissions
+            if issubclass(self.model, ChangeLoggedModel):
+                response_body = extract_page_body(response.content.decode(response.charset))
+                self.assertIn("Change Log", response_body, msg=response_body)
+
         @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
         def test_get_object_without_permission(self):
             instance = self._get_queryset().first()
@@ -348,7 +353,9 @@ def test_get_object_with_constrained_permission(self):
             obj_perm = ObjectPermission(
                 name="Test permission",
                 constraints={"pk": instance1.pk},
-                actions=["view"],
+                # To get a different rendering flow than the `test_get_object_with_permission` test above,
+                # enable additional permissions for this object so that add/edit/delete buttons are rendered.
+                actions=["view", "add", "change", "delete"],
             )
             obj_perm.save()
             obj_perm.users.add(self.user)