We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN 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
    }
  }
}
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `nautobot/core/graphql/generators.py`
Content:
```
1 """Library of generators for GraphQL."""
2 
3 import logging
4 
5 import graphene
6 import graphene_django_optimizer as gql_optimizer
7 from graphql import GraphQLError
8 from graphene_django import DjangoObjectType
9 
10 from nautobot.core.graphql.utils import str_to_var_name, get_filtering_args_from_filterset
11 from nautobot.extras.choices import RelationshipSideChoices
12 from nautobot.extras.models import RelationshipAssociation
13 from nautobot.utilities.utils import get_filterset_for_model
14 
15 logger = logging.getLogger("nautobot.graphql.generators")
16 RESOLVER_PREFIX = "resolve_"
17 
18 
19 def generate_restricted_queryset():
20     """
21     Generate a function to return a restricted queryset compatible with the internal permissions system.
22 
23     Note that for built-in models such as ContentType the queryset has no `restrict` method, so we have to
24     fail gracefully in that case.
25     """
26 
27     def get_queryset(queryset, info):
28         if not hasattr(queryset, "restrict"):
29             logger.debug(f"Queryset {queryset} is not restrictable")
30             return queryset
31         return queryset.restrict(info.context.user, "view")
32 
33     return get_queryset
34 
35 
36 def generate_null_choices_resolver(name, resolver_name):
37     """
38     Generate function to resolve appropriate type when a field has `null=False` (default), `blank=True`, and
39     `choices` defined.
40 
41     Args:
42         name (str): name of the field to resolve
43         resolver_name (str): name of the resolver as declare in DjangoObjectType
44     """
45 
46     def resolve_fields_w_choices(model, info, **kwargs):
47         field_value = getattr(model, name)
48         if field_value:
49             return field_value
50         return None
51 
52     resolve_fields_w_choices.__name__ = resolver_name
53     return resolve_fields_w_choices
54 
55 
56 def generate_filter_resolver(schema_type, resolver_name, field_name):
57     """
58     Generate function to resolve OneToMany filtering.
59 
60     Args:
61         schema_type (DjangoObjectType): DjangoObjectType for a given model
62         resolver_name (str): name of the resolver
63         field_name (str): name of OneToMany field to filter
64     """
65     filterset_class = schema_type._meta.filterset_class
66 
67     def resolve_filter(self, *args, **kwargs):
68         if not filterset_class:
69             return getattr(self, field_name).all()
70 
71         resolved_obj = filterset_class(kwargs, getattr(self, field_name).all())
72 
73         # Check result filter for errors.
74         if not resolved_obj.errors:
75             return resolved_obj.qs.all()
76 
77         errors = {}
78 
79         # Build error message from results
80         # Error messages are collected from each filter object
81         for key in resolved_obj.errors:
82             errors[key] = resolved_obj.errors[key]
83 
84         # Raising this exception will send the error message in the response of the GraphQL request
85         raise GraphQLError(errors)
86 
87     resolve_filter.__name__ = resolver_name
88     return resolve_filter
89 
90 
91 def generate_custom_field_resolver(name, resolver_name):
92     """Generate function to resolve each custom field within each DjangoObjectType.
93 
94     Args:
95         name (str): name of the custom field to resolve
96         resolver_name (str): name of the resolver as declare in DjangoObjectType
97     """
98 
99     def resolve_custom_field(self, info, **kwargs):
100         return self.cf.get(name, None)
101 
102     resolve_custom_field.__name__ = resolver_name
103     return resolve_custom_field
104 
105 
106 def generate_computed_field_resolver(name, resolver_name):
107     """Generate an instance method for resolving an individual computed field within a given DjangoObjectType.
108 
109     Args:
110         name (str): name of the computed field to resolve
111         resolver_name (str): name of the resolver as declare in DjangoObjectType
112     """
113 
114     def resolve_computed_field(self, info, **kwargs):
115         return self.get_computed_field(slug=name)
116 
117     resolve_computed_field.__name__ = resolver_name
118     return resolve_computed_field
119 
120 
121 def generate_relationship_resolver(name, resolver_name, relationship, side, peer_model):
122     """Generate function to resolve each custom relationship within each DjangoObjectType.
123 
124     Args:
125         name (str): name of the custom field to resolve
126         resolver_name (str): name of the resolver as declare in DjangoObjectType
127         relationship (Relationship): Relationship object to generate a resolver for
128         side (str): side of the relationship to use for the resolver
129         peer_model (Model): Django Model of the peer of this relationship
130     """
131 
132     def resolve_relationship(self, info, **kwargs):
133         """Return a queryset or an object depending on the type of the relationship."""
134         peer_side = RelationshipSideChoices.OPPOSITE[side]
135         query_params = {"relationship": relationship}
136         if not relationship.symmetric:
137             # Get the objects on the other side of this relationship
138             query_params[f"{side}_id"] = self.pk
139             queryset_ids = gql_optimizer.query(
140                 RelationshipAssociation.objects.filter(**query_params).values_list(f"{peer_side}_id", flat=True), info
141             )
142         else:
143             # Get objects that are peers for this relationship, regardless of side
144             queryset_ids = list(
145                 gql_optimizer.query(
146                     RelationshipAssociation.objects.filter(source_id=self.pk, **query_params).values_list(
147                         "destination_id", flat=True
148                     ),
149                     info,
150                 )
151             )
152             queryset_ids += list(
153                 gql_optimizer.query(
154                     RelationshipAssociation.objects.filter(destination_id=self.pk, **query_params).values_list(
155                         "source_id", flat=True
156                     ),
157                     info,
158                 )
159             )
160 
161         if relationship.has_many(peer_side):
162             return gql_optimizer.query(peer_model.objects.filter(id__in=queryset_ids), info)
163 
164         return gql_optimizer.query(peer_model.objects.filter(id__in=queryset_ids).first(), info)
165 
166     resolve_relationship.__name__ = resolver_name
167     return resolve_relationship
168 
169 
170 def generate_schema_type(app_name: str, model: object) -> DjangoObjectType:
171     """
172     Take a Django model and generate a Graphene Type class definition.
173 
174     Args:
175         app_name (str): name of the application or plugin the Model is part of.
176         model (object): Django Model
177 
178     Example:
179         For a model with a name of "Device", the following class definition is generated:
180 
181         class DeviceType(DjangoObjectType):
182             Meta:
183                 model = Device
184                 fields = ["__all__"]
185 
186         If a FilterSet exists for this model at
187         '<app_name>.filters.<ModelName>FilterSet' the filterset will be stored in
188         filterset_class as follows:
189 
190         class DeviceType(DjangoObjectType):
191             Meta:
192                 model = Device
193                 fields = ["__all__"]
194                 filterset_class = DeviceFilterSet
195     """
196 
197     main_attrs = {}
198     meta_attrs = {"model": model, "fields": "__all__"}
199 
200     # We'll attempt to find a FilterSet corresponding to the model
201     # Not all models have a FilterSet defined so the function return none if it can't find a filterset
202     meta_attrs["filterset_class"] = get_filterset_for_model(model)
203 
204     main_attrs["Meta"] = type("Meta", (object,), meta_attrs)
205 
206     schema_type = type(f"{model.__name__}Type", (DjangoObjectType,), main_attrs)
207     return schema_type
208 
209 
210 def generate_list_search_parameters(schema_type):
211     """Generate list of query parameters for the list resolver based on a filterset."""
212 
213     search_params = {}
214     if schema_type._meta.filterset_class is not None:
215         search_params = get_filtering_args_from_filterset(
216             schema_type._meta.filterset_class,
217         )
218 
219     return search_params
220 
221 
222 def generate_single_item_resolver(schema_type, resolver_name):
223     """Generate a resolver for a single element of schema_type
224 
225     Args:
226         schema_type (DjangoObjectType): DjangoObjectType for a given model
227         resolver_name (str): name of the resolver
228 
229     Returns:
230         callable: Resolver function for a single element
231     """
232     model = schema_type._meta.model
233 
234     def single_resolver(self, info, **kwargs):
235 
236         obj_id = kwargs.get("id", None)
237         if obj_id:
238             return gql_optimizer.query(
239                 model.objects.restrict(info.context.user, "view").filter(pk=obj_id), info
240             ).first()
241         return None
242 
243     single_resolver.__name__ = resolver_name
244     return single_resolver
245 
246 
247 def generate_list_resolver(schema_type, resolver_name):
248     """
249     Generate resolver for a list of schema_type.
250 
251     If a filterset_class is associated with the schema_type,
252     the resolver will pass all arguments received to the FilterSet
253     If not, it will return a restricted queryset for all objects
254 
255     Args:
256         schema_type (DjangoObjectType): DjangoObjectType for a given model
257         resolver_name (str): name of the resolver
258 
259     Returns:
260         callable: Resolver function for list of element
261     """
262     model = schema_type._meta.model
263 
264     def list_resolver(self, info, **kwargs):
265         filterset_class = schema_type._meta.filterset_class
266         if filterset_class is not None:
267             resolved_obj = filterset_class(kwargs, model.objects.restrict(info.context.user, "view").all())
268 
269             # Check result filter for errors.
270             if resolved_obj.errors:
271                 errors = {}
272 
273                 # Build error message from results
274                 # Error messages are collected from each filter object
275                 for key in resolved_obj.errors:
276                     errors[key] = resolved_obj.errors[key]
277 
278                 # Raising this exception will send the error message in the response of the GraphQL request
279                 raise GraphQLError(errors)
280 
281             return gql_optimizer.query(resolved_obj.qs.all(), info)
282 
283         return gql_optimizer.query(model.objects.restrict(info.context.user, "view").all(), info)
284 
285     list_resolver.__name__ = resolver_name
286     return list_resolver
287 
288 
289 def generate_attrs_for_schema_type(schema_type):
290     """Generate both attributes and resolvers for a given schema_type.
291 
292     Args:
293         schema_type (DjangoObjectType): DjangoObjectType for a given model
294 
295     Returns:
296         dict: Dict of attributes ready to merge into the QueryMixin class
297     """
298     attrs = {}
299     model = schema_type._meta.model
300 
301     single_item_name = str_to_var_name(model._meta.verbose_name)
302     list_name = str_to_var_name(model._meta.verbose_name_plural)
303 
304     # Define Attributes for single item and list with their search parameters
305     search_params = generate_list_search_parameters(schema_type)
306     attrs[single_item_name] = graphene.Field(schema_type, id=graphene.ID())
307     attrs[list_name] = graphene.List(schema_type, **search_params)
308 
309     # Define Resolvers for both single item and list
310     single_item_resolver_name = f"{RESOLVER_PREFIX}{single_item_name}"
311     list_resolver_name = f"{RESOLVER_PREFIX}{list_name}"
312     attrs[single_item_resolver_name] = generate_single_item_resolver(schema_type, single_item_resolver_name)
313     attrs[list_resolver_name] = generate_list_resolver(schema_type, list_resolver_name)
314 
315     return attrs
316
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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 @@
         """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

{"golden_diff": "diff --git a/nautobot/core/graphql/generators.py b/nautobot/core/graphql/generators.py\n--- a/nautobot/core/graphql/generators.py\n+++ b/nautobot/core/graphql/generators.py\n@@ -133,35 +133,69 @@\n         \"\"\"Return a queryset or an object depending on the type of the relationship.\"\"\"\n         peer_side = RelationshipSideChoices.OPPOSITE[side]\n         query_params = {\"relationship\": relationship}\n+        # https://github.com/nautobot/nautobot/issues/1228\n+        # If querying for **only** the ID of the related object, for example:\n+        # { device(id:\"...\") { ... rel_my_relationship { id } } }\n+        # we will get this exception:\n+        # TypeError: Cannot call select_related() after .values() or .values_list()\n+        # This appears to be a bug in graphene_django_optimizer but I haven't found a known issue on GitHub.\n+        # For now we just work around it by catching the exception and retrying without optimization, below...\n         if not relationship.symmetric:\n             # Get the objects on the other side of this relationship\n             query_params[f\"{side}_id\"] = self.pk\n-            queryset_ids = gql_optimizer.query(\n-                RelationshipAssociation.objects.filter(**query_params).values_list(f\"{peer_side}_id\", flat=True), info\n-            )\n+\n+            try:\n+                queryset_ids = gql_optimizer.query(\n+                    RelationshipAssociation.objects.filter(**query_params).values_list(f\"{peer_side}_id\", flat=True),\n+                    info,\n+                )\n+            except TypeError:\n+                logger.debug(\"Caught TypeError in graphene_django_optimizer, falling back to un-optimized query\")\n+                queryset_ids = RelationshipAssociation.objects.filter(**query_params).values_list(\n+                    f\"{peer_side}_id\", flat=True\n+                )\n         else:\n             # Get objects that are peers for this relationship, regardless of side\n-            queryset_ids = list(\n-                gql_optimizer.query(\n+            try:\n+                queryset_ids = list(\n+                    gql_optimizer.query(\n+                        RelationshipAssociation.objects.filter(source_id=self.pk, **query_params).values_list(\n+                            \"destination_id\", flat=True\n+                        ),\n+                        info,\n+                    )\n+                )\n+                queryset_ids += list(\n+                    gql_optimizer.query(\n+                        RelationshipAssociation.objects.filter(destination_id=self.pk, **query_params).values_list(\n+                            \"source_id\", flat=True\n+                        ),\n+                        info,\n+                    )\n+                )\n+            except TypeError:\n+                logger.debug(\"Caught TypeError in graphene_django_optimizer, falling back to un-optimized query\")\n+                queryset_ids = list(\n                     RelationshipAssociation.objects.filter(source_id=self.pk, **query_params).values_list(\n                         \"destination_id\", flat=True\n                     ),\n-                    info,\n                 )\n-            )\n-            queryset_ids += list(\n-                gql_optimizer.query(\n+                queryset_ids += list(\n                     RelationshipAssociation.objects.filter(destination_id=self.pk, **query_params).values_list(\n                         \"source_id\", flat=True\n                     ),\n-                    info,\n                 )\n-            )\n \n         if relationship.has_many(peer_side):\n             return gql_optimizer.query(peer_model.objects.filter(id__in=queryset_ids), info)\n \n-        return gql_optimizer.query(peer_model.objects.filter(id__in=queryset_ids).first(), info)\n+        # Also apparently a graphene_django_optimizer bug - in the same query case as described above, here we may see:\n+        # AttributeError: object has no attribute \"only\"\n+        try:\n+            return gql_optimizer.query(peer_model.objects.filter(id__in=queryset_ids).first(), info)\n+        except AttributeError:\n+            logger.debug(\"Caught AttributeError in graphene_django_optimizer, falling back to un-optimized query\")\n+            return peer_model.objects.filter(id__in=queryset_ids).first()\n \n     resolve_relationship.__name__ = resolver_name\n     return resolve_relationship\n", "issue": "Some GraphQL Queries for Relationships fail on 1.2\n<!--\r\n    NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED.\r\n\r\n    This form is only for reporting reproducible bugs. If you need assistance\r\n    with Nautobot installation, or if you have a general question, please start a\r\n    discussion instead: https://github.com/nautobot/nautobot/discussions\r\n\r\n    Please describe the environment in which you are running Nautobot. Be sure\r\n    that you are running an unmodified instance of the latest stable release\r\n    before submitting a bug report, and that any plugins have been disabled.\r\n-->\r\n### Environment\r\n* Python version: 3.8\r\n* Nautobot version: 1.2.2\r\n\r\n<!--\r\n    Describe in detail the exact steps that someone else can take to reproduce\r\n    this bug using the current stable release of Nautobot. Begin with the\r\n    creation of any necessary database objects and call out every operation\r\n    being performed explicitly. If reporting a bug in the REST API, be sure to\r\n    reconstruct the raw HTTP request(s) being made: Don't rely on a client\r\n    library such as pynautobot.\r\n-->\r\n### Steps to Reproduce\r\n1. Have a relationship e.g. between cluster and devices (one to many)\r\n2. Run a graphql query like: \r\n```{\r\ndevice(id:\"bb91df1c-61ec-4a85-8138-cdb97c6c890e\")\r\n{\r\n  name\r\n  rel_cluster_to_device{\r\n    id\r\n  }\r\n  }\r\n}\r\n```\r\n\r\n<!-- What did you expect to happen? -->\r\n### Expected Behavior\r\nGet the ID of the relationship\r\n\r\n<!-- What happened instead? -->\r\n### Observed Behavior\r\n```\r\n{\r\n  \"errors\": [\r\n    {\r\n      \"message\": \"Cannot call only() after .values() or .values_list()\",\r\n      \"locations\": [\r\n        {\r\n          \"line\": 5,\r\n          \"column\": 3\r\n        }\r\n      ],\r\n      \"path\": [\r\n        \"device\",\r\n        \"rel_cluster_to_device\"\r\n      ]\r\n    }\r\n  ],\r\n  \"data\": {\r\n    \"device\": {\r\n      \"name\": \"R10-S3\",\r\n      \"rel_cluster_to_device\": null\r\n    }\r\n  }\r\n}\r\n```\n", "before_files": [{"content": "\"\"\"Library of generators for GraphQL.\"\"\"\n\nimport logging\n\nimport graphene\nimport graphene_django_optimizer as gql_optimizer\nfrom graphql import GraphQLError\nfrom graphene_django import DjangoObjectType\n\nfrom nautobot.core.graphql.utils import str_to_var_name, get_filtering_args_from_filterset\nfrom nautobot.extras.choices import RelationshipSideChoices\nfrom nautobot.extras.models import RelationshipAssociation\nfrom nautobot.utilities.utils import get_filterset_for_model\n\nlogger = logging.getLogger(\"nautobot.graphql.generators\")\nRESOLVER_PREFIX = \"resolve_\"\n\n\ndef generate_restricted_queryset():\n    \"\"\"\n    Generate a function to return a restricted queryset compatible with the internal permissions system.\n\n    Note that for built-in models such as ContentType the queryset has no `restrict` method, so we have to\n    fail gracefully in that case.\n    \"\"\"\n\n    def get_queryset(queryset, info):\n        if not hasattr(queryset, \"restrict\"):\n            logger.debug(f\"Queryset {queryset} is not restrictable\")\n            return queryset\n        return queryset.restrict(info.context.user, \"view\")\n\n    return get_queryset\n\n\ndef generate_null_choices_resolver(name, resolver_name):\n    \"\"\"\n    Generate function to resolve appropriate type when a field has `null=False` (default), `blank=True`, and\n    `choices` defined.\n\n    Args:\n        name (str): name of the field to resolve\n        resolver_name (str): name of the resolver as declare in DjangoObjectType\n    \"\"\"\n\n    def resolve_fields_w_choices(model, info, **kwargs):\n        field_value = getattr(model, name)\n        if field_value:\n            return field_value\n        return None\n\n    resolve_fields_w_choices.__name__ = resolver_name\n    return resolve_fields_w_choices\n\n\ndef generate_filter_resolver(schema_type, resolver_name, field_name):\n    \"\"\"\n    Generate function to resolve OneToMany filtering.\n\n    Args:\n        schema_type (DjangoObjectType): DjangoObjectType for a given model\n        resolver_name (str): name of the resolver\n        field_name (str): name of OneToMany field to filter\n    \"\"\"\n    filterset_class = schema_type._meta.filterset_class\n\n    def resolve_filter(self, *args, **kwargs):\n        if not filterset_class:\n            return getattr(self, field_name).all()\n\n        resolved_obj = filterset_class(kwargs, getattr(self, field_name).all())\n\n        # Check result filter for errors.\n        if not resolved_obj.errors:\n            return resolved_obj.qs.all()\n\n        errors = {}\n\n        # Build error message from results\n        # Error messages are collected from each filter object\n        for key in resolved_obj.errors:\n            errors[key] = resolved_obj.errors[key]\n\n        # Raising this exception will send the error message in the response of the GraphQL request\n        raise GraphQLError(errors)\n\n    resolve_filter.__name__ = resolver_name\n    return resolve_filter\n\n\ndef generate_custom_field_resolver(name, resolver_name):\n    \"\"\"Generate function to resolve each custom field within each DjangoObjectType.\n\n    Args:\n        name (str): name of the custom field to resolve\n        resolver_name (str): name of the resolver as declare in DjangoObjectType\n    \"\"\"\n\n    def resolve_custom_field(self, info, **kwargs):\n        return self.cf.get(name, None)\n\n    resolve_custom_field.__name__ = resolver_name\n    return resolve_custom_field\n\n\ndef generate_computed_field_resolver(name, resolver_name):\n    \"\"\"Generate an instance method for resolving an individual computed field within a given DjangoObjectType.\n\n    Args:\n        name (str): name of the computed field to resolve\n        resolver_name (str): name of the resolver as declare in DjangoObjectType\n    \"\"\"\n\n    def resolve_computed_field(self, info, **kwargs):\n        return self.get_computed_field(slug=name)\n\n    resolve_computed_field.__name__ = resolver_name\n    return resolve_computed_field\n\n\ndef generate_relationship_resolver(name, resolver_name, relationship, side, peer_model):\n    \"\"\"Generate function to resolve each custom relationship within each DjangoObjectType.\n\n    Args:\n        name (str): name of the custom field to resolve\n        resolver_name (str): name of the resolver as declare in DjangoObjectType\n        relationship (Relationship): Relationship object to generate a resolver for\n        side (str): side of the relationship to use for the resolver\n        peer_model (Model): Django Model of the peer of this relationship\n    \"\"\"\n\n    def resolve_relationship(self, info, **kwargs):\n        \"\"\"Return a queryset or an object depending on the type of the relationship.\"\"\"\n        peer_side = RelationshipSideChoices.OPPOSITE[side]\n        query_params = {\"relationship\": relationship}\n        if not relationship.symmetric:\n            # Get the objects on the other side of this relationship\n            query_params[f\"{side}_id\"] = self.pk\n            queryset_ids = gql_optimizer.query(\n                RelationshipAssociation.objects.filter(**query_params).values_list(f\"{peer_side}_id\", flat=True), info\n            )\n        else:\n            # Get objects that are peers for this relationship, regardless of side\n            queryset_ids = list(\n                gql_optimizer.query(\n                    RelationshipAssociation.objects.filter(source_id=self.pk, **query_params).values_list(\n                        \"destination_id\", flat=True\n                    ),\n                    info,\n                )\n            )\n            queryset_ids += list(\n                gql_optimizer.query(\n                    RelationshipAssociation.objects.filter(destination_id=self.pk, **query_params).values_list(\n                        \"source_id\", flat=True\n                    ),\n                    info,\n                )\n            )\n\n        if relationship.has_many(peer_side):\n            return gql_optimizer.query(peer_model.objects.filter(id__in=queryset_ids), info)\n\n        return gql_optimizer.query(peer_model.objects.filter(id__in=queryset_ids).first(), info)\n\n    resolve_relationship.__name__ = resolver_name\n    return resolve_relationship\n\n\ndef generate_schema_type(app_name: str, model: object) -> DjangoObjectType:\n    \"\"\"\n    Take a Django model and generate a Graphene Type class definition.\n\n    Args:\n        app_name (str): name of the application or plugin the Model is part of.\n        model (object): Django Model\n\n    Example:\n        For a model with a name of \"Device\", the following class definition is generated:\n\n        class DeviceType(DjangoObjectType):\n            Meta:\n                model = Device\n                fields = [\"__all__\"]\n\n        If a FilterSet exists for this model at\n        '<app_name>.filters.<ModelName>FilterSet' the filterset will be stored in\n        filterset_class as follows:\n\n        class DeviceType(DjangoObjectType):\n            Meta:\n                model = Device\n                fields = [\"__all__\"]\n                filterset_class = DeviceFilterSet\n    \"\"\"\n\n    main_attrs = {}\n    meta_attrs = {\"model\": model, \"fields\": \"__all__\"}\n\n    # We'll attempt to find a FilterSet corresponding to the model\n    # Not all models have a FilterSet defined so the function return none if it can't find a filterset\n    meta_attrs[\"filterset_class\"] = get_filterset_for_model(model)\n\n    main_attrs[\"Meta\"] = type(\"Meta\", (object,), meta_attrs)\n\n    schema_type = type(f\"{model.__name__}Type\", (DjangoObjectType,), main_attrs)\n    return schema_type\n\n\ndef generate_list_search_parameters(schema_type):\n    \"\"\"Generate list of query parameters for the list resolver based on a filterset.\"\"\"\n\n    search_params = {}\n    if schema_type._meta.filterset_class is not None:\n        search_params = get_filtering_args_from_filterset(\n            schema_type._meta.filterset_class,\n        )\n\n    return search_params\n\n\ndef generate_single_item_resolver(schema_type, resolver_name):\n    \"\"\"Generate a resolver for a single element of schema_type\n\n    Args:\n        schema_type (DjangoObjectType): DjangoObjectType for a given model\n        resolver_name (str): name of the resolver\n\n    Returns:\n        callable: Resolver function for a single element\n    \"\"\"\n    model = schema_type._meta.model\n\n    def single_resolver(self, info, **kwargs):\n\n        obj_id = kwargs.get(\"id\", None)\n        if obj_id:\n            return gql_optimizer.query(\n                model.objects.restrict(info.context.user, \"view\").filter(pk=obj_id), info\n            ).first()\n        return None\n\n    single_resolver.__name__ = resolver_name\n    return single_resolver\n\n\ndef generate_list_resolver(schema_type, resolver_name):\n    \"\"\"\n    Generate resolver for a list of schema_type.\n\n    If a filterset_class is associated with the schema_type,\n    the resolver will pass all arguments received to the FilterSet\n    If not, it will return a restricted queryset for all objects\n\n    Args:\n        schema_type (DjangoObjectType): DjangoObjectType for a given model\n        resolver_name (str): name of the resolver\n\n    Returns:\n        callable: Resolver function for list of element\n    \"\"\"\n    model = schema_type._meta.model\n\n    def list_resolver(self, info, **kwargs):\n        filterset_class = schema_type._meta.filterset_class\n        if filterset_class is not None:\n            resolved_obj = filterset_class(kwargs, model.objects.restrict(info.context.user, \"view\").all())\n\n            # Check result filter for errors.\n            if resolved_obj.errors:\n                errors = {}\n\n                # Build error message from results\n                # Error messages are collected from each filter object\n                for key in resolved_obj.errors:\n                    errors[key] = resolved_obj.errors[key]\n\n                # Raising this exception will send the error message in the response of the GraphQL request\n                raise GraphQLError(errors)\n\n            return gql_optimizer.query(resolved_obj.qs.all(), info)\n\n        return gql_optimizer.query(model.objects.restrict(info.context.user, \"view\").all(), info)\n\n    list_resolver.__name__ = resolver_name\n    return list_resolver\n\n\ndef generate_attrs_for_schema_type(schema_type):\n    \"\"\"Generate both attributes and resolvers for a given schema_type.\n\n    Args:\n        schema_type (DjangoObjectType): DjangoObjectType for a given model\n\n    Returns:\n        dict: Dict of attributes ready to merge into the QueryMixin class\n    \"\"\"\n    attrs = {}\n    model = schema_type._meta.model\n\n    single_item_name = str_to_var_name(model._meta.verbose_name)\n    list_name = str_to_var_name(model._meta.verbose_name_plural)\n\n    # Define Attributes for single item and list with their search parameters\n    search_params = generate_list_search_parameters(schema_type)\n    attrs[single_item_name] = graphene.Field(schema_type, id=graphene.ID())\n    attrs[list_name] = graphene.List(schema_type, **search_params)\n\n    # Define Resolvers for both single item and list\n    single_item_resolver_name = f\"{RESOLVER_PREFIX}{single_item_name}\"\n    list_resolver_name = f\"{RESOLVER_PREFIX}{list_name}\"\n    attrs[single_item_resolver_name] = generate_single_item_resolver(schema_type, single_item_resolver_name)\n    attrs[list_resolver_name] = generate_list_resolver(schema_type, list_resolver_name)\n\n    return attrs\n", "path": "nautobot/core/graphql/generators.py"}], "after_files": [{"content": "\"\"\"Library of generators for GraphQL.\"\"\"\n\nimport logging\n\nimport graphene\nimport graphene_django_optimizer as gql_optimizer\nfrom graphql import GraphQLError\nfrom graphene_django import DjangoObjectType\n\nfrom nautobot.core.graphql.utils import str_to_var_name, get_filtering_args_from_filterset\nfrom nautobot.extras.choices import RelationshipSideChoices\nfrom nautobot.extras.models import RelationshipAssociation\nfrom nautobot.utilities.utils import get_filterset_for_model\n\nlogger = logging.getLogger(\"nautobot.graphql.generators\")\nRESOLVER_PREFIX = \"resolve_\"\n\n\ndef generate_restricted_queryset():\n    \"\"\"\n    Generate a function to return a restricted queryset compatible with the internal permissions system.\n\n    Note that for built-in models such as ContentType the queryset has no `restrict` method, so we have to\n    fail gracefully in that case.\n    \"\"\"\n\n    def get_queryset(queryset, info):\n        if not hasattr(queryset, \"restrict\"):\n            logger.debug(f\"Queryset {queryset} is not restrictable\")\n            return queryset\n        return queryset.restrict(info.context.user, \"view\")\n\n    return get_queryset\n\n\ndef generate_null_choices_resolver(name, resolver_name):\n    \"\"\"\n    Generate function to resolve appropriate type when a field has `null=False` (default), `blank=True`, and\n    `choices` defined.\n\n    Args:\n        name (str): name of the field to resolve\n        resolver_name (str): name of the resolver as declare in DjangoObjectType\n    \"\"\"\n\n    def resolve_fields_w_choices(model, info, **kwargs):\n        field_value = getattr(model, name)\n        if field_value:\n            return field_value\n        return None\n\n    resolve_fields_w_choices.__name__ = resolver_name\n    return resolve_fields_w_choices\n\n\ndef generate_filter_resolver(schema_type, resolver_name, field_name):\n    \"\"\"\n    Generate function to resolve OneToMany filtering.\n\n    Args:\n        schema_type (DjangoObjectType): DjangoObjectType for a given model\n        resolver_name (str): name of the resolver\n        field_name (str): name of OneToMany field to filter\n    \"\"\"\n    filterset_class = schema_type._meta.filterset_class\n\n    def resolve_filter(self, *args, **kwargs):\n        if not filterset_class:\n            return getattr(self, field_name).all()\n\n        resolved_obj = filterset_class(kwargs, getattr(self, field_name).all())\n\n        # Check result filter for errors.\n        if not resolved_obj.errors:\n            return resolved_obj.qs.all()\n\n        errors = {}\n\n        # Build error message from results\n        # Error messages are collected from each filter object\n        for key in resolved_obj.errors:\n            errors[key] = resolved_obj.errors[key]\n\n        # Raising this exception will send the error message in the response of the GraphQL request\n        raise GraphQLError(errors)\n\n    resolve_filter.__name__ = resolver_name\n    return resolve_filter\n\n\ndef generate_custom_field_resolver(name, resolver_name):\n    \"\"\"Generate function to resolve each custom field within each DjangoObjectType.\n\n    Args:\n        name (str): name of the custom field to resolve\n        resolver_name (str): name of the resolver as declare in DjangoObjectType\n    \"\"\"\n\n    def resolve_custom_field(self, info, **kwargs):\n        return self.cf.get(name, None)\n\n    resolve_custom_field.__name__ = resolver_name\n    return resolve_custom_field\n\n\ndef generate_computed_field_resolver(name, resolver_name):\n    \"\"\"Generate an instance method for resolving an individual computed field within a given DjangoObjectType.\n\n    Args:\n        name (str): name of the computed field to resolve\n        resolver_name (str): name of the resolver as declare in DjangoObjectType\n    \"\"\"\n\n    def resolve_computed_field(self, info, **kwargs):\n        return self.get_computed_field(slug=name)\n\n    resolve_computed_field.__name__ = resolver_name\n    return resolve_computed_field\n\n\ndef generate_relationship_resolver(name, resolver_name, relationship, side, peer_model):\n    \"\"\"Generate function to resolve each custom relationship within each DjangoObjectType.\n\n    Args:\n        name (str): name of the custom field to resolve\n        resolver_name (str): name of the resolver as declare in DjangoObjectType\n        relationship (Relationship): Relationship object to generate a resolver for\n        side (str): side of the relationship to use for the resolver\n        peer_model (Model): Django Model of the peer of this relationship\n    \"\"\"\n\n    def resolve_relationship(self, info, **kwargs):\n        \"\"\"Return a queryset or an object depending on the type of the relationship.\"\"\"\n        peer_side = RelationshipSideChoices.OPPOSITE[side]\n        query_params = {\"relationship\": relationship}\n        # https://github.com/nautobot/nautobot/issues/1228\n        # If querying for **only** the ID of the related object, for example:\n        # { device(id:\"...\") { ... rel_my_relationship { id } } }\n        # we will get this exception:\n        # TypeError: Cannot call select_related() after .values() or .values_list()\n        # This appears to be a bug in graphene_django_optimizer but I haven't found a known issue on GitHub.\n        # For now we just work around it by catching the exception and retrying without optimization, below...\n        if not relationship.symmetric:\n            # Get the objects on the other side of this relationship\n            query_params[f\"{side}_id\"] = self.pk\n\n            try:\n                queryset_ids = gql_optimizer.query(\n                    RelationshipAssociation.objects.filter(**query_params).values_list(f\"{peer_side}_id\", flat=True),\n                    info,\n                )\n            except TypeError:\n                logger.debug(\"Caught TypeError in graphene_django_optimizer, falling back to un-optimized query\")\n                queryset_ids = RelationshipAssociation.objects.filter(**query_params).values_list(\n                    f\"{peer_side}_id\", flat=True\n                )\n        else:\n            # Get objects that are peers for this relationship, regardless of side\n            try:\n                queryset_ids = list(\n                    gql_optimizer.query(\n                        RelationshipAssociation.objects.filter(source_id=self.pk, **query_params).values_list(\n                            \"destination_id\", flat=True\n                        ),\n                        info,\n                    )\n                )\n                queryset_ids += list(\n                    gql_optimizer.query(\n                        RelationshipAssociation.objects.filter(destination_id=self.pk, **query_params).values_list(\n                            \"source_id\", flat=True\n                        ),\n                        info,\n                    )\n                )\n            except TypeError:\n                logger.debug(\"Caught TypeError in graphene_django_optimizer, falling back to un-optimized query\")\n                queryset_ids = list(\n                    RelationshipAssociation.objects.filter(source_id=self.pk, **query_params).values_list(\n                        \"destination_id\", flat=True\n                    ),\n                )\n                queryset_ids += list(\n                    RelationshipAssociation.objects.filter(destination_id=self.pk, **query_params).values_list(\n                        \"source_id\", flat=True\n                    ),\n                )\n\n        if relationship.has_many(peer_side):\n            return gql_optimizer.query(peer_model.objects.filter(id__in=queryset_ids), info)\n\n        # Also apparently a graphene_django_optimizer bug - in the same query case as described above, here we may see:\n        # AttributeError: object has no attribute \"only\"\n        try:\n            return gql_optimizer.query(peer_model.objects.filter(id__in=queryset_ids).first(), info)\n        except AttributeError:\n            logger.debug(\"Caught AttributeError in graphene_django_optimizer, falling back to un-optimized query\")\n            return peer_model.objects.filter(id__in=queryset_ids).first()\n\n    resolve_relationship.__name__ = resolver_name\n    return resolve_relationship\n\n\ndef generate_schema_type(app_name: str, model: object) -> DjangoObjectType:\n    \"\"\"\n    Take a Django model and generate a Graphene Type class definition.\n\n    Args:\n        app_name (str): name of the application or plugin the Model is part of.\n        model (object): Django Model\n\n    Example:\n        For a model with a name of \"Device\", the following class definition is generated:\n\n        class DeviceType(DjangoObjectType):\n            Meta:\n                model = Device\n                fields = [\"__all__\"]\n\n        If a FilterSet exists for this model at\n        '<app_name>.filters.<ModelName>FilterSet' the filterset will be stored in\n        filterset_class as follows:\n\n        class DeviceType(DjangoObjectType):\n            Meta:\n                model = Device\n                fields = [\"__all__\"]\n                filterset_class = DeviceFilterSet\n    \"\"\"\n\n    main_attrs = {}\n    meta_attrs = {\"model\": model, \"fields\": \"__all__\"}\n\n    # We'll attempt to find a FilterSet corresponding to the model\n    # Not all models have a FilterSet defined so the function return none if it can't find a filterset\n    meta_attrs[\"filterset_class\"] = get_filterset_for_model(model)\n\n    main_attrs[\"Meta\"] = type(\"Meta\", (object,), meta_attrs)\n\n    schema_type = type(f\"{model.__name__}Type\", (DjangoObjectType,), main_attrs)\n    return schema_type\n\n\ndef generate_list_search_parameters(schema_type):\n    \"\"\"Generate list of query parameters for the list resolver based on a filterset.\"\"\"\n\n    search_params = {}\n    if schema_type._meta.filterset_class is not None:\n        search_params = get_filtering_args_from_filterset(\n            schema_type._meta.filterset_class,\n        )\n\n    return search_params\n\n\ndef generate_single_item_resolver(schema_type, resolver_name):\n    \"\"\"Generate a resolver for a single element of schema_type\n\n    Args:\n        schema_type (DjangoObjectType): DjangoObjectType for a given model\n        resolver_name (str): name of the resolver\n\n    Returns:\n        callable: Resolver function for a single element\n    \"\"\"\n    model = schema_type._meta.model\n\n    def single_resolver(self, info, **kwargs):\n\n        obj_id = kwargs.get(\"id\", None)\n        if obj_id:\n            return gql_optimizer.query(\n                model.objects.restrict(info.context.user, \"view\").filter(pk=obj_id), info\n            ).first()\n        return None\n\n    single_resolver.__name__ = resolver_name\n    return single_resolver\n\n\ndef generate_list_resolver(schema_type, resolver_name):\n    \"\"\"\n    Generate resolver for a list of schema_type.\n\n    If a filterset_class is associated with the schema_type,\n    the resolver will pass all arguments received to the FilterSet\n    If not, it will return a restricted queryset for all objects\n\n    Args:\n        schema_type (DjangoObjectType): DjangoObjectType for a given model\n        resolver_name (str): name of the resolver\n\n    Returns:\n        callable: Resolver function for list of element\n    \"\"\"\n    model = schema_type._meta.model\n\n    def list_resolver(self, info, **kwargs):\n        filterset_class = schema_type._meta.filterset_class\n        if filterset_class is not None:\n            resolved_obj = filterset_class(kwargs, model.objects.restrict(info.context.user, \"view\").all())\n\n            # Check result filter for errors.\n            if resolved_obj.errors:\n                errors = {}\n\n                # Build error message from results\n                # Error messages are collected from each filter object\n                for key in resolved_obj.errors:\n                    errors[key] = resolved_obj.errors[key]\n\n                # Raising this exception will send the error message in the response of the GraphQL request\n                raise GraphQLError(errors)\n\n            return gql_optimizer.query(resolved_obj.qs.all(), info)\n\n        return gql_optimizer.query(model.objects.restrict(info.context.user, \"view\").all(), info)\n\n    list_resolver.__name__ = resolver_name\n    return list_resolver\n\n\ndef generate_attrs_for_schema_type(schema_type):\n    \"\"\"Generate both attributes and resolvers for a given schema_type.\n\n    Args:\n        schema_type (DjangoObjectType): DjangoObjectType for a given model\n\n    Returns:\n        dict: Dict of attributes ready to merge into the QueryMixin class\n    \"\"\"\n    attrs = {}\n    model = schema_type._meta.model\n\n    single_item_name = str_to_var_name(model._meta.verbose_name)\n    list_name = str_to_var_name(model._meta.verbose_name_plural)\n\n    # Define Attributes for single item and list with their search parameters\n    search_params = generate_list_search_parameters(schema_type)\n    attrs[single_item_name] = graphene.Field(schema_type, id=graphene.ID())\n    attrs[list_name] = graphene.List(schema_type, **search_params)\n\n    # Define Resolvers for both single item and list\n    single_item_resolver_name = f\"{RESOLVER_PREFIX}{single_item_name}\"\n    list_resolver_name = f\"{RESOLVER_PREFIX}{list_name}\"\n    attrs[single_item_resolver_name] = generate_single_item_resolver(schema_type, single_item_resolver_name)\n    attrs[list_resolver_name] = generate_list_resolver(schema_type, list_resolver_name)\n\n    return attrs\n", "path": "nautobot/core/graphql/generators.py"}]}