internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: saleor/saleor

Issue:
Dashboard 2.0: Consider maintaining the GraphQL schema in DSL instead of JSON
Currently we use `python manage.py graphql_schema` to save the schema in JSON format. Most tools can't consume this format so it's useless for features like editor IntelliSense. It being JSON also means it's almost impossible to tell what changed when reviewing a pull request.

I suggest using a dedicated tool to dump the schema in GraphQL DSL format instead. It's text and it's easily diffable during review plus it can be used together with tools like [`gql`](https://www.npmjs.com/package/@playlyfe/gql) to get autocomplete in IDEs. The main downside is that external tools require the server to be running before the schema can be generated.

Potential candidates:
* [`graphql-cli`](https://github.com/graphql-cli/graphql-cli)
* [`get-graphql-schema`](https://github.com/prisma/get-graphql-schema)


Discussion:
GraphQL-Core provides the `print_schema` utility that takes GraphQLSchema and returns `str` with schema's SDL. I'll PR with management command printing SDL on stdout for us to print in terminal or redirect to file.

Resolving patch:
diff --git a/saleor/graphql/management/__init__.py b/saleor/graphql/management/__init__.py
new file mode 100644
diff --git a/saleor/graphql/management/commands/__init__.py b/saleor/graphql/management/commands/__init__.py
new file mode 100644
diff --git a/saleor/graphql/management/commands/get_graphql_schema.py b/saleor/graphql/management/commands/get_graphql_schema.py
new file mode 100644
--- /dev/null
+++ b/saleor/graphql/management/commands/get_graphql_schema.py
@@ -0,0 +1,11 @@
+from django.core.management.base import BaseCommand
+from graphql import print_schema
+
+from ...api import schema
+
+
+class Command(BaseCommand):
+    help = 'Writes SDL for GraphQL API schema to stdout'
+
+    def handle(self, *args, **options):
+        self.stdout.write(print_schema(schema))


Test patch:
Repository: saleor/saleor

Issue:
Creating empty order draft causes API to explode
### What I'm trying to achieve
To get order draft details from API.

### Steps to reproduce the problem
Execute this query
```
{
  orders {
    edges {
      node {
        id
        userEmail
      }
    }
  }
}
```

### What I expected to happen
Definitely not to throw an error.


Discussion:
Blocks #2754 
On it

Resolving patch:
diff --git a/saleor/graphql/order/types.py b/saleor/graphql/order/types.py
--- a/saleor/graphql/order/types.py
+++ b/saleor/graphql/order/types.py
@@ -98,6 +98,8 @@ class Order(CountableDjangoObjectType):
     events = graphene.List(
         OrderEvent,
         description='List of events associated with the order.')
+    user_email = graphene.String(
+        required=False, description='Email address of the customer.')
 
     class Meta:
         description = 'Represents an order in the shop.'
@@ -157,6 +159,7 @@ def resolve_user_email(obj, info):
             return obj.user_email
         if obj.user_id:
             return obj.user.email
+        return None
 
 
 class OrderLine(CountableDjangoObjectType):


Test patch:
Repository: saleor/saleor

Issue:
OrderCancel mutation throws error when asked not to restock inventory
### What I'm trying to achieve
Cancel order without doing restock.

### Steps to reproduce the problem
1. Execute query
```
mutation {
  orderCancel(id: "T3JkZXI6MTQ=", restock: false) {
    errors {
      field
      message
    }
    order {
      id
    }
  }
}
```
2. Get an error
3. Order is cancelled anyway

### What I expected to happen
To work perfectly. Note: if `restock: true`, mutation executes properly.



Discussion:

Resolving patch:
diff --git a/saleor/graphql/order/mutations/orders.py b/saleor/graphql/order/mutations/orders.py
--- a/saleor/graphql/order/mutations/orders.py
+++ b/saleor/graphql/order/mutations/orders.py
@@ -247,8 +247,7 @@ def mutate(cls, root, info, id, restock):
                 parameters={'quantity': order.get_total_quantity()})
         else:
             order.events.create(
-                type=OrderEvents.ORDER_CANCELED.value,
-                user=info.context.user)
+                type=OrderEvents.CANCELED.value, user=info.context.user)
         return OrderCancel(order=order)
 
 


Test patch:
diff --git a/tests/api/test_order.py b/tests/api/test_order.py
--- a/tests/api/test_order.py
+++ b/tests/api/test_order.py
@@ -437,17 +437,20 @@ def test_order_add_note(admin_api_client, order_with_lines, admin_user):
     assert event.parameters == {'message': message}
 
 
-def test_order_cancel(admin_api_client, order_with_lines):
-    order = order_with_lines
-    query = """
-        mutation cancelOrder($id: ID!, $restock: Boolean!) {
-            orderCancel(id: $id, restock: $restock) {
-                order {
-                    status
-                }
+CANCEL_ORDER_QUERY = """
+    mutation cancelOrder($id: ID!, $restock: Boolean!) {
+        orderCancel(id: $id, restock: $restock) {
+            order {
+                status
             }
         }
-    """
+    }
+"""
+
+
+def test_order_cancel_and_restock(admin_api_client, order_with_lines):
+    order = order_with_lines
+    query = CANCEL_ORDER_QUERY
     order_id = graphene.Node.to_global_id('Order', order.id)
     restock = True
     quantity = order.get_total_quantity()
@@ -463,6 +466,22 @@ def test_order_cancel(admin_api_client, order_with_lines):
     assert data['status'] == order.status.upper()
 
 
+def test_order_cancel(admin_api_client, order_with_lines):
+    order = order_with_lines
+    query = CANCEL_ORDER_QUERY
+    order_id = graphene.Node.to_global_id('Order', order.id)
+    restock = False
+    variables = json.dumps({'id': order_id, 'restock': restock})
+    response = admin_api_client.post(
+        reverse('api'), {'query': query, 'variables': variables})
+    content = get_graphql_content(response)
+    data = content['data']['orderCancel']['order']
+    order.refresh_from_db()
+    order_event = order.events.last()
+    assert order_event.type == OrderEvents.CANCELED.value
+    assert data['status'] == order.status.upper()
+
+
 def test_order_capture(admin_api_client, payment_preauth, admin_user):
     order = payment_preauth.order
     query = """
Repository: saleor/saleor

Issue:
Return permissions provided by Shop object
### What I'm trying to achieve
To obtain user permissions.

### Steps to reproduce the problem
1. Execute query against admin user
```
{
  user {
    permissions {
      code
    }
  }
  shop {
    permissions {
      code
    }
  }
}
```
2. Compare permissions returned by user and shop objects

### What I expected to happen
user:permissions should be subset of shop:permissions. Instead, it's superset.


Discussion:
Blocks #2593 

Resolving patch:
diff --git a/saleor/graphql/account/types.py b/saleor/graphql/account/types.py
--- a/saleor/graphql/account/types.py
+++ b/saleor/graphql/account/types.py
@@ -1,8 +1,9 @@
 import graphene
-from django.contrib.auth import get_user_model, models as auth_models
+from django.contrib.auth import get_user_model
 from graphene import relay
 
 from ...account import models
+from ...core.permissions import get_permissions
 from ..core.types.common import CountableDjangoObjectType, PermissionDisplay
 from ..utils import format_permissions_for_display
 
@@ -43,12 +44,10 @@ class Meta:
 
     def resolve_permissions(self, info, **kwargs):
         if self.is_superuser:
-            permissions = auth_models.Permission.objects.all()
+            permissions = get_permissions()
         else:
-            permissions = (
-                self.user_permissions.all() |
-                auth_models.Permission.objects.filter(group__user=self))
-        permissions = permissions.select_related('content_type')
+            permissions = self.user_permissions.prefetch_related(
+                'content_type').order_by('codename')
         return format_permissions_for_display(permissions)
 
 


Test patch:
Repository: saleor/saleor

Issue:
Add access control headers to GraphQL API
### What I'm trying to achieve
I would like to be able to access API from a domain different that API in the browser

### Describe a proposed solution
I will add proper headers in the `FileUploadGraphQLView` which is our main one.



Discussion:

Resolving patch:
diff --git a/saleor/graphql/file_upload/views.py b/saleor/graphql/file_upload/views.py
--- a/saleor/graphql/file_upload/views.py
+++ b/saleor/graphql/file_upload/views.py
@@ -1,12 +1,27 @@
-from graphene_django.views import GraphQLView
 import json
+
 import graphene
+from django.conf import settings
+from graphene_django.views import GraphQLView
 
 # This class is modified verion of the `ModifiedGraphQLView` class from
 # `graphene-file-upload` (https://github.com/lmcgartland/graphene-file-upload).
 
 
 class FileUploadGraphQLView(GraphQLView):
+    def dispatch(self, request, *args, **kwargs):
+        # Handle options method the GraphQlView restricts it.
+        if request.method == 'OPTIONS':
+            response = self.options(request, *args, **kwargs)
+        else:
+            response = super().dispatch(request, *args, **kwargs)
+        # Add access control headers
+        response['Access-Control-Allow-Origin'] = ','.join(
+            settings.ALLOWED_HOSTS)
+        response['Access-Control-Allow-Methods'] = 'POST, OPTIONS'
+        response[
+            'Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization'
+        return response
 
     @staticmethod
     def get_graphql_params(request, data):


Test patch:
Repository: saleor/saleor

Issue:
Make sure all GraphQL types that accept or return a country code use an enum type
Currently some types return an enum, some return a string and `VoucherInput` takes a list of strings.


Discussion:

Resolving patch:
diff --git a/saleor/graphql/shipping/types.py b/saleor/graphql/shipping/types.py
--- a/saleor/graphql/shipping/types.py
+++ b/saleor/graphql/shipping/types.py
@@ -8,7 +8,7 @@
 
 from ...core.weight import convert_weight, get_default_weight_unit
 from ...shipping import ShippingMethodType, models
-from ..core.types.common import CountableDjangoObjectType
+from ..core.types.common import CountableDjangoObjectType, CountryDisplay
 from ..core.types.money import MoneyRange
 
 ShippingMethodTypeEnum = graphene.Enum(
@@ -33,7 +33,7 @@ class ShippingZone(CountableDjangoObjectType):
     price_range = graphene.Field(
         MoneyRange, description='Lowest and highest prices for the shipping.')
     countries = graphene.List(
-        graphene.String,
+        CountryDisplay,
         description='List of countries available for the method.')
     shipping_methods = graphene.List(
         ShippingMethod,
@@ -57,7 +57,9 @@ def resolve_price_range(self, info):
         return self.price_range
 
     def resolve_countries(self, info):
-        return self.countries
+        return [
+            CountryDisplay(code=country.code, country=country.name)
+            for country in self.countries]
 
     def resolve_shipping_methods(self, info):
         return self.shipping_methods.all()


Test patch:
diff --git a/tests/api/test_shipping_method.py b/tests/api/test_shipping_method.py
--- a/tests/api/test_shipping_method.py
+++ b/tests/api/test_shipping_method.py
@@ -134,7 +134,9 @@ def test_shipping_zones_query(user_api_client, shipping_zone):
             }
             shippingZone {
                 name
-                countries
+                countries {
+                    code
+                }
                 default
             }
         }
@@ -153,7 +155,7 @@ def test_create_shipping_zone(admin_api_client):
     assert not data['errors']
     zone = data['shippingZone']
     assert zone['name'] == 'test shipping'
-    assert zone['countries'] == ['PL']
+    assert zone['countries'] == [{'code': 'PL'}]
     assert zone['default'] == False
Repository: saleor/saleor

Issue:
Cannot create fulfillment group without specifying tracking number
### Steps to reproduce the problem
1. Execute the following query without specifying `trackingNumber` in `FulfillmentCreateInput`:
```
mutation OrderCreateFulfillment($order: ID!, $input: FulfillmentCreateInput!) {
  orderFulfillmentCreate(order: $order, input: $input) {
    errors {
      field
      message
    }
  }
}
"
```
2. Get an error:
```
{"errors":[{"message":"'tracking_number'","locations":[{"line":167,"column":3}],"path":["orderFulfillmentCreate"]}],"data":{"orderFulfillmentCreate":null}}
```

### What I expected to happen
To successfully create fulfillment group.


Discussion:

Resolving patch:
diff --git a/saleor/graphql/order/mutations/fulfillments.py b/saleor/graphql/order/mutations/fulfillments.py
--- a/saleor/graphql/order/mutations/fulfillments.py
+++ b/saleor/graphql/order/mutations/fulfillments.py
@@ -130,7 +130,8 @@ def mutate(cls, root, info, order, input):
         if errors:
             return cls(errors=errors)
         fulfillment = models.Fulfillment(
-            tracking_number=input.pop('tracking_number') or '', order=order)
+            tracking_number=input.pop('tracking_number', None) or '',
+            order=order)
         cleaned_input = cls.clean_input(input, errors)
         if errors:
             return cls(errors=errors)


Test patch:
Repository: saleor/saleor

Issue:
Make Meta description required for the BaseMutations
I think we should force developers to describe what their APIs are doing. 


Discussion:

Resolving patch:
diff --git a/saleor/graphql/core/mutations.py b/saleor/graphql/core/mutations.py
--- a/saleor/graphql/core/mutations.py
+++ b/saleor/graphql/core/mutations.py
@@ -45,6 +45,12 @@ class BaseMutation(graphene.Mutation):
     class Meta:
         abstract = True
 
+    @classmethod
+    def __init_subclass_with_meta__(cls, description=None, **options):
+        if not description:
+            raise ImproperlyConfigured('No description provided in Meta')
+        super().__init_subclass_with_meta__(description=description, **options)
+
     @classmethod
     def _update_mutation_arguments_and_fields(cls, arguments, fields):
         cls._meta.arguments.update(arguments)
diff --git a/saleor/graphql/shop/mutations.py b/saleor/graphql/shop/mutations.py
--- a/saleor/graphql/shop/mutations.py
+++ b/saleor/graphql/shop/mutations.py
@@ -31,6 +31,9 @@ class Arguments:
             description='Fields required to update shop settings.',
             required=True)
 
+    class Meta:
+        description = 'Updates shop settings'
+
     shop = graphene.Field(
         Shop, description='Updated Shop')
 
@@ -56,6 +59,9 @@ class ShopDomainUpdate(BaseMutation):
     class Arguments:
         input = SiteDomainInput(description='Fields required to update site')
 
+    class Meta:
+        description = 'Updates site domain of the shop'
+
     shop = graphene.Field(Shop, description='Updated Shop')
 
     @classmethod
@@ -81,6 +87,9 @@ class Arguments:
         collection = graphene.ID(
             description='Collection displayed on homepage')
 
+    class Meta:
+        description = 'Updates homepage collection of the shop'
+
     shop = graphene.Field(
         Shop, description='Updated Shop')
 


Test patch:
diff --git a/tests/api/test_base_mutation.py b/tests/api/test_base_mutation.py
--- a/tests/api/test_base_mutation.py
+++ b/tests/api/test_base_mutation.py
@@ -1,4 +1,6 @@
 import graphene
+import pytest
+from django.core.exceptions import ImproperlyConfigured
 from saleor.graphql.core.mutations import BaseMutation
 from saleor.graphql.product import types as product_types
 
@@ -9,6 +11,9 @@ class Mutation(BaseMutation):
     class Arguments:
         product_id = graphene.ID(required=True)
 
+    class Meta:
+        description = 'Base mutation'
+
     @classmethod
     def mutate(cls, root, info, product_id):
         errors = []
@@ -28,6 +33,15 @@ class Mutations(graphene.ObjectType):
     types=[product_types.Product, product_types.ProductVariant])
 
 
+def test_mutation_without_description_raises_error():
+    with pytest.raises(ImproperlyConfigured) as exc_info:
+        class MutationNoDescription(BaseMutation):
+            name = graphene.Field(graphene.String)
+
+            class Arguments:
+                product_id = graphene.ID(required=True)
+
+
 def test_resolve_id(product):
     product_id = graphene.Node.to_global_id('Product', product.pk)
     query = """
Repository: saleor/saleor

Issue:
[Backend] [Cosmetics] CELERY_TASK_ALWAYS_EAGER strange set value
In settings present strange code: 
`CELERY_TASK_ALWAYS_EAGER = False if CELERY_BROKER_URL else True`

can easy rewrite to:
`CELERY_TASK_ALWAYS_EAGER = not CELERY_BROKER_URL`



Discussion:

Resolving patch:
diff --git a/saleor/settings.py b/saleor/settings.py
--- a/saleor/settings.py
+++ b/saleor/settings.py
@@ -480,7 +480,7 @@ def get_host():
 # CELERY SETTINGS
 CELERY_BROKER_URL = os.environ.get(
     'CELERY_BROKER_URL', os.environ.get('CLOUDAMQP_URL')) or ''
-CELERY_TASK_ALWAYS_EAGER = False if CELERY_BROKER_URL else True
+CELERY_TASK_ALWAYS_EAGER = not CELERY_BROKER_URL
 CELERY_ACCEPT_CONTENT = ['json']
 CELERY_TASK_SERIALIZER = 'json'
 CELERY_RESULT_SERIALIZER = 'json'


Test patch:
Repository: saleor/saleor

Issue:
Error when updateHomepageCollection is given null as collection
### What I'm trying to achieve
To remove homepage collection.

### Steps to reproduce the problem
1. Execute updateHomepageCollection with `collection: null`
2. Get an error ` "mutate() missing 1 required positional argument: 'collection'"`

### What I expected to happen
To return shop object with `homepageCollection` field being `null`.


Discussion:

Resolving patch:
diff --git a/saleor/graphql/shop/mutations.py b/saleor/graphql/shop/mutations.py
--- a/saleor/graphql/shop/mutations.py
+++ b/saleor/graphql/shop/mutations.py
@@ -93,10 +93,12 @@ class Meta:
 
     @classmethod
     @permission_required('site.manage_settings')
-    def mutate(cls, root, info, collection):
+    def mutate(cls, root, info, collection=None):
         errors = []
-        new_collection = cls.get_node_or_error(
-            info, collection, errors, 'collection', Collection)
+        new_collection = None
+        if collection:
+            new_collection = cls.get_node_or_error(
+                info, collection, errors, 'collection', Collection)
         if errors:
             return HomepageCollectionUpdate(errors=errors)
         site_settings = info.context.site.settings


Test patch:
diff --git a/tests/api/test_shop.py b/tests/api/test_shop.py
--- a/tests/api/test_shop.py
+++ b/tests/api/test_shop.py
@@ -242,6 +242,32 @@ def test_homepage_collection_update(
     assert site.settings.homepage_collection == collection
 
 
+def test_homepage_collection_update_set_null(
+        staff_api_client, collection, site_settings,
+        permission_manage_settings):
+    query = """
+        mutation homepageCollectionUpdate($collection: ID) {
+            homepageCollectionUpdate(collection: $collection) {
+                shop {
+                    homepageCollection {
+                        id
+                    }
+                }
+            }
+        }
+    """
+    site_settings.homepage_collection = collection
+    site_settings.save()
+    variables = {'collection': None}
+    response = staff_api_client.post_graphql(
+        query, variables, permissions=[permission_manage_settings])
+    content = get_graphql_content(response)
+    data = content['data']['homepageCollectionUpdate']['shop']
+    assert data['homepageCollection'] is None
+    site_settings.refresh_from_db()
+    assert site_settings.homepage_collection is None
+
+
 def test_query_default_country(user_api_client, settings):
     settings.DEFAULT_COUNTRY = 'US'
     query = """
Repository: saleor/saleor

Issue:
collectionUpdate should not be able to override `products` field
Currently, it is possible to completely override the collection's product list with `collectionUpdate` mutation, because it accepts `CollectionInput` as an argument, which has `products` field. Thus should not be possible, because there are dedicated mutations to either add or remove products from collection.


Discussion:

Resolving patch:
diff --git a/saleor/graphql/product/mutations/products.py b/saleor/graphql/product/mutations/products.py
--- a/saleor/graphql/product/mutations/products.py
+++ b/saleor/graphql/product/mutations/products.py
@@ -79,17 +79,20 @@ class CollectionInput(graphene.InputObjectType):
         description='Informs whether a collection is published.')
     name = graphene.String(description='Name of the collection.')
     slug = graphene.String(description='Slug of the collection.')
+    background_image = Upload(description='Background image file.')
+    seo = SeoInput(description='Search engine optimization fields.')
+
+
+class CollectionCreateInput(CollectionInput):
     products = graphene.List(
         graphene.ID,
         description='List of products to be added to the collection.',
         name='products')
-    background_image = Upload(description='Background image file.')
-    seo = SeoInput(description='Search engine optimization fields.')
 
 
 class CollectionCreate(ModelMutation):
     class Arguments:
-        input = CollectionInput(
+        input = CollectionCreateInput(
             required=True,
             description='Fields required to create a collection.')
 


Test patch:
Repository: saleor/saleor

Issue:
User type should be able to return `created` and `lastLogin` fields
There is no way to obtain information when a user was registered and when she/he logged last time.


Discussion:

Resolving patch:
diff --git a/saleor/graphql/account/resolvers.py b/saleor/graphql/account/resolvers.py
--- a/saleor/graphql/account/resolvers.py
+++ b/saleor/graphql/account/resolvers.py
@@ -16,14 +16,16 @@ def resolve_customers(info, query):
     qs = models.User.objects.filter(
         Q(is_staff=False) | (Q(is_staff=True) & Q(orders__isnull=False))
     ).prefetch_related('addresses')
-    return filter_by_query_param(
+    qs = filter_by_query_param(
         queryset=qs, query=query, search_fields=USER_SEARCH_FIELDS)
+    return qs.distinct()
 
 
 def resolve_staff_users(info, query):
     qs = models.User.objects.filter(is_staff=True)
-    return filter_by_query_param(
+    qs = filter_by_query_param(
         queryset=qs, query=query, search_fields=USER_SEARCH_FIELDS)
+    return qs.distinct()
 
 
 def resolve_address_validator(info, input):
diff --git a/saleor/graphql/account/types.py b/saleor/graphql/account/types.py
--- a/saleor/graphql/account/types.py
+++ b/saleor/graphql/account/types.py
@@ -42,9 +42,7 @@ class User(CountableDjangoObjectType):
     permissions = graphene.List(PermissionDisplay)
 
     class Meta:
-        exclude_fields = [
-            'date_joined', 'password', 'is_superuser',
-            'OrderEvent_set', 'last_login']
+        exclude_fields = ['password', 'is_superuser', 'OrderEvent_set']
         description = 'Represents user data.'
         interfaces = [relay.Node]
         model = get_user_model()


Test patch:
diff --git a/tests/api/test_account.py b/tests/api/test_account.py
--- a/tests/api/test_account.py
+++ b/tests/api/test_account.py
@@ -97,6 +97,8 @@ def test_query_user(staff_api_client, customer_user, permission_manage_users):
             orders {
                 totalCount
             }
+            dateJoined
+            lastLogin
             defaultShippingAddress {
                 firstName
                 lastName