We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Getting error when category has no background image
### Steps to reproduce the problem
1. Create category with no background image
2. Exec query
```
{
  category {
    backgroundImage {
      url
    }
  }
}
```
3. Get an error
```
{
  "errors": [
    {
      "message": "The 'background_image' attribute has no file associated with it.",
      "locations": [
        {
          "line": 6,
          "column": 7
        }
      ],
      "path": [
        "categories",
        "edges",
        1,
        "node",
        "backgroundImage",
        "url"
      ]
    }
  ]
}
```

### What I expected to happen
To successfully fetch category data.

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

{"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@@ -296,6 +296,9 @@\n         interfaces = [relay.Node]\n         model = models.Collection\n \n+    def resolve_background_image(self, info, **kwargs):\n+        return self.background_image or None\n+\n     def resolve_products(self, info, **kwargs):\n         user = info.context.user\n         return products_with_details(\n@@ -331,6 +334,9 @@\n     def resolve_ancestors(self, info, **kwargs):\n         return self.get_ancestors().distinct()\n \n+    def resolve_background_image(self, info, **kwargs):\n+        return self.background_image or None\n+\n     def resolve_children(self, info, **kwargs):\n         return self.children.distinct()\n", "issue": "Getting error when category has no background image\n### Steps to reproduce the problem\r\n1. Create category with no background image\r\n2. Exec query\r\n```\r\n{\r\n  category {\r\n    backgroundImage {\r\n      url\r\n    }\r\n  }\r\n}\r\n```\r\n3. Get an error\r\n```\r\n{\r\n  \"errors\": [\r\n    {\r\n      \"message\": \"The 'background_image' attribute has no file associated with it.\",\r\n      \"locations\": [\r\n        {\r\n          \"line\": 6,\r\n          \"column\": 7\r\n        }\r\n      ],\r\n      \"path\": [\r\n        \"categories\",\r\n        \"edges\",\r\n        1,\r\n        \"node\",\r\n        \"backgroundImage\",\r\n        \"url\"\r\n      ]\r\n    }\r\n  ]\r\n}\r\n```\r\n\r\n### What I expected to happen\r\nTo successfully fetch category data.\n", "before_files": [{"content": "import re\n\nimport 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 .descriptions import AttributeDescriptions, AttributeValueDescriptions\nfrom .filters import ProductFilterSet\n\nCOLOR_PATTERN = r'^(#[0-9a-fA-F]{3}|#(?:[0-9a-fA-F]{2}){2,4}|(rgb|hsl)a?\\((-?\\d+%?[,\\s]+){2,3}\\s*[\\d\\.]+%?\\))$'  # noqa\ncolor_pattern = re.compile(COLOR_PATTERN)\n\n\nclass AttributeValueType(graphene.Enum):\n    COLOR = 'COLOR'\n    GRADIENT = 'GRADIENT'\n    URL = 'URL'\n    STRING = 'STRING'\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.Attribute.objects.filter(\n            pk__in=keys)}\n    values_map = {\n        val.pk: val for val in models.AttributeValue.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\ndef resolve_attribute_value_type(attribute_value):\n    if color_pattern.match(attribute_value):\n        return AttributeValueType.COLOR\n    if 'gradient(' in attribute_value:\n        return AttributeValueType.GRADIENT\n    if '://' in attribute_value:\n        return AttributeValueType.URL\n    return AttributeValueType.STRING\n\n\nclass AttributeValue(CountableDjangoObjectType):\n    name = graphene.String(description=AttributeValueDescriptions.NAME)\n    slug = graphene.String(description=AttributeValueDescriptions.SLUG)\n    type = AttributeValueType(description=AttributeValueDescriptions.TYPE)\n    value = graphene.String(description=AttributeValueDescriptions.VALUE)\n\n    class Meta:\n        description = 'Represents a value of an attribute.'\n        exclude_fields = ['attribute']\n        interfaces = [relay.Node]\n        model = models.AttributeValue\n\n    def resolve_type(self, info):\n        return resolve_attribute_value_type(self.value)\n\n\nclass Attribute(CountableDjangoObjectType):\n    name = graphene.String(description=AttributeDescriptions.NAME)\n    slug = graphene.String(description=AttributeDescriptions.SLUG)\n    values = graphene.List(\n        AttributeValue, description=AttributeDescriptions.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 = []\n        interfaces = [relay.Node]\n        filter_fields = ['id', 'slug']\n        model = models.Attribute\n\n    def resolve_values(self, info):\n        return self.values.all()\n\n\nclass AttributeTypeEnum(graphene.Enum):\n    PRODUCT = 'PRODUCT'\n    VARIANT = 'VARIANT'\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        Attribute, default_value=None, description=AttributeDescriptions.NAME)\n    value = graphene.Field(\n        AttributeValue,\n        default_value=None, description='Value of an attribute.')\n\n    class Meta:\n        description = 'Represents a custom 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 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    variant_attributes = graphene.List(\n        Attribute, description='Variant attributes of that product type.')\n    product_attributes = graphene.List(\n        Attribute, description='Product attributes of that product type.')\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    def resolve_variant_attributes(self, info):\n        return self.variant_attributes.prefetch_related('values')\n\n    def resolve_product_attributes(self, info):\n        return self.product_attributes.prefetch_related('values')\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().prefetch_related(\n            'variants', 'images', 'product_type')\n        categories_tree = self.get_descendants(include_self=True)\n        qs = qs.filter(category__in=categories_tree)\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 re\n\nimport 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 .descriptions import AttributeDescriptions, AttributeValueDescriptions\nfrom .filters import ProductFilterSet\n\nCOLOR_PATTERN = r'^(#[0-9a-fA-F]{3}|#(?:[0-9a-fA-F]{2}){2,4}|(rgb|hsl)a?\\((-?\\d+%?[,\\s]+){2,3}\\s*[\\d\\.]+%?\\))$'  # noqa\ncolor_pattern = re.compile(COLOR_PATTERN)\n\n\nclass AttributeValueType(graphene.Enum):\n    COLOR = 'COLOR'\n    GRADIENT = 'GRADIENT'\n    URL = 'URL'\n    STRING = 'STRING'\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.Attribute.objects.filter(\n            pk__in=keys)}\n    values_map = {\n        val.pk: val for val in models.AttributeValue.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\ndef resolve_attribute_value_type(attribute_value):\n    if color_pattern.match(attribute_value):\n        return AttributeValueType.COLOR\n    if 'gradient(' in attribute_value:\n        return AttributeValueType.GRADIENT\n    if '://' in attribute_value:\n        return AttributeValueType.URL\n    return AttributeValueType.STRING\n\n\nclass AttributeValue(CountableDjangoObjectType):\n    name = graphene.String(description=AttributeValueDescriptions.NAME)\n    slug = graphene.String(description=AttributeValueDescriptions.SLUG)\n    type = AttributeValueType(description=AttributeValueDescriptions.TYPE)\n    value = graphene.String(description=AttributeValueDescriptions.VALUE)\n\n    class Meta:\n        description = 'Represents a value of an attribute.'\n        exclude_fields = ['attribute']\n        interfaces = [relay.Node]\n        model = models.AttributeValue\n\n    def resolve_type(self, info):\n        return resolve_attribute_value_type(self.value)\n\n\nclass Attribute(CountableDjangoObjectType):\n    name = graphene.String(description=AttributeDescriptions.NAME)\n    slug = graphene.String(description=AttributeDescriptions.SLUG)\n    values = graphene.List(\n        AttributeValue, description=AttributeDescriptions.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 = []\n        interfaces = [relay.Node]\n        filter_fields = ['id', 'slug']\n        model = models.Attribute\n\n    def resolve_values(self, info):\n        return self.values.all()\n\n\nclass AttributeTypeEnum(graphene.Enum):\n    PRODUCT = 'PRODUCT'\n    VARIANT = 'VARIANT'\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        Attribute, default_value=None, description=AttributeDescriptions.NAME)\n    value = graphene.Field(\n        AttributeValue,\n        default_value=None, description='Value of an attribute.')\n\n    class Meta:\n        description = 'Represents a custom 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 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    variant_attributes = graphene.List(\n        Attribute, description='Variant attributes of that product type.')\n    product_attributes = graphene.List(\n        Attribute, description='Product attributes of that product type.')\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    def resolve_variant_attributes(self, info):\n        return self.variant_attributes.prefetch_related('values')\n\n    def resolve_product_attributes(self, info):\n        return self.product_attributes.prefetch_related('values')\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_background_image(self, info, **kwargs):\n        return self.background_image or None\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_background_image(self, info, **kwargs):\n        return self.background_image or None\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().prefetch_related(\n            'variants', 'images', 'product_type')\n        categories_tree = self.get_descendants(include_self=True)\n        qs = qs.filter(category__in=categories_tree)\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"}]}