We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Return a variant price instead of the priceOverride
### What I'm trying to achieve
In my impression, `priceOverride` is an internal functionality which shouldn't be available in the API.
In addition, when I want to show a variant price I need to always introduce logic like:
```
let variantPrice = variants[0].priceOverride || product.price
```

### Describe a proposed solution
I propose to change the attribute `priceOverride` to `price` which shows product price when the override is empty.


--- 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/types.py`
Content:
```
1 import graphene
2 from graphene import relay
3 from graphene_django.filter import DjangoFilterConnectionField
4 from graphql.error import GraphQLError
5 
6 from ...product import models
7 from ...product.templatetags.product_images import get_thumbnail
8 from ...product.utils import products_with_details
9 from ...product.utils.availability import get_availability
10 from ...product.utils.costs import (
11     get_margin_for_variant, get_product_costs_data)
12 from ..core.decorators import permission_required
13 from ..core.types.common import CountableDjangoObjectType
14 from ..core.types.money import (
15     Money, MoneyRange, TaxedMoney, TaxedMoneyRange, TaxRateType)
16 from ..utils import get_database_id
17 from .filters import ProductFilterSet
18 
19 
20 def resolve_attribute_list(attributes):
21     keys = list(attributes.keys())
22     values = list(attributes.values())
23 
24     attributes_map = {
25         att.pk: att for att in models.ProductAttribute.objects.filter(
26             pk__in=keys)}
27     values_map = {
28         val.pk: val for val in models.AttributeChoiceValue.objects.filter(
29             pk__in=values)}
30 
31     attributes_list = [SelectedAttribute(
32         attribute=attributes_map.get(int(k)),
33         value=values_map.get(int(v)))
34         for k, v in attributes.items()]
35     return attributes_list
36 
37 
38 class ProductAttributeValue(CountableDjangoObjectType):
39     name = graphene.String(description='Visible name for display purposes.')
40     slug = graphene.String(
41         description='Internal representation of an attribute name.')
42 
43     class Meta:
44         description = 'Represents a value of an attribute.'
45         exclude_fields = ['attribute']
46         interfaces = [relay.Node]
47         model = models.AttributeChoiceValue
48 
49 
50 class ProductAttribute(CountableDjangoObjectType):
51     name = graphene.String(description='Visible name for display purposes.')
52     slug = graphene.String(
53         description='Internal representation of an attribute name.')
54     values = graphene.List(
55         ProductAttributeValue, description='List of attribute\'s values.')
56 
57     class Meta:
58         description = """Custom attribute of a product. Attributes can be
59         assigned to products and variants at the product type level."""
60         exclude_fields = ['product_types', 'product_variant_types']
61         interfaces = [relay.Node]
62         filter_fields = ['id', 'slug']
63         model = models.ProductAttribute
64 
65     def resolve_values(self, info):
66         return self.values.all()
67 
68 
69 class Margin(graphene.ObjectType):
70     start = graphene.Int()
71     stop = graphene.Int()
72 
73 
74 class SelectedAttribute(graphene.ObjectType):
75     attribute = graphene.Field(
76         ProductAttribute,
77         default_value=None, description='Name of an attribute')
78     value = graphene.Field(
79         ProductAttributeValue,
80         default_value=None, description='Value of an attribute.')
81 
82     class Meta:
83         description = 'Represents a custom product attribute.'
84 
85 
86 class ProductVariant(CountableDjangoObjectType):
87     stock_quantity = graphene.Int(
88         required=True, description='Quantity of a product available for sale.')
89     price_override = graphene.Field(
90         Money,
91         description="""Override the base price of a product if necessary.
92         A value of `null` indicates that the default product price is used.""")
93     attributes = graphene.List(
94         SelectedAttribute,
95         description='List of attributes assigned to this variant.')
96     cost_price = graphene.Field(
97         Money, description='Cost price of the variant.')
98     margin = graphene.Int(description='Gross margin percentage value.')
99 
100     class Meta:
101         description = """Represents a version of a product such as different
102         size or color."""
103         exclude_fields = ['variant_images']
104         interfaces = [relay.Node]
105         model = models.ProductVariant
106         filter_fields = ['id']
107 
108     def resolve_stock_quantity(self, info):
109         return self.quantity_available
110 
111     def resolve_attributes(self, info):
112         return resolve_attribute_list(self.attributes)
113 
114     def resolve_margin(self, info):
115         return get_margin_for_variant(self)
116 
117 
118 class ProductAvailability(graphene.ObjectType):
119     available = graphene.Boolean()
120     on_sale = graphene.Boolean()
121     discount = graphene.Field(TaxedMoney)
122     discount_local_currency = graphene.Field(TaxedMoney)
123     price_range = graphene.Field(TaxedMoneyRange)
124     price_range_undiscounted = graphene.Field(TaxedMoneyRange)
125     price_range_local_currency = graphene.Field(TaxedMoneyRange)
126 
127     class Meta:
128         description = 'Represents availability of a product in the storefront.'
129 
130 
131 class Image(graphene.ObjectType):
132     url = graphene.String(
133         required=True,
134         description='The URL of the image.',
135         size=graphene.Int(description='Size of the image'))
136 
137     class Meta:
138         description = 'Represents an image.'
139 
140     def resolve_url(self, info, size=None):
141         if size:
142             return get_thumbnail(self, size, method='thumbnail')
143         return self.url
144 
145 
146 class Product(CountableDjangoObjectType):
147     url = graphene.String(
148         description='The storefront URL for the product.', required=True)
149     thumbnail_url = graphene.String(
150         description='The URL of a main thumbnail for a product.',
151         size=graphene.Argument(graphene.Int, description='Size of thumbnail'))
152     availability = graphene.Field(
153         ProductAvailability,
154         description="""Informs about product's availability in the storefront,
155         current price and discounts.""")
156     price = graphene.Field(
157         Money,
158         description="""The product's base price (without any discounts
159         applied).""")
160     attributes = graphene.List(
161         SelectedAttribute,
162         description='List of product attributes assigned to this product.')
163     purchase_cost = graphene.Field(MoneyRange)
164     margin = graphene.Field(Margin)
165     image_by_id = graphene.Field(
166         lambda: ProductImage,
167         id=graphene.Argument(
168             graphene.ID, description='ID of a product image.'),
169         description='Get a single product image by ID')
170 
171     class Meta:
172         description = """Represents an individual item for sale in the
173         storefront."""
174         interfaces = [relay.Node]
175         model = models.Product
176 
177     def resolve_thumbnail_url(self, info, *, size=None):
178         if not size:
179             size = 255
180         return get_thumbnail(self.get_first_image(), size, method='thumbnail')
181 
182     def resolve_url(self, info):
183         return self.get_absolute_url()
184 
185     def resolve_availability(self, info):
186         context = info.context
187         availability = get_availability(
188             self, context.discounts, context.taxes, context.currency)
189         return ProductAvailability(**availability._asdict())
190 
191     def resolve_attributes(self, info):
192         return resolve_attribute_list(self.attributes)
193 
194     def resolve_product_type(self, info):
195         return self.product_type
196 
197     @permission_required('product.manage_products')
198     def resolve_purchase_cost(self, info):
199         purchase_cost, _ = get_product_costs_data(self)
200         return purchase_cost
201 
202     @permission_required('product.manage_products')
203     def resolve_margin(self, info):
204         _, margin = get_product_costs_data(self)
205         return Margin(margin[0], margin[1])
206 
207     def resolve_image_by_id(self, info, id):
208         pk = get_database_id(info, id, ProductImage)
209         try:
210             return self.images.get(pk=pk)
211         except models.ProductImage.DoesNotExist:
212             raise GraphQLError('Product image not found.')
213 
214 
215 class ProductType(CountableDjangoObjectType):
216     products = DjangoFilterConnectionField(
217         Product,
218         filterset_class=ProductFilterSet,
219         description='List of products of this type.')
220     tax_rate = TaxRateType(description='A type of tax rate.')
221 
222     class Meta:
223         description = """Represents a type of product. It defines what
224         attributes are available to products of this type."""
225         interfaces = [relay.Node]
226         model = models.ProductType
227         filter_fields = ['id']
228 
229     def resolve_products(self, info, **kwargs):
230         user = info.context.user
231         return products_with_details(
232             user=user).filter(product_type=self).distinct()
233 
234 
235 class Collection(CountableDjangoObjectType):
236     products = DjangoFilterConnectionField(
237         Product, filterset_class=ProductFilterSet,
238         description='List of collection products.')
239     background_image = graphene.Field(Image)
240 
241     class Meta:
242         description = "Represents a collection of products."
243         exclude_fields = ['voucher_set', 'sale_set', 'menuitem_set']
244         filter_fields = {
245             'name': ['exact', 'icontains', 'istartswith']}
246         interfaces = [relay.Node]
247         model = models.Collection
248 
249     def resolve_products(self, info, **kwargs):
250         user = info.context.user
251         return products_with_details(
252             user=user).filter(collections=self).distinct()
253 
254 
255 class Category(CountableDjangoObjectType):
256     products = DjangoFilterConnectionField(
257         Product,
258         filterset_class=ProductFilterSet,
259         description='List of products in the category.')
260     url = graphene.String(
261         description='The storefront\'s URL for the category.')
262     ancestors = DjangoFilterConnectionField(
263         lambda: Category,
264         description='List of ancestors of the category.')
265     children = DjangoFilterConnectionField(
266         lambda: Category,
267         description='List of children of the category.')
268     background_image = graphene.Field(Image)
269 
270     class Meta:
271         description = """Represents a single category of products. Categories
272         allow to organize products in a tree-hierarchies which can be used for
273         navigation in the storefront."""
274         exclude_fields = [
275             'lft', 'rght', 'tree_id', 'voucher_set', 'sale_set',
276             'menuitem_set']
277         interfaces = [relay.Node]
278         filter_fields = ['id', 'name']
279         model = models.Category
280 
281     def resolve_ancestors(self, info, **kwargs):
282         return self.get_ancestors().distinct()
283 
284     def resolve_children(self, info, **kwargs):
285         return self.children.distinct()
286 
287     def resolve_url(self, info):
288         return self.get_absolute_url()
289 
290     def resolve_products(self, info, **kwargs):
291         qs = models.Product.objects.available_products()
292         qs = qs.filter(category=self)
293         return qs.distinct()
294 
295 
296 class ProductImage(CountableDjangoObjectType):
297     url = graphene.String(
298         required=True,
299         description='The URL of the image.',
300         size=graphene.Int(description='Size of the image'))
301 
302     class Meta:
303         description = 'Represents a product image.'
304         exclude_fields = [
305             'image', 'product', 'ppoi', 'productvariant_set',
306             'variant_images']
307         interfaces = [relay.Node]
308         model = models.ProductImage
309 
310     def resolve_url(self, info, *, size=None):
311         if size:
312             return get_thumbnail(self.image, size, method='thumbnail')
313         return self.image.url
314
```
--- 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/types.py b/saleor/graphql/product/types.py
--- a/saleor/graphql/product/types.py
+++ b/saleor/graphql/product/types.py
@@ -90,6 +90,7 @@
         Money,
         description="""Override the base price of a product if necessary.
         A value of `null` indicates that the default product price is used.""")
+    price = graphene.Field(Money, description="Price of the product variant.")
     attributes = graphene.List(
         SelectedAttribute,
         description='List of attributes assigned to this variant.')
@@ -114,6 +115,15 @@
     def resolve_margin(self, info):
         return get_margin_for_variant(self)
 
+    def resolve_price(self, info):
+        return (
+            self.price_override
+            if self.price_override is not None else self.product.price)
+
+    @permission_required('product.manage_products')
+    def resolve_price_override(self, info):
+        return self.price_override
+
 
 class ProductAvailability(graphene.ObjectType):
     available = graphene.Boolean()

{"golden_diff": "diff --git a/saleor/graphql/product/types.py b/saleor/graphql/product/types.py\n--- a/saleor/graphql/product/types.py\n+++ b/saleor/graphql/product/types.py\n@@ -90,6 +90,7 @@\n         Money,\n         description=\"\"\"Override the base price of a product if necessary.\n         A value of `null` indicates that the default product price is used.\"\"\")\n+    price = graphene.Field(Money, description=\"Price of the product variant.\")\n     attributes = graphene.List(\n         SelectedAttribute,\n         description='List of attributes assigned to this variant.')\n@@ -114,6 +115,15 @@\n     def resolve_margin(self, info):\n         return get_margin_for_variant(self)\n \n+    def resolve_price(self, info):\n+        return (\n+            self.price_override\n+            if self.price_override is not None else self.product.price)\n+\n+    @permission_required('product.manage_products')\n+    def resolve_price_override(self, info):\n+        return self.price_override\n+\n \n class ProductAvailability(graphene.ObjectType):\n     available = graphene.Boolean()\n", "issue": "Return a variant price instead of the priceOverride\n### What I'm trying to achieve\r\nIn my impression, `priceOverride` is an internal functionality which shouldn't be available in the API.\r\nIn addition, when I want to show a variant price I need to always introduce logic like:\r\n```\r\nlet variantPrice = variants[0].priceOverride || product.price\r\n```\r\n\r\n### Describe a proposed solution\r\nI propose to change the attribute `priceOverride` to `price` which shows product price when the override is empty.\r\n\n", "before_files": [{"content": "import graphene\nfrom graphene import relay\nfrom graphene_django.filter import DjangoFilterConnectionField\nfrom graphql.error import GraphQLError\n\nfrom ...product import models\nfrom ...product.templatetags.product_images import get_thumbnail\nfrom ...product.utils import products_with_details\nfrom ...product.utils.availability import get_availability\nfrom ...product.utils.costs import (\n    get_margin_for_variant, get_product_costs_data)\nfrom ..core.decorators import permission_required\nfrom ..core.types.common import CountableDjangoObjectType\nfrom ..core.types.money import (\n    Money, MoneyRange, TaxedMoney, TaxedMoneyRange, TaxRateType)\nfrom ..utils import get_database_id\nfrom .filters import ProductFilterSet\n\n\ndef resolve_attribute_list(attributes):\n    keys = list(attributes.keys())\n    values = list(attributes.values())\n\n    attributes_map = {\n        att.pk: att for att in models.ProductAttribute.objects.filter(\n            pk__in=keys)}\n    values_map = {\n        val.pk: val for val in models.AttributeChoiceValue.objects.filter(\n            pk__in=values)}\n\n    attributes_list = [SelectedAttribute(\n        attribute=attributes_map.get(int(k)),\n        value=values_map.get(int(v)))\n        for k, v in attributes.items()]\n    return attributes_list\n\n\nclass ProductAttributeValue(CountableDjangoObjectType):\n    name = graphene.String(description='Visible name for display purposes.')\n    slug = graphene.String(\n        description='Internal representation of an attribute name.')\n\n    class Meta:\n        description = 'Represents a value of an attribute.'\n        exclude_fields = ['attribute']\n        interfaces = [relay.Node]\n        model = models.AttributeChoiceValue\n\n\nclass ProductAttribute(CountableDjangoObjectType):\n    name = graphene.String(description='Visible name for display purposes.')\n    slug = graphene.String(\n        description='Internal representation of an attribute name.')\n    values = graphene.List(\n        ProductAttributeValue, description='List of attribute\\'s values.')\n\n    class Meta:\n        description = \"\"\"Custom attribute of a product. Attributes can be\n        assigned to products and variants at the product type level.\"\"\"\n        exclude_fields = ['product_types', 'product_variant_types']\n        interfaces = [relay.Node]\n        filter_fields = ['id', 'slug']\n        model = models.ProductAttribute\n\n    def resolve_values(self, info):\n        return self.values.all()\n\n\nclass Margin(graphene.ObjectType):\n    start = graphene.Int()\n    stop = graphene.Int()\n\n\nclass SelectedAttribute(graphene.ObjectType):\n    attribute = graphene.Field(\n        ProductAttribute,\n        default_value=None, description='Name of an attribute')\n    value = graphene.Field(\n        ProductAttributeValue,\n        default_value=None, description='Value of an attribute.')\n\n    class Meta:\n        description = 'Represents a custom product attribute.'\n\n\nclass ProductVariant(CountableDjangoObjectType):\n    stock_quantity = graphene.Int(\n        required=True, description='Quantity of a product available for sale.')\n    price_override = graphene.Field(\n        Money,\n        description=\"\"\"Override the base price of a product if necessary.\n        A value of `null` indicates that the default product price is used.\"\"\")\n    attributes = graphene.List(\n        SelectedAttribute,\n        description='List of attributes assigned to this variant.')\n    cost_price = graphene.Field(\n        Money, description='Cost price of the variant.')\n    margin = graphene.Int(description='Gross margin percentage value.')\n\n    class Meta:\n        description = \"\"\"Represents a version of a product such as different\n        size or color.\"\"\"\n        exclude_fields = ['variant_images']\n        interfaces = [relay.Node]\n        model = models.ProductVariant\n        filter_fields = ['id']\n\n    def resolve_stock_quantity(self, info):\n        return self.quantity_available\n\n    def resolve_attributes(self, info):\n        return resolve_attribute_list(self.attributes)\n\n    def resolve_margin(self, info):\n        return get_margin_for_variant(self)\n\n\nclass ProductAvailability(graphene.ObjectType):\n    available = graphene.Boolean()\n    on_sale = graphene.Boolean()\n    discount = graphene.Field(TaxedMoney)\n    discount_local_currency = graphene.Field(TaxedMoney)\n    price_range = graphene.Field(TaxedMoneyRange)\n    price_range_undiscounted = graphene.Field(TaxedMoneyRange)\n    price_range_local_currency = graphene.Field(TaxedMoneyRange)\n\n    class Meta:\n        description = 'Represents availability of a product in the storefront.'\n\n\nclass Image(graphene.ObjectType):\n    url = graphene.String(\n        required=True,\n        description='The URL of the image.',\n        size=graphene.Int(description='Size of the image'))\n\n    class Meta:\n        description = 'Represents an image.'\n\n    def resolve_url(self, info, size=None):\n        if size:\n            return get_thumbnail(self, size, method='thumbnail')\n        return self.url\n\n\nclass Product(CountableDjangoObjectType):\n    url = graphene.String(\n        description='The storefront URL for the product.', required=True)\n    thumbnail_url = graphene.String(\n        description='The URL of a main thumbnail for a product.',\n        size=graphene.Argument(graphene.Int, description='Size of thumbnail'))\n    availability = graphene.Field(\n        ProductAvailability,\n        description=\"\"\"Informs about product's availability in the storefront,\n        current price and discounts.\"\"\")\n    price = graphene.Field(\n        Money,\n        description=\"\"\"The product's base price (without any discounts\n        applied).\"\"\")\n    attributes = graphene.List(\n        SelectedAttribute,\n        description='List of product attributes assigned to this product.')\n    purchase_cost = graphene.Field(MoneyRange)\n    margin = graphene.Field(Margin)\n    image_by_id = graphene.Field(\n        lambda: ProductImage,\n        id=graphene.Argument(\n            graphene.ID, description='ID of a product image.'),\n        description='Get a single product image by ID')\n\n    class Meta:\n        description = \"\"\"Represents an individual item for sale in the\n        storefront.\"\"\"\n        interfaces = [relay.Node]\n        model = models.Product\n\n    def resolve_thumbnail_url(self, info, *, size=None):\n        if not size:\n            size = 255\n        return get_thumbnail(self.get_first_image(), size, method='thumbnail')\n\n    def resolve_url(self, info):\n        return self.get_absolute_url()\n\n    def resolve_availability(self, info):\n        context = info.context\n        availability = get_availability(\n            self, context.discounts, context.taxes, context.currency)\n        return ProductAvailability(**availability._asdict())\n\n    def resolve_attributes(self, info):\n        return resolve_attribute_list(self.attributes)\n\n    def resolve_product_type(self, info):\n        return self.product_type\n\n    @permission_required('product.manage_products')\n    def resolve_purchase_cost(self, info):\n        purchase_cost, _ = get_product_costs_data(self)\n        return purchase_cost\n\n    @permission_required('product.manage_products')\n    def resolve_margin(self, info):\n        _, margin = get_product_costs_data(self)\n        return Margin(margin[0], margin[1])\n\n    def resolve_image_by_id(self, info, id):\n        pk = get_database_id(info, id, ProductImage)\n        try:\n            return self.images.get(pk=pk)\n        except models.ProductImage.DoesNotExist:\n            raise GraphQLError('Product image not found.')\n\n\nclass ProductType(CountableDjangoObjectType):\n    products = DjangoFilterConnectionField(\n        Product,\n        filterset_class=ProductFilterSet,\n        description='List of products of this type.')\n    tax_rate = TaxRateType(description='A type of tax rate.')\n\n    class Meta:\n        description = \"\"\"Represents a type of product. It defines what\n        attributes are available to products of this type.\"\"\"\n        interfaces = [relay.Node]\n        model = models.ProductType\n        filter_fields = ['id']\n\n    def resolve_products(self, info, **kwargs):\n        user = info.context.user\n        return products_with_details(\n            user=user).filter(product_type=self).distinct()\n\n\nclass Collection(CountableDjangoObjectType):\n    products = DjangoFilterConnectionField(\n        Product, filterset_class=ProductFilterSet,\n        description='List of collection products.')\n    background_image = graphene.Field(Image)\n\n    class Meta:\n        description = \"Represents a collection of products.\"\n        exclude_fields = ['voucher_set', 'sale_set', 'menuitem_set']\n        filter_fields = {\n            'name': ['exact', 'icontains', 'istartswith']}\n        interfaces = [relay.Node]\n        model = models.Collection\n\n    def resolve_products(self, info, **kwargs):\n        user = info.context.user\n        return products_with_details(\n            user=user).filter(collections=self).distinct()\n\n\nclass Category(CountableDjangoObjectType):\n    products = DjangoFilterConnectionField(\n        Product,\n        filterset_class=ProductFilterSet,\n        description='List of products in the category.')\n    url = graphene.String(\n        description='The storefront\\'s URL for the category.')\n    ancestors = DjangoFilterConnectionField(\n        lambda: Category,\n        description='List of ancestors of the category.')\n    children = DjangoFilterConnectionField(\n        lambda: Category,\n        description='List of children of the category.')\n    background_image = graphene.Field(Image)\n\n    class Meta:\n        description = \"\"\"Represents a single category of products. Categories\n        allow to organize products in a tree-hierarchies which can be used for\n        navigation in the storefront.\"\"\"\n        exclude_fields = [\n            'lft', 'rght', 'tree_id', 'voucher_set', 'sale_set',\n            'menuitem_set']\n        interfaces = [relay.Node]\n        filter_fields = ['id', 'name']\n        model = models.Category\n\n    def resolve_ancestors(self, info, **kwargs):\n        return self.get_ancestors().distinct()\n\n    def resolve_children(self, info, **kwargs):\n        return self.children.distinct()\n\n    def resolve_url(self, info):\n        return self.get_absolute_url()\n\n    def resolve_products(self, info, **kwargs):\n        qs = models.Product.objects.available_products()\n        qs = qs.filter(category=self)\n        return qs.distinct()\n\n\nclass ProductImage(CountableDjangoObjectType):\n    url = graphene.String(\n        required=True,\n        description='The URL of the image.',\n        size=graphene.Int(description='Size of the image'))\n\n    class Meta:\n        description = 'Represents a product image.'\n        exclude_fields = [\n            'image', 'product', 'ppoi', 'productvariant_set',\n            'variant_images']\n        interfaces = [relay.Node]\n        model = models.ProductImage\n\n    def resolve_url(self, info, *, size=None):\n        if size:\n            return get_thumbnail(self.image, size, method='thumbnail')\n        return self.image.url\n", "path": "saleor/graphql/product/types.py"}], "after_files": [{"content": "import graphene\nfrom graphene import relay\nfrom graphene_django.filter import DjangoFilterConnectionField\nfrom graphql.error import GraphQLError\n\nfrom ...product import models\nfrom ...product.templatetags.product_images import get_thumbnail\nfrom ...product.utils import products_with_details\nfrom ...product.utils.availability import get_availability\nfrom ...product.utils.costs import (\n    get_margin_for_variant, get_product_costs_data)\nfrom ..core.decorators import permission_required\nfrom ..core.types.common import CountableDjangoObjectType\nfrom ..core.types.money import (\n    Money, MoneyRange, TaxedMoney, TaxedMoneyRange, TaxRateType)\nfrom ..utils import get_database_id\nfrom .filters import ProductFilterSet\n\n\ndef resolve_attribute_list(attributes):\n    keys = list(attributes.keys())\n    values = list(attributes.values())\n\n    attributes_map = {\n        att.pk: att for att in models.ProductAttribute.objects.filter(\n            pk__in=keys)}\n    values_map = {\n        val.pk: val for val in models.AttributeChoiceValue.objects.filter(\n            pk__in=values)}\n\n    attributes_list = [SelectedAttribute(\n        attribute=attributes_map.get(int(k)),\n        value=values_map.get(int(v)))\n        for k, v in attributes.items()]\n    return attributes_list\n\n\nclass ProductAttributeValue(CountableDjangoObjectType):\n    name = graphene.String(description='Visible name for display purposes.')\n    slug = graphene.String(\n        description='Internal representation of an attribute name.')\n\n    class Meta:\n        description = 'Represents a value of an attribute.'\n        exclude_fields = ['attribute']\n        interfaces = [relay.Node]\n        model = models.AttributeChoiceValue\n\n\nclass ProductAttribute(CountableDjangoObjectType):\n    name = graphene.String(description='Visible name for display purposes.')\n    slug = graphene.String(\n        description='Internal representation of an attribute name.')\n    values = graphene.List(\n        ProductAttributeValue, description='List of attribute\\'s values.')\n\n    class Meta:\n        description = \"\"\"Custom attribute of a product. Attributes can be\n        assigned to products and variants at the product type level.\"\"\"\n        exclude_fields = ['product_types', 'product_variant_types']\n        interfaces = [relay.Node]\n        filter_fields = ['id', 'slug']\n        model = models.ProductAttribute\n\n    def resolve_values(self, info):\n        return self.values.all()\n\n\nclass Margin(graphene.ObjectType):\n    start = graphene.Int()\n    stop = graphene.Int()\n\n\nclass SelectedAttribute(graphene.ObjectType):\n    attribute = graphene.Field(\n        ProductAttribute,\n        default_value=None, description='Name of an attribute')\n    value = graphene.Field(\n        ProductAttributeValue,\n        default_value=None, description='Value of an attribute.')\n\n    class Meta:\n        description = 'Represents a custom product attribute.'\n\n\nclass ProductVariant(CountableDjangoObjectType):\n    stock_quantity = graphene.Int(\n        required=True, description='Quantity of a product available for sale.')\n    price_override = graphene.Field(\n        Money,\n        description=\"\"\"Override the base price of a product if necessary.\n        A value of `null` indicates that the default product price is used.\"\"\")\n    price = graphene.Field(Money, description=\"Price of the product variant.\")\n    attributes = graphene.List(\n        SelectedAttribute,\n        description='List of attributes assigned to this variant.')\n    cost_price = graphene.Field(\n        Money, description='Cost price of the variant.')\n    margin = graphene.Int(description='Gross margin percentage value.')\n\n    class Meta:\n        description = \"\"\"Represents a version of a product such as different\n        size or color.\"\"\"\n        exclude_fields = ['variant_images']\n        interfaces = [relay.Node]\n        model = models.ProductVariant\n        filter_fields = ['id']\n\n    def resolve_stock_quantity(self, info):\n        return self.quantity_available\n\n    def resolve_attributes(self, info):\n        return resolve_attribute_list(self.attributes)\n\n    def resolve_margin(self, info):\n        return get_margin_for_variant(self)\n\n    def resolve_price(self, info):\n        return (\n            self.price_override\n            if self.price_override is not None else self.product.price)\n\n    @permission_required('product.manage_products')\n    def resolve_price_override(self, info):\n        return self.price_override\n\n\nclass ProductAvailability(graphene.ObjectType):\n    available = graphene.Boolean()\n    on_sale = graphene.Boolean()\n    discount = graphene.Field(TaxedMoney)\n    discount_local_currency = graphene.Field(TaxedMoney)\n    price_range = graphene.Field(TaxedMoneyRange)\n    price_range_undiscounted = graphene.Field(TaxedMoneyRange)\n    price_range_local_currency = graphene.Field(TaxedMoneyRange)\n\n    class Meta:\n        description = 'Represents availability of a product in the storefront.'\n\n\nclass Image(graphene.ObjectType):\n    url = graphene.String(\n        required=True,\n        description='The URL of the image.',\n        size=graphene.Int(description='Size of the image'))\n\n    class Meta:\n        description = 'Represents an image.'\n\n    def resolve_url(self, info, size=None):\n        if size:\n            return get_thumbnail(self, size, method='thumbnail')\n        return self.url\n\n\nclass Product(CountableDjangoObjectType):\n    url = graphene.String(\n        description='The storefront URL for the product.', required=True)\n    thumbnail_url = graphene.String(\n        description='The URL of a main thumbnail for a product.',\n        size=graphene.Argument(graphene.Int, description='Size of thumbnail'))\n    availability = graphene.Field(\n        ProductAvailability,\n        description=\"\"\"Informs about product's availability in the storefront,\n        current price and discounts.\"\"\")\n    price = graphene.Field(\n        Money,\n        description=\"\"\"The product's base price (without any discounts\n        applied).\"\"\")\n    attributes = graphene.List(\n        SelectedAttribute,\n        description='List of product attributes assigned to this product.')\n    purchase_cost = graphene.Field(MoneyRange)\n    margin = graphene.Field(Margin)\n    image_by_id = graphene.Field(\n        lambda: ProductImage,\n        id=graphene.Argument(\n            graphene.ID, description='ID of a product image.'),\n        description='Get a single product image by ID')\n\n    class Meta:\n        description = \"\"\"Represents an individual item for sale in the\n        storefront.\"\"\"\n        interfaces = [relay.Node]\n        model = models.Product\n\n    def resolve_thumbnail_url(self, info, *, size=None):\n        if not size:\n            size = 255\n        return get_thumbnail(self.get_first_image(), size, method='thumbnail')\n\n    def resolve_url(self, info):\n        return self.get_absolute_url()\n\n    def resolve_availability(self, info):\n        context = info.context\n        availability = get_availability(\n            self, context.discounts, context.taxes, context.currency)\n        return ProductAvailability(**availability._asdict())\n\n    def resolve_attributes(self, info):\n        return resolve_attribute_list(self.attributes)\n\n    def resolve_product_type(self, info):\n        return self.product_type\n\n    @permission_required('product.manage_products')\n    def resolve_purchase_cost(self, info):\n        purchase_cost, _ = get_product_costs_data(self)\n        return purchase_cost\n\n    @permission_required('product.manage_products')\n    def resolve_margin(self, info):\n        _, margin = get_product_costs_data(self)\n        return Margin(margin[0], margin[1])\n\n    def resolve_image_by_id(self, info, id):\n        pk = get_database_id(info, id, ProductImage)\n        try:\n            return self.images.get(pk=pk)\n        except models.ProductImage.DoesNotExist:\n            raise GraphQLError('Product image not found.')\n\n\nclass ProductType(CountableDjangoObjectType):\n    products = DjangoFilterConnectionField(\n        Product,\n        filterset_class=ProductFilterSet,\n        description='List of products of this type.')\n    tax_rate = TaxRateType(description='A type of tax rate.')\n\n    class Meta:\n        description = \"\"\"Represents a type of product. It defines what\n        attributes are available to products of this type.\"\"\"\n        interfaces = [relay.Node]\n        model = models.ProductType\n        filter_fields = ['id']\n\n    def resolve_products(self, info, **kwargs):\n        user = info.context.user\n        return products_with_details(\n            user=user).filter(product_type=self).distinct()\n\n\nclass Collection(CountableDjangoObjectType):\n    products = DjangoFilterConnectionField(\n        Product, filterset_class=ProductFilterSet,\n        description='List of collection products.')\n    background_image = graphene.Field(Image)\n\n    class Meta:\n        description = \"Represents a collection of products.\"\n        exclude_fields = ['voucher_set', 'sale_set', 'menuitem_set']\n        filter_fields = {\n            'name': ['exact', 'icontains', 'istartswith']}\n        interfaces = [relay.Node]\n        model = models.Collection\n\n    def resolve_products(self, info, **kwargs):\n        user = info.context.user\n        return products_with_details(\n            user=user).filter(collections=self).distinct()\n\n\nclass Category(CountableDjangoObjectType):\n    products = DjangoFilterConnectionField(\n        Product,\n        filterset_class=ProductFilterSet,\n        description='List of products in the category.')\n    url = graphene.String(\n        description='The storefront\\'s URL for the category.')\n    ancestors = DjangoFilterConnectionField(\n        lambda: Category,\n        description='List of ancestors of the category.')\n    children = DjangoFilterConnectionField(\n        lambda: Category,\n        description='List of children of the category.')\n    background_image = graphene.Field(Image)\n\n    class Meta:\n        description = \"\"\"Represents a single category of products. Categories\n        allow to organize products in a tree-hierarchies which can be used for\n        navigation in the storefront.\"\"\"\n        exclude_fields = [\n            'lft', 'rght', 'tree_id', 'voucher_set', 'sale_set',\n            'menuitem_set']\n        interfaces = [relay.Node]\n        filter_fields = ['id', 'name']\n        model = models.Category\n\n    def resolve_ancestors(self, info, **kwargs):\n        return self.get_ancestors().distinct()\n\n    def resolve_children(self, info, **kwargs):\n        return self.children.distinct()\n\n    def resolve_url(self, info):\n        return self.get_absolute_url()\n\n    def resolve_products(self, info, **kwargs):\n        qs = models.Product.objects.available_products()\n        qs = qs.filter(category=self)\n        return qs.distinct()\n\n\nclass ProductImage(CountableDjangoObjectType):\n    url = graphene.String(\n        required=True,\n        description='The URL of the image.',\n        size=graphene.Int(description='Size of the image'))\n\n    class Meta:\n        description = 'Represents a product image.'\n        exclude_fields = [\n            'image', 'product', 'ppoi', 'productvariant_set',\n            'variant_images']\n        interfaces = [relay.Node]\n        model = models.ProductImage\n\n    def resolve_url(self, info, *, size=None):\n        if size:\n            return get_thumbnail(self.image, size, method='thumbnail')\n        return self.image.url\n", "path": "saleor/graphql/product/types.py"}]}