We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Product permissions are not properly checked for service accounts
### What I'm trying to achieve
I want to access all products using service account. 

### Steps to reproduce the problem
1. Configure service account
2. Create unpublished product
3. Fetch product using service account token

### What I expected to happen
Query returns existing product, it fails to find it instead.

---

### Analysis
We do check product visibility based on users permissions: https://github.com/mirumee/saleor/blob/b0a0a04b4e0ab228f354b7d0b8b9023dfa096787/saleor/core/models.py#L53-L56
This does not work for service accounts because _user_ is anonymous on this level.
In mutation we have this issue covered with:
https://github.com/mirumee/saleor/blob/b0a0a04b4e0ab228f354b7d0b8b9023dfa096787/saleor/graphql/core/mutations.py#L268-L270

### Solution
One of two solutions could be applied:
- inject sevice account into _user_ and let all standard permission check pass, this introduces problem with possible attempt to fetch any user context data in application
- always check for service account OR user permission in resolvers

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `saleor/graphql/product/resolvers.py`
Content:
```
1 import graphene_django_optimizer as gql_optimizer
2 from django.db.models import Sum
3 from graphql import GraphQLError
4 from graphql_relay import from_global_id
5 
6 from ...order import OrderStatus
7 from ...product import models
8 from ...search.backends import picker
9 from ..core.enums import OrderDirection
10 from ..utils import (
11     filter_by_period,
12     filter_by_query_param,
13     get_database_id,
14     get_nodes,
15     sort_queryset,
16 )
17 from .filters import (
18     filter_attributes_by_product_types,
19     filter_products_by_attributes,
20     filter_products_by_categories,
21     filter_products_by_collections,
22     filter_products_by_minimal_price,
23     filter_products_by_price,
24     filter_products_by_stock_availability,
25 )
26 from .sorters import (
27     AttributeSortField,
28     CategorySortField,
29     CollectionSortField,
30     ProductOrder,
31     ProductOrderField,
32     ProductTypeSortField,
33 )
34 
35 PRODUCT_SEARCH_FIELDS = ("name", "description")
36 PRODUCT_TYPE_SEARCH_FIELDS = ("name",)
37 CATEGORY_SEARCH_FIELDS = ("name", "slug", "description", "parent__name")
38 COLLECTION_SEARCH_FIELDS = ("name", "slug")
39 ATTRIBUTES_SEARCH_FIELDS = ("name", "slug")
40 
41 
42 def resolve_attributes(
43     info,
44     qs=None,
45     in_category=None,
46     in_collection=None,
47     query=None,
48     sort_by=None,
49     **_kwargs,
50 ):
51     qs = qs or models.Attribute.objects.get_visible_to_user(info.context.user)
52     qs = filter_by_query_param(qs, query, ATTRIBUTES_SEARCH_FIELDS)
53 
54     if in_category:
55         qs = filter_attributes_by_product_types(qs, "in_category", in_category)
56 
57     if in_collection:
58         qs = filter_attributes_by_product_types(qs, "in_collection", in_collection)
59 
60     if sort_by:
61         qs = sort_queryset(qs, sort_by, AttributeSortField)
62     else:
63         qs = qs.order_by("name")
64 
65     qs = qs.distinct()
66     return gql_optimizer.query(qs, info)
67 
68 
69 def resolve_categories(info, query, level=None, sort_by=None, **_kwargs):
70     qs = models.Category.objects.prefetch_related("children")
71     if level is not None:
72         qs = qs.filter(level=level)
73     qs = filter_by_query_param(qs, query, CATEGORY_SEARCH_FIELDS)
74     qs = sort_queryset(qs, sort_by, CategorySortField)
75     qs = qs.distinct()
76     return gql_optimizer.query(qs, info)
77 
78 
79 def resolve_collections(info, query, sort_by=None, **_kwargs):
80     user = info.context.user
81     qs = models.Collection.objects.visible_to_user(user)
82     qs = filter_by_query_param(qs, query, COLLECTION_SEARCH_FIELDS)
83     qs = sort_queryset(qs, sort_by, CollectionSortField)
84     return gql_optimizer.query(qs, info)
85 
86 
87 def resolve_digital_contents(info):
88     qs = models.DigitalContent.objects.all()
89     return gql_optimizer.query(qs, info)
90 
91 
92 def sort_products(
93     qs: models.ProductsQueryset, sort_by: ProductOrder
94 ) -> models.ProductsQueryset:
95     if sort_by is None:
96         return qs
97 
98     # Check if one of the required fields was provided
99     if sort_by.field and sort_by.attribute_id:
100         raise GraphQLError(
101             "You must provide either `field` or `attributeId` to sort the products."
102         )
103 
104     if not sort_by.field and not sort_by.attribute_id:
105         return qs
106 
107     if sort_by.field:
108         return sort_queryset(qs, sort_by, ProductOrderField)
109     return sort_products_by_attribute(qs, sort_by)
110 
111 
112 def sort_products_by_attribute(
113     qs: models.ProductsQueryset, sort_by: ProductOrder
114 ) -> models.ProductsQueryset:
115     direction = sort_by.direction
116 
117     # If an attribute ID was passed, attempt to convert it
118     if sort_by.attribute_id:
119         graphene_type, attribute_pk = from_global_id(sort_by.attribute_id)
120         is_ascending = direction == OrderDirection.ASC
121 
122         # If the passed attribute ID is valid, execute the sorting
123         if attribute_pk.isnumeric() and graphene_type == "Attribute":
124             qs = qs.sort_by_attribute(attribute_pk, ascending=is_ascending)
125 
126     return qs
127 
128 
129 def resolve_products(
130     info,
131     attributes=None,
132     categories=None,
133     collections=None,
134     price_lte=None,
135     price_gte=None,
136     minimal_price_lte=None,
137     minimal_price_gte=None,
138     sort_by=None,
139     stock_availability=None,
140     query=None,
141     **_kwargs,
142 ):
143 
144     user = info.context.user
145     qs = models.Product.objects.visible_to_user(user)
146     qs = sort_products(qs, sort_by)
147 
148     if query:
149         search = picker.pick_backend()
150         qs &= search(query)
151 
152     if attributes:
153         qs = filter_products_by_attributes(qs, attributes)
154 
155     if categories:
156         categories = get_nodes(categories, "Category", models.Category)
157         qs = filter_products_by_categories(qs, categories)
158 
159     if collections:
160         collections = get_nodes(collections, "Collection", models.Collection)
161         qs = filter_products_by_collections(qs, collections)
162 
163     if stock_availability:
164         qs = filter_products_by_stock_availability(qs, stock_availability)
165 
166     qs = filter_products_by_price(qs, price_lte, price_gte)
167     qs = filter_products_by_minimal_price(qs, minimal_price_lte, minimal_price_gte)
168     qs = qs.distinct()
169 
170     return gql_optimizer.query(qs, info)
171 
172 
173 def resolve_product_types(info, query, sort_by=None, **_kwargs):
174     qs = models.ProductType.objects.all()
175     qs = filter_by_query_param(qs, query, PRODUCT_TYPE_SEARCH_FIELDS)
176     if sort_by:
177         qs = sort_queryset(qs, sort_by, ProductTypeSortField)
178     else:
179         qs = qs.order_by("name")
180     return gql_optimizer.query(qs, info)
181 
182 
183 def resolve_product_variants(info, ids=None):
184     user = info.context.user
185     visible_products = models.Product.objects.visible_to_user(user).values_list(
186         "pk", flat=True
187     )
188     qs = models.ProductVariant.objects.filter(product__id__in=visible_products)
189     if ids:
190         db_ids = [get_database_id(info, node_id, "ProductVariant") for node_id in ids]
191         qs = qs.filter(pk__in=db_ids)
192     return gql_optimizer.query(qs, info)
193 
194 
195 def resolve_report_product_sales(period):
196     qs = models.ProductVariant.objects.prefetch_related(
197         "product", "product__images", "order_lines__order"
198     ).all()
199 
200     # exclude draft and canceled orders
201     exclude_status = [OrderStatus.DRAFT, OrderStatus.CANCELED]
202     qs = qs.exclude(order_lines__order__status__in=exclude_status)
203 
204     # filter by period
205     qs = filter_by_period(qs, period, "order_lines__order__created")
206 
207     qs = qs.annotate(quantity_ordered=Sum("order_lines__quantity"))
208     qs = qs.filter(quantity_ordered__isnull=False)
209     return qs.order_by("-quantity_ordered")
210
```
--- 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/saleor/graphql/product/resolvers.py b/saleor/graphql/product/resolvers.py
--- a/saleor/graphql/product/resolvers.py
+++ b/saleor/graphql/product/resolvers.py
@@ -142,6 +142,8 @@
 ):
 
     user = info.context.user
+    if user.is_anonymous and info.context.service_account:
+        user = info.context.service_account
     qs = models.Product.objects.visible_to_user(user)
     qs = sort_products(qs, sort_by)

{"golden_diff": "diff --git a/saleor/graphql/product/resolvers.py b/saleor/graphql/product/resolvers.py\n--- a/saleor/graphql/product/resolvers.py\n+++ b/saleor/graphql/product/resolvers.py\n@@ -142,6 +142,8 @@\n ):\n \n     user = info.context.user\n+    if user.is_anonymous and info.context.service_account:\n+        user = info.context.service_account\n     qs = models.Product.objects.visible_to_user(user)\n     qs = sort_products(qs, sort_by)\n", "issue": "Product permissions are not properly checked for service accounts\n### What I'm trying to achieve\r\nI want to access all products using service account. \r\n\r\n### Steps to reproduce the problem\r\n1. Configure service account\r\n2. Create unpublished product\r\n3. Fetch product using service account token\r\n\r\n### What I expected to happen\r\nQuery returns existing product, it fails to find it instead.\r\n\r\n---\r\n\r\n### Analysis\r\nWe do check product visibility based on users permissions: https://github.com/mirumee/saleor/blob/b0a0a04b4e0ab228f354b7d0b8b9023dfa096787/saleor/core/models.py#L53-L56\r\nThis does not work for service accounts because _user_ is anonymous on this level.\r\nIn mutation we have this issue covered with:\r\nhttps://github.com/mirumee/saleor/blob/b0a0a04b4e0ab228f354b7d0b8b9023dfa096787/saleor/graphql/core/mutations.py#L268-L270\r\n\r\n### Solution\r\nOne of two solutions could be applied:\r\n- inject sevice account into _user_ and let all standard permission check pass, this introduces problem with possible attempt to fetch any user context data in application\r\n- always check for service account OR user permission in resolvers\n", "before_files": [{"content": "import graphene_django_optimizer as gql_optimizer\nfrom django.db.models import Sum\nfrom graphql import GraphQLError\nfrom graphql_relay import from_global_id\n\nfrom ...order import OrderStatus\nfrom ...product import models\nfrom ...search.backends import picker\nfrom ..core.enums import OrderDirection\nfrom ..utils import (\n    filter_by_period,\n    filter_by_query_param,\n    get_database_id,\n    get_nodes,\n    sort_queryset,\n)\nfrom .filters import (\n    filter_attributes_by_product_types,\n    filter_products_by_attributes,\n    filter_products_by_categories,\n    filter_products_by_collections,\n    filter_products_by_minimal_price,\n    filter_products_by_price,\n    filter_products_by_stock_availability,\n)\nfrom .sorters import (\n    AttributeSortField,\n    CategorySortField,\n    CollectionSortField,\n    ProductOrder,\n    ProductOrderField,\n    ProductTypeSortField,\n)\n\nPRODUCT_SEARCH_FIELDS = (\"name\", \"description\")\nPRODUCT_TYPE_SEARCH_FIELDS = (\"name\",)\nCATEGORY_SEARCH_FIELDS = (\"name\", \"slug\", \"description\", \"parent__name\")\nCOLLECTION_SEARCH_FIELDS = (\"name\", \"slug\")\nATTRIBUTES_SEARCH_FIELDS = (\"name\", \"slug\")\n\n\ndef resolve_attributes(\n    info,\n    qs=None,\n    in_category=None,\n    in_collection=None,\n    query=None,\n    sort_by=None,\n    **_kwargs,\n):\n    qs = qs or models.Attribute.objects.get_visible_to_user(info.context.user)\n    qs = filter_by_query_param(qs, query, ATTRIBUTES_SEARCH_FIELDS)\n\n    if in_category:\n        qs = filter_attributes_by_product_types(qs, \"in_category\", in_category)\n\n    if in_collection:\n        qs = filter_attributes_by_product_types(qs, \"in_collection\", in_collection)\n\n    if sort_by:\n        qs = sort_queryset(qs, sort_by, AttributeSortField)\n    else:\n        qs = qs.order_by(\"name\")\n\n    qs = qs.distinct()\n    return gql_optimizer.query(qs, info)\n\n\ndef resolve_categories(info, query, level=None, sort_by=None, **_kwargs):\n    qs = models.Category.objects.prefetch_related(\"children\")\n    if level is not None:\n        qs = qs.filter(level=level)\n    qs = filter_by_query_param(qs, query, CATEGORY_SEARCH_FIELDS)\n    qs = sort_queryset(qs, sort_by, CategorySortField)\n    qs = qs.distinct()\n    return gql_optimizer.query(qs, info)\n\n\ndef resolve_collections(info, query, sort_by=None, **_kwargs):\n    user = info.context.user\n    qs = models.Collection.objects.visible_to_user(user)\n    qs = filter_by_query_param(qs, query, COLLECTION_SEARCH_FIELDS)\n    qs = sort_queryset(qs, sort_by, CollectionSortField)\n    return gql_optimizer.query(qs, info)\n\n\ndef resolve_digital_contents(info):\n    qs = models.DigitalContent.objects.all()\n    return gql_optimizer.query(qs, info)\n\n\ndef sort_products(\n    qs: models.ProductsQueryset, sort_by: ProductOrder\n) -> models.ProductsQueryset:\n    if sort_by is None:\n        return qs\n\n    # Check if one of the required fields was provided\n    if sort_by.field and sort_by.attribute_id:\n        raise GraphQLError(\n            \"You must provide either `field` or `attributeId` to sort the products.\"\n        )\n\n    if not sort_by.field and not sort_by.attribute_id:\n        return qs\n\n    if sort_by.field:\n        return sort_queryset(qs, sort_by, ProductOrderField)\n    return sort_products_by_attribute(qs, sort_by)\n\n\ndef sort_products_by_attribute(\n    qs: models.ProductsQueryset, sort_by: ProductOrder\n) -> models.ProductsQueryset:\n    direction = sort_by.direction\n\n    # If an attribute ID was passed, attempt to convert it\n    if sort_by.attribute_id:\n        graphene_type, attribute_pk = from_global_id(sort_by.attribute_id)\n        is_ascending = direction == OrderDirection.ASC\n\n        # If the passed attribute ID is valid, execute the sorting\n        if attribute_pk.isnumeric() and graphene_type == \"Attribute\":\n            qs = qs.sort_by_attribute(attribute_pk, ascending=is_ascending)\n\n    return qs\n\n\ndef resolve_products(\n    info,\n    attributes=None,\n    categories=None,\n    collections=None,\n    price_lte=None,\n    price_gte=None,\n    minimal_price_lte=None,\n    minimal_price_gte=None,\n    sort_by=None,\n    stock_availability=None,\n    query=None,\n    **_kwargs,\n):\n\n    user = info.context.user\n    qs = models.Product.objects.visible_to_user(user)\n    qs = sort_products(qs, sort_by)\n\n    if query:\n        search = picker.pick_backend()\n        qs &= search(query)\n\n    if attributes:\n        qs = filter_products_by_attributes(qs, attributes)\n\n    if categories:\n        categories = get_nodes(categories, \"Category\", models.Category)\n        qs = filter_products_by_categories(qs, categories)\n\n    if collections:\n        collections = get_nodes(collections, \"Collection\", models.Collection)\n        qs = filter_products_by_collections(qs, collections)\n\n    if stock_availability:\n        qs = filter_products_by_stock_availability(qs, stock_availability)\n\n    qs = filter_products_by_price(qs, price_lte, price_gte)\n    qs = filter_products_by_minimal_price(qs, minimal_price_lte, minimal_price_gte)\n    qs = qs.distinct()\n\n    return gql_optimizer.query(qs, info)\n\n\ndef resolve_product_types(info, query, sort_by=None, **_kwargs):\n    qs = models.ProductType.objects.all()\n    qs = filter_by_query_param(qs, query, PRODUCT_TYPE_SEARCH_FIELDS)\n    if sort_by:\n        qs = sort_queryset(qs, sort_by, ProductTypeSortField)\n    else:\n        qs = qs.order_by(\"name\")\n    return gql_optimizer.query(qs, info)\n\n\ndef resolve_product_variants(info, ids=None):\n    user = info.context.user\n    visible_products = models.Product.objects.visible_to_user(user).values_list(\n        \"pk\", flat=True\n    )\n    qs = models.ProductVariant.objects.filter(product__id__in=visible_products)\n    if ids:\n        db_ids = [get_database_id(info, node_id, \"ProductVariant\") for node_id in ids]\n        qs = qs.filter(pk__in=db_ids)\n    return gql_optimizer.query(qs, info)\n\n\ndef resolve_report_product_sales(period):\n    qs = models.ProductVariant.objects.prefetch_related(\n        \"product\", \"product__images\", \"order_lines__order\"\n    ).all()\n\n    # exclude draft and canceled orders\n    exclude_status = [OrderStatus.DRAFT, OrderStatus.CANCELED]\n    qs = qs.exclude(order_lines__order__status__in=exclude_status)\n\n    # filter by period\n    qs = filter_by_period(qs, period, \"order_lines__order__created\")\n\n    qs = qs.annotate(quantity_ordered=Sum(\"order_lines__quantity\"))\n    qs = qs.filter(quantity_ordered__isnull=False)\n    return qs.order_by(\"-quantity_ordered\")\n", "path": "saleor/graphql/product/resolvers.py"}], "after_files": [{"content": "import graphene_django_optimizer as gql_optimizer\nfrom django.db.models import Sum\nfrom graphql import GraphQLError\nfrom graphql_relay import from_global_id\n\nfrom ...order import OrderStatus\nfrom ...product import models\nfrom ...search.backends import picker\nfrom ..core.enums import OrderDirection\nfrom ..utils import (\n    filter_by_period,\n    filter_by_query_param,\n    get_database_id,\n    get_nodes,\n    sort_queryset,\n)\nfrom .filters import (\n    filter_attributes_by_product_types,\n    filter_products_by_attributes,\n    filter_products_by_categories,\n    filter_products_by_collections,\n    filter_products_by_minimal_price,\n    filter_products_by_price,\n    filter_products_by_stock_availability,\n)\nfrom .sorters import (\n    AttributeSortField,\n    CategorySortField,\n    CollectionSortField,\n    ProductOrder,\n    ProductOrderField,\n    ProductTypeSortField,\n)\n\nPRODUCT_SEARCH_FIELDS = (\"name\", \"description\")\nPRODUCT_TYPE_SEARCH_FIELDS = (\"name\",)\nCATEGORY_SEARCH_FIELDS = (\"name\", \"slug\", \"description\", \"parent__name\")\nCOLLECTION_SEARCH_FIELDS = (\"name\", \"slug\")\nATTRIBUTES_SEARCH_FIELDS = (\"name\", \"slug\")\n\n\ndef resolve_attributes(\n    info,\n    qs=None,\n    in_category=None,\n    in_collection=None,\n    query=None,\n    sort_by=None,\n    **_kwargs,\n):\n    qs = qs or models.Attribute.objects.get_visible_to_user(info.context.user)\n    qs = filter_by_query_param(qs, query, ATTRIBUTES_SEARCH_FIELDS)\n\n    if in_category:\n        qs = filter_attributes_by_product_types(qs, \"in_category\", in_category)\n\n    if in_collection:\n        qs = filter_attributes_by_product_types(qs, \"in_collection\", in_collection)\n\n    if sort_by:\n        qs = sort_queryset(qs, sort_by, AttributeSortField)\n    else:\n        qs = qs.order_by(\"name\")\n\n    qs = qs.distinct()\n    return gql_optimizer.query(qs, info)\n\n\ndef resolve_categories(info, query, level=None, sort_by=None, **_kwargs):\n    qs = models.Category.objects.prefetch_related(\"children\")\n    if level is not None:\n        qs = qs.filter(level=level)\n    qs = filter_by_query_param(qs, query, CATEGORY_SEARCH_FIELDS)\n    qs = sort_queryset(qs, sort_by, CategorySortField)\n    qs = qs.distinct()\n    return gql_optimizer.query(qs, info)\n\n\ndef resolve_collections(info, query, sort_by=None, **_kwargs):\n    user = info.context.user\n    qs = models.Collection.objects.visible_to_user(user)\n    qs = filter_by_query_param(qs, query, COLLECTION_SEARCH_FIELDS)\n    qs = sort_queryset(qs, sort_by, CollectionSortField)\n    return gql_optimizer.query(qs, info)\n\n\ndef resolve_digital_contents(info):\n    qs = models.DigitalContent.objects.all()\n    return gql_optimizer.query(qs, info)\n\n\ndef sort_products(\n    qs: models.ProductsQueryset, sort_by: ProductOrder\n) -> models.ProductsQueryset:\n    if sort_by is None:\n        return qs\n\n    # Check if one of the required fields was provided\n    if sort_by.field and sort_by.attribute_id:\n        raise GraphQLError(\n            \"You must provide either `field` or `attributeId` to sort the products.\"\n        )\n\n    if not sort_by.field and not sort_by.attribute_id:\n        return qs\n\n    if sort_by.field:\n        return sort_queryset(qs, sort_by, ProductOrderField)\n    return sort_products_by_attribute(qs, sort_by)\n\n\ndef sort_products_by_attribute(\n    qs: models.ProductsQueryset, sort_by: ProductOrder\n) -> models.ProductsQueryset:\n    direction = sort_by.direction\n\n    # If an attribute ID was passed, attempt to convert it\n    if sort_by.attribute_id:\n        graphene_type, attribute_pk = from_global_id(sort_by.attribute_id)\n        is_ascending = direction == OrderDirection.ASC\n\n        # If the passed attribute ID is valid, execute the sorting\n        if attribute_pk.isnumeric() and graphene_type == \"Attribute\":\n            qs = qs.sort_by_attribute(attribute_pk, ascending=is_ascending)\n\n    return qs\n\n\ndef resolve_products(\n    info,\n    attributes=None,\n    categories=None,\n    collections=None,\n    price_lte=None,\n    price_gte=None,\n    minimal_price_lte=None,\n    minimal_price_gte=None,\n    sort_by=None,\n    stock_availability=None,\n    query=None,\n    **_kwargs,\n):\n\n    user = info.context.user\n    if user.is_anonymous and info.context.service_account:\n        user = info.context.service_account\n    qs = models.Product.objects.visible_to_user(user)\n    qs = sort_products(qs, sort_by)\n\n    if query:\n        search = picker.pick_backend()\n        qs &= search(query)\n\n    if attributes:\n        qs = filter_products_by_attributes(qs, attributes)\n\n    if categories:\n        categories = get_nodes(categories, \"Category\", models.Category)\n        qs = filter_products_by_categories(qs, categories)\n\n    if collections:\n        collections = get_nodes(collections, \"Collection\", models.Collection)\n        qs = filter_products_by_collections(qs, collections)\n\n    if stock_availability:\n        qs = filter_products_by_stock_availability(qs, stock_availability)\n\n    qs = filter_products_by_price(qs, price_lte, price_gte)\n    qs = filter_products_by_minimal_price(qs, minimal_price_lte, minimal_price_gte)\n    qs = qs.distinct()\n\n    return gql_optimizer.query(qs, info)\n\n\ndef resolve_product_types(info, query, sort_by=None, **_kwargs):\n    qs = models.ProductType.objects.all()\n    qs = filter_by_query_param(qs, query, PRODUCT_TYPE_SEARCH_FIELDS)\n    if sort_by:\n        qs = sort_queryset(qs, sort_by, ProductTypeSortField)\n    else:\n        qs = qs.order_by(\"name\")\n    return gql_optimizer.query(qs, info)\n\n\ndef resolve_product_variants(info, ids=None):\n    user = info.context.user\n    visible_products = models.Product.objects.visible_to_user(user).values_list(\n        \"pk\", flat=True\n    )\n    qs = models.ProductVariant.objects.filter(product__id__in=visible_products)\n    if ids:\n        db_ids = [get_database_id(info, node_id, \"ProductVariant\") for node_id in ids]\n        qs = qs.filter(pk__in=db_ids)\n    return gql_optimizer.query(qs, info)\n\n\ndef resolve_report_product_sales(period):\n    qs = models.ProductVariant.objects.prefetch_related(\n        \"product\", \"product__images\", \"order_lines__order\"\n    ).all()\n\n    # exclude draft and canceled orders\n    exclude_status = [OrderStatus.DRAFT, OrderStatus.CANCELED]\n    qs = qs.exclude(order_lines__order__status__in=exclude_status)\n\n    # filter by period\n    qs = filter_by_period(qs, period, \"order_lines__order__created\")\n\n    qs = qs.annotate(quantity_ordered=Sum(\"order_lines__quantity\"))\n    qs = qs.filter(quantity_ordered__isnull=False)\n    return qs.order_by(\"-quantity_ordered\")\n", "path": "saleor/graphql/product/resolvers.py"}]}