Repository: nautobot/nautobot

Issue:
Some GraphQL Queries for Relationships fail on 1.2
<!--
    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: 3.8
* Nautobot version: 1.2.2

<!--
    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. Have a relationship e.g. between cluster and devices (one to many)
2. Run a graphql query like: 
```{
device(id:"bb91df1c-61ec-4a85-8138-cdb97c6c890e")
{
  name
  rel_cluster_to_device{
    id
  }
  }
}
```

<!-- What did you expect to happen? -->
### Expected Behavior
Get the ID of the relationship

<!-- What happened instead? -->
### Observed Behavior
```
{
  "errors": [
    {
      "message": "Cannot call only() after .values() or .values_list()",
      "locations": [
        {
          "line": 5,
          "column": 3
        }
      ],
      "path": [
        "device",
        "rel_cluster_to_device"
      ]
    }
  ],
  "data": {
    "device": {
      "name": "R10-S3",
      "rel_cluster_to_device": null
    }
  }
}
```


Discussion:
```
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]: An error occurred while resolving field DeviceType.rel_device_to_vm
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]: Traceback (most recent call last):
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:   File "/opt/nautobot/lib/python3.9/site-packages/graphql/execution/executor.py", line 452, in resolve_or_error
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:     return executor.execute(resolve_fn, source, info, **args)
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:   File "/opt/nautobot/lib/python3.9/site-packages/graphql/execution/executors/sync.py", line 16, in execute
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:     return fn(*args, **kwargs)
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:   File "/opt/nautobot/lib/python3.9/site-packages/nautobot/core/graphql/generators.py", line 139, in resolve_relationship
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:     queryset_ids = gql_optimizer.query(
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:   File "/opt/nautobot/lib/python3.9/site-packages/graphene_django_optimizer/query.py", line 40, in query
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:     return QueryOptimizer(info, **options).optimize(queryset)
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:   File "/opt/nautobot/lib/python3.9/site-packages/graphene_django_optimizer/query.py", line 60, in optimize
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:     return store.optimize_queryset(queryset)
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:   File "/opt/nautobot/lib/python3.9/site-packages/graphene_django_optimizer/query.py", line 378, in optimize_queryset
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:     queryset = queryset.select_related(*self.select_list)
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:   File "/opt/nautobot/lib/python3.9/site-packages/django/db/models/query.py", line 1047, in select_related
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:     raise TypeError("Cannot call select_related() after .values() or .values_list()")
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]: TypeError: Cannot call select_related() after .values() or .values_list()
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]: Traceback (most recent call last):
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:   File "/opt/nautobot/lib/python3.9/site-packages/graphql/execution/executor.py", line 452, in resolve_or_error
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:     return executor.execute(resolve_fn, source, info, **args)
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:   File "/opt/nautobot/lib/python3.9/site-packages/graphql/execution/executors/sync.py", line 16, in execute
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:     return fn(*args, **kwargs)
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:   File "/opt/nautobot/lib/python3.9/site-packages/nautobot/core/graphql/generators.py", line 139, in resolve_relationship
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:     queryset_ids = gql_optimizer.query(
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:   File "/opt/nautobot/lib/python3.9/site-packages/graphene_django_optimizer/query.py", line 40, in query
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:     return QueryOptimizer(info, **options).optimize(queryset)
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:   File "/opt/nautobot/lib/python3.9/site-packages/graphene_django_optimizer/query.py", line 60, in optimize
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:     return store.optimize_queryset(queryset)
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:   File "/opt/nautobot/lib/python3.9/site-packages/graphene_django_optimizer/query.py", line 378, in optimize_queryset
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:     queryset = queryset.select_related(*self.select_list)
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:   File "/opt/nautobot/lib/python3.9/site-packages/django/db/models/query.py", line 1047, in select_related
Jan 04 10:38:42 dh01-a-06-18 nautobot-server[2465943]:     raise TypeError("Cannot call select_related() after .values() or .values_list()")
```
Thanks for the report! Looks likely to be a regression introduced by #769. Investigating.
Interestingly, this query reproduces the error:

```graphql
query {
  device(id: "a7c1dcc8-80eb-4754-9981-e2f63a9ab11e") {
    id
    name
    rel_bgp_device_router_id {
      id
    }
  }
}
```

but the following variant query works just fine:

```graphql
query {
  device(id: "a7c1dcc8-80eb-4754-9981-e2f63a9ab11e") {
    id
    name
    rel_bgp_device_router_id {
      id
      address
    }
  }
}
```

Resolving patch:
diff --git a/nautobot/core/graphql/generators.py b/nautobot/core/graphql/generators.py
--- a/nautobot/core/graphql/generators.py
+++ b/nautobot/core/graphql/generators.py
@@ -133,35 +133,69 @@ def resolve_relationship(self, info, **kwargs):
         """Return a queryset or an object depending on the type of the relationship."""
         peer_side = RelationshipSideChoices.OPPOSITE[side]
         query_params = {"relationship": relationship}
+        # https://github.com/nautobot/nautobot/issues/1228
+        # If querying for **only** the ID of the related object, for example:
+        # { device(id:"...") { ... rel_my_relationship { id } } }
+        # we will get this exception:
+        # TypeError: Cannot call select_related() after .values() or .values_list()
+        # This appears to be a bug in graphene_django_optimizer but I haven't found a known issue on GitHub.
+        # For now we just work around it by catching the exception and retrying without optimization, below...
         if not relationship.symmetric:
             # Get the objects on the other side of this relationship
             query_params[f"{side}_id"] = self.pk
-            queryset_ids = gql_optimizer.query(
-                RelationshipAssociation.objects.filter(**query_params).values_list(f"{peer_side}_id", flat=True), info
-            )
+
+            try:
+                queryset_ids = gql_optimizer.query(
+                    RelationshipAssociation.objects.filter(**query_params).values_list(f"{peer_side}_id", flat=True),
+                    info,
+                )
+            except TypeError:
+                logger.debug("Caught TypeError in graphene_django_optimizer, falling back to un-optimized query")
+                queryset_ids = RelationshipAssociation.objects.filter(**query_params).values_list(
+                    f"{peer_side}_id", flat=True
+                )
         else:
             # Get objects that are peers for this relationship, regardless of side
-            queryset_ids = list(
-                gql_optimizer.query(
+            try:
+                queryset_ids = list(
+                    gql_optimizer.query(
+                        RelationshipAssociation.objects.filter(source_id=self.pk, **query_params).values_list(
+                            "destination_id", flat=True
+                        ),
+                        info,
+                    )
+                )
+                queryset_ids += list(
+                    gql_optimizer.query(
+                        RelationshipAssociation.objects.filter(destination_id=self.pk, **query_params).values_list(
+                            "source_id", flat=True
+                        ),
+                        info,
+                    )
+                )
+            except TypeError:
+                logger.debug("Caught TypeError in graphene_django_optimizer, falling back to un-optimized query")
+                queryset_ids = list(
                     RelationshipAssociation.objects.filter(source_id=self.pk, **query_params).values_list(
                         "destination_id", flat=True
                     ),
-                    info,
                 )
-            )
-            queryset_ids += list(
-                gql_optimizer.query(
+                queryset_ids += list(
                     RelationshipAssociation.objects.filter(destination_id=self.pk, **query_params).values_list(
                         "source_id", flat=True
                     ),
-                    info,
                 )
-            )
 
         if relationship.has_many(peer_side):
             return gql_optimizer.query(peer_model.objects.filter(id__in=queryset_ids), info)
 
-        return gql_optimizer.query(peer_model.objects.filter(id__in=queryset_ids).first(), info)
+        # Also apparently a graphene_django_optimizer bug - in the same query case as described above, here we may see:
+        # AttributeError: object has no attribute "only"
+        try:
+            return gql_optimizer.query(peer_model.objects.filter(id__in=queryset_ids).first(), info)
+        except AttributeError:
+            logger.debug("Caught AttributeError in graphene_django_optimizer, falling back to un-optimized query")
+            return peer_model.objects.filter(id__in=queryset_ids).first()
 
     resolve_relationship.__name__ = resolver_name
     return resolve_relationship


Test patch:
diff --git a/nautobot/core/tests/test_graphql.py b/nautobot/core/tests/test_graphql.py
--- a/nautobot/core/tests/test_graphql.py
+++ b/nautobot/core/tests/test_graphql.py
@@ -1,4 +1,5 @@
 import types
+from unittest import skip
 import uuid
 
 from django.contrib.auth import get_user_model
@@ -56,6 +57,7 @@
     ConfigContext,
     GraphQLQuery,
     Relationship,
+    RelationshipAssociation,
     Status,
     Webhook,
 )
@@ -69,7 +71,6 @@
 
 
 class GraphQLTestCase(TestCase):
-    @classmethod
     def setUp(self):
         self.user = create_test_user("graphql_testuser")
         GraphQLQuery.objects.create(name="GQL 1", slug="gql-1", query="{ query: sites {name} }")
@@ -621,164 +622,160 @@ def test_graphql_query_format(self):
 
 
 class GraphQLQueryTest(TestCase):
-    def setUp(self):
+    """Execute various GraphQL queries and verify their correct responses."""
+
+    @classmethod
+    def setUpTestData(cls):
         """Initialize the Database with some datas."""
-        super().setUp()
-        self.user = User.objects.create(username="Super User", is_active=True, is_superuser=True)
+        super().setUpTestData()
+        cls.user = User.objects.create(username="Super User", is_active=True, is_superuser=True)
 
         # Initialize fake request that will be required to execute GraphQL query
-        self.request = RequestFactory().request(SERVER_NAME="WebRequestContext")
-        self.request.id = uuid.uuid4()
-        self.request.user = self.user
-
-        self.backend = get_default_backend()
-        self.schema = graphene_settings.SCHEMA
+        cls.request = RequestFactory().request(SERVER_NAME="WebRequestContext")
+        cls.request.id = uuid.uuid4()
+        cls.request.user = cls.user
 
         # Populate Data
         manufacturer = Manufacturer.objects.create(name="Manufacturer 1", slug="manufacturer-1")
-        self.devicetype = DeviceType.objects.create(
+        cls.devicetype = DeviceType.objects.create(
             manufacturer=manufacturer, model="Device Type 1", slug="device-type-1"
         )
-        self.devicerole1 = DeviceRole.objects.create(name="Device Role 1", slug="device-role-1")
-        self.devicerole2 = DeviceRole.objects.create(name="Device Role 2", slug="device-role-2")
-        self.status1 = Status.objects.create(name="status1", slug="status1")
-        self.status2 = Status.objects.create(name="status2", slug="status2")
-        self.region1 = Region.objects.create(name="Region1", slug="region1")
-        self.region2 = Region.objects.create(name="Region2", slug="region2")
-        self.site1 = Site.objects.create(
-            name="Site-1", slug="site-1", asn=65000, status=self.status1, region=self.region1
-        )
-        self.site2 = Site.objects.create(
-            name="Site-2", slug="site-2", asn=65099, status=self.status2, region=self.region2
-        )
-        self.rack1 = Rack.objects.create(name="Rack 1", site=self.site1)
-        self.rack2 = Rack.objects.create(name="Rack 2", site=self.site2)
-        self.tenant1 = Tenant.objects.create(name="Tenant 1", slug="tenant-1")
-        self.tenant2 = Tenant.objects.create(name="Tenant 2", slug="tenant-2")
-
-        self.vlan1 = VLAN.objects.create(name="VLAN 1", vid=100, site=self.site1)
-        self.vlan2 = VLAN.objects.create(name="VLAN 2", vid=200, site=self.site2)
-
-        self.device1 = Device.objects.create(
+        cls.devicerole1 = DeviceRole.objects.create(name="Device Role 1", slug="device-role-1")
+        cls.devicerole2 = DeviceRole.objects.create(name="Device Role 2", slug="device-role-2")
+        cls.status1 = Status.objects.create(name="status1", slug="status1")
+        cls.status2 = Status.objects.create(name="status2", slug="status2")
+        cls.region1 = Region.objects.create(name="Region1", slug="region1")
+        cls.region2 = Region.objects.create(name="Region2", slug="region2")
+        cls.site1 = Site.objects.create(name="Site-1", slug="site-1", asn=65000, status=cls.status1, region=cls.region1)
+        cls.site2 = Site.objects.create(name="Site-2", slug="site-2", asn=65099, status=cls.status2, region=cls.region2)
+        cls.rack1 = Rack.objects.create(name="Rack 1", site=cls.site1)
+        cls.rack2 = Rack.objects.create(name="Rack 2", site=cls.site2)
+        cls.tenant1 = Tenant.objects.create(name="Tenant 1", slug="tenant-1")
+        cls.tenant2 = Tenant.objects.create(name="Tenant 2", slug="tenant-2")
+
+        cls.vlan1 = VLAN.objects.create(name="VLAN 1", vid=100, site=cls.site1)
+        cls.vlan2 = VLAN.objects.create(name="VLAN 2", vid=200, site=cls.site2)
+
+        cls.device1 = Device.objects.create(
             name="Device 1",
-            device_type=self.devicetype,
-            device_role=self.devicerole1,
-            site=self.site1,
-            status=self.status1,
-            rack=self.rack1,
-            tenant=self.tenant1,
+            device_type=cls.devicetype,
+            device_role=cls.devicerole1,
+            site=cls.site1,
+            status=cls.status1,
+            rack=cls.rack1,
+            tenant=cls.tenant1,
             face="front",
             comments="First Device",
         )
 
-        self.device1_rear_ports = (
-            RearPort.objects.create(device=self.device1, name="Rear Port 1", type=PortTypeChoices.TYPE_8P8C),
-            RearPort.objects.create(device=self.device1, name="Rear Port 2", type=PortTypeChoices.TYPE_8P8C),
-            RearPort.objects.create(device=self.device1, name="Rear Port 3", type=PortTypeChoices.TYPE_8P8C),
-            RearPort.objects.create(device=self.device1, name="Rear Port 4", type=PortTypeChoices.TYPE_8P8C),
+        cls.device1_rear_ports = (
+            RearPort.objects.create(device=cls.device1, name="Rear Port 1", type=PortTypeChoices.TYPE_8P8C),
+            RearPort.objects.create(device=cls.device1, name="Rear Port 2", type=PortTypeChoices.TYPE_8P8C),
+            RearPort.objects.create(device=cls.device1, name="Rear Port 3", type=PortTypeChoices.TYPE_8P8C),
+            RearPort.objects.create(device=cls.device1, name="Rear Port 4", type=PortTypeChoices.TYPE_8P8C),
         )
 
-        self.device1_frontports = [
+        cls.device1_frontports = [
             FrontPort.objects.create(
-                device=self.device1,
+                device=cls.device1,
                 name="Front Port 1",
                 type=PortTypeChoices.TYPE_8P8C,
-                rear_port=self.device1_rear_ports[0],
+                rear_port=cls.device1_rear_ports[0],
             ),
             FrontPort.objects.create(
-                device=self.device1,
+                device=cls.device1,
                 name="Front Port 2",
                 type=PortTypeChoices.TYPE_8P8C,
-                rear_port=self.device1_rear_ports[1],
+                rear_port=cls.device1_rear_ports[1],
             ),
             FrontPort.objects.create(
-                device=self.device1,
+                device=cls.device1,
                 name="Front Port 3",
                 type=PortTypeChoices.TYPE_8P8C,
-                rear_port=self.device1_rear_ports[2],
+                rear_port=cls.device1_rear_ports[2],
             ),
             FrontPort.objects.create(
-                device=self.device1,
+                device=cls.device1,
                 name="Front Port 4",
                 type=PortTypeChoices.TYPE_8P8C,
-                rear_port=self.device1_rear_ports[3],
+                rear_port=cls.device1_rear_ports[3],
             ),
         ]
 
-        self.interface11 = Interface.objects.create(
+        cls.interface11 = Interface.objects.create(
             name="Int1",
             type=InterfaceTypeChoices.TYPE_VIRTUAL,
-            device=self.device1,
+            device=cls.device1,
             mac_address="00:11:11:11:11:11",
             mode=InterfaceModeChoices.MODE_ACCESS,
-            untagged_vlan=self.vlan1,
+            untagged_vlan=cls.vlan1,
         )
-        self.interface12 = Interface.objects.create(
+        cls.interface12 = Interface.objects.create(
             name="Int2",
             type=InterfaceTypeChoices.TYPE_VIRTUAL,
-            device=self.device1,
+            device=cls.device1,
         )
-        self.ipaddr1 = IPAddress.objects.create(
-            address="10.0.1.1/24", status=self.status1, assigned_object=self.interface11
+        cls.ipaddr1 = IPAddress.objects.create(
+            address="10.0.1.1/24", status=cls.status1, assigned_object=cls.interface11
         )
 
-        self.device2 = Device.objects.create(
+        cls.device2 = Device.objects.create(
             name="Device 2",
-            device_type=self.devicetype,
-            device_role=self.devicerole2,
-            site=self.site1,
-            status=self.status2,
-            rack=self.rack2,
-            tenant=self.tenant2,
+            device_type=cls.devicetype,
+            device_role=cls.devicerole2,
+            site=cls.site1,
+            status=cls.status2,
+            rack=cls.rack2,
+            tenant=cls.tenant2,
             face="rear",
         )
 
-        self.interface21 = Interface.objects.create(
+        cls.interface21 = Interface.objects.create(
             name="Int1",
             type=InterfaceTypeChoices.TYPE_VIRTUAL,
-            device=self.device2,
-            untagged_vlan=self.vlan2,
+            device=cls.device2,
+            untagged_vlan=cls.vlan2,
             mode=InterfaceModeChoices.MODE_ACCESS,
         )
-        self.interface22 = Interface.objects.create(
-            name="Int2", type=InterfaceTypeChoices.TYPE_1GE_FIXED, device=self.device2, mac_address="00:12:12:12:12:12"
+        cls.interface22 = Interface.objects.create(
+            name="Int2", type=InterfaceTypeChoices.TYPE_1GE_FIXED, device=cls.device2, mac_address="00:12:12:12:12:12"
         )
-        self.ipaddr2 = IPAddress.objects.create(
-            address="10.0.2.1/30", status=self.status2, assigned_object=self.interface12
+        cls.ipaddr2 = IPAddress.objects.create(
+            address="10.0.2.1/30", status=cls.status2, assigned_object=cls.interface12
         )
 
-        self.device3 = Device.objects.create(
+        cls.device3 = Device.objects.create(
             name="Device 3",
-            device_type=self.devicetype,
-            device_role=self.devicerole1,
-            site=self.site2,
-            status=self.status1,
+            device_type=cls.devicetype,
+            device_role=cls.devicerole1,
+            site=cls.site2,
+            status=cls.status1,
         )
 
-        self.interface31 = Interface.objects.create(
-            name="Int1", type=InterfaceTypeChoices.TYPE_VIRTUAL, device=self.device3
+        cls.interface31 = Interface.objects.create(
+            name="Int1", type=InterfaceTypeChoices.TYPE_VIRTUAL, device=cls.device3
         )
-        self.interface31 = Interface.objects.create(
+        cls.interface31 = Interface.objects.create(
             name="Mgmt1",
             type=InterfaceTypeChoices.TYPE_VIRTUAL,
-            device=self.device3,
+            device=cls.device3,
             mgmt_only=True,
             enabled=False,
         )
 
-        self.cable1 = Cable.objects.create(
-            termination_a=self.interface11,
-            termination_b=self.interface12,
-            status=self.status1,
+        cls.cable1 = Cable.objects.create(
+            termination_a=cls.interface11,
+            termination_b=cls.interface12,
+            status=cls.status1,
         )
-        self.cable2 = Cable.objects.create(
-            termination_a=self.interface31,
-            termination_b=self.interface21,
-            status=self.status2,
+        cls.cable2 = Cable.objects.create(
+            termination_a=cls.interface31,
+            termination_b=cls.interface21,
+            status=cls.status2,
         )
 
         context1 = ConfigContext.objects.create(name="context 1", weight=101, data={"a": 123, "b": 456, "c": 777})
-        context1.regions.add(self.region1)
+        context1.regions.add(cls.region1)
 
         Provider.objects.create(name="provider 1", slug="provider-1", asn=1)
         Provider.objects.create(name="provider 2", slug="provider-2", asn=4294967295)
@@ -790,18 +787,65 @@ def setUp(self):
 
         clustertype = ClusterType.objects.create(name="Cluster Type 1", slug="cluster-type-1")
         cluster = Cluster.objects.create(name="Cluster 1", type=clustertype)
-        self.virtualmachine = VirtualMachine.objects.create(
+        cls.virtualmachine = VirtualMachine.objects.create(
             name="Virtual Machine 1",
             cluster=cluster,
-            status=self.status1,
+            status=cls.status1,
         )
-        self.vminterface = VMInterface.objects.create(
-            virtual_machine=self.virtualmachine,
+        cls.vminterface = VMInterface.objects.create(
+            virtual_machine=cls.virtualmachine,
             name="eth0",
         )
-        self.vmipaddr = IPAddress.objects.create(
-            address="1.1.1.1/32", status=self.status1, assigned_object=self.vminterface
+        cls.vmipaddr = IPAddress.objects.create(
+            address="1.1.1.1/32", status=cls.status1, assigned_object=cls.vminterface
+        )
+
+        cls.relationship_o2o_1 = Relationship(
+            name="Device to VirtualMachine",
+            slug="device-to-vm",
+            source_type=ContentType.objects.get_for_model(Device),
+            destination_type=ContentType.objects.get_for_model(VirtualMachine),
+            type="one-to-one",
+        )
+        cls.relationship_o2o_1.validated_save()
+
+        cls.ro2o_assoc_1 = RelationshipAssociation(
+            relationship=cls.relationship_o2o_1,
+            source=cls.device1,
+            destination=cls.virtualmachine,
+        )
+        cls.ro2o_assoc_1.validated_save()
+
+        cls.relationship_m2ms_1 = Relationship(
+            name="Device Group",
+            slug="device-group",
+            source_type=ContentType.objects.get_for_model(Device),
+            destination_type=ContentType.objects.get_for_model(Device),
+            type="symmetric-many-to-many",
+        )
+        cls.relationship_m2ms_1.validated_save()
+
+        cls.rm2ms_assoc_1 = RelationshipAssociation(
+            relationship=cls.relationship_m2ms_1,
+            source=cls.device1,
+            destination=cls.device2,
+        )
+        cls.rm2ms_assoc_1.validated_save()
+        cls.rm2ms_assoc_2 = RelationshipAssociation(
+            relationship=cls.relationship_m2ms_1,
+            source=cls.device2,
+            destination=cls.device3,
+        )
+        cls.rm2ms_assoc_2.validated_save()
+        cls.rm2ms_assoc_3 = RelationshipAssociation(
+            relationship=cls.relationship_m2ms_1,
+            source=cls.device3,
+            destination=cls.device1,
         )
+        cls.rm2ms_assoc_3.validated_save()
+
+        cls.backend = get_default_backend()
+        cls.schema = graphene_settings.SCHEMA
 
     def execute_query(self, query, variables=None):
 
@@ -852,6 +896,38 @@ def test_query_config_context_and_custom_field_data(self):
         self.assertEqual(custom_field_data[0], {})
         self.assertEqual(result.data["device"]["_custom_field_data"], {})
 
+    @skip("Works in isolation, fails as part of the overall test suite due to issue #446")
+    @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
+    def test_query_relationship_associations(self):
+        """Test queries involving relationship associations."""
+
+        # Query testing for https://github.com/nautobot/nautobot/issues/1228
+        query = """
+        query {
+            device (id: "%s") {
+                name
+                rel_device_to_vm {
+                    id
+                }
+                rel_device_group {
+                    id
+                }
+            }
+        }
+        """ % (
+            self.device1.id,
+        )
+        result = self.execute_query(query)
+
+        self.assertIsInstance(result.data, dict, result)
+        self.assertIsInstance(result.data["device"], dict, result)
+        self.assertEqual(result.data["device"]["name"], self.device1.name)
+        self.assertIsInstance(result.data["device"]["rel_device_to_vm"], dict, result)
+        self.assertEqual(result.data["device"]["rel_device_to_vm"]["id"], str(self.virtualmachine.id))
+        self.assertIsInstance(result.data["device"]["rel_device_group"], list, result)
+        self.assertIn(str(self.device2.id), set(item["id"] for item in result.data["device"]["rel_device_group"]))
+        self.assertIn(str(self.device3.id), set(item["id"] for item in result.data["device"]["rel_device_group"]))
+
     @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
     def test_query_device_role_filter(self):