Repository: saleor/saleor

Issue:
`parent` field should not be included in `CategoryInput` type
`CategoryInput` should be consistent with the rest of `...Input` types and take only those arguments, which reference what object looks like, and not what is its place. Therefore, `categoryCreate` mutation should have signature `categoryCreate(input: CategoryInput!, parentId: ID)`.


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
@@ -11,17 +11,14 @@
 from ...core.utils import clean_seo_fields
 from ...file_upload.types import Upload
 from ...shipping.types import WeightScalar
-from ..types import Collection, Product, ProductImage, ProductVariant
+from ..types import Category, Collection, Product, ProductImage, ProductVariant
 from ..utils import attributes_to_hstore, update_variants_names
 
 
 class CategoryInput(graphene.InputObjectType):
     description = graphene.String(description='Category description')
     name = graphene.String(description='Category name')
-    parent = graphene.ID(
-        description='''
-        ID of the parent category. If empty, category will be top level
-        category.''', name='parent')
+
     slug = graphene.String(description='Category slug')
     seo = SeoInput(description='Search engine optimization fields.')
     background_image = Upload(description='Background image file.')
@@ -31,6 +28,10 @@ class CategoryCreate(ModelMutation):
     class Arguments:
         input = CategoryInput(
             required=True, description='Fields required to create a category.')
+        parent_id = graphene.ID(
+            description='''
+                ID of the parent category. If empty, category will be top level
+                category.''', name='parent')
 
     class Meta:
         description = 'Creates a new category.'
@@ -41,6 +42,11 @@ def clean_input(cls, info, instance, input, errors):
         cleaned_input = super().clean_input(info, instance, input, errors)
         if 'slug' not in cleaned_input:
             cleaned_input['slug'] = slugify(cleaned_input['name'])
+        parent_id = input['parent_id']
+        if parent_id:
+            parent = cls.get_node_or_error(
+                info, parent_id, errors, field='parent', only_type=Category)
+            cleaned_input['parent'] = parent
         clean_seo_fields(cleaned_input)
         return cleaned_input
 
@@ -48,6 +54,12 @@ def clean_input(cls, info, instance, input, errors):
     def user_is_allowed(cls, user, input):
         return user.has_perm('product.manage_products')
 
+    @classmethod
+    def mutate(cls, root, info, **data):
+        parent_id = data.pop('parent_id', None)
+        data['input']['parent_id'] = parent_id
+        return super().mutate(root, info, **data)
+
 
 class CategoryUpdate(CategoryCreate):
     class Arguments:


Test patch:
diff --git a/tests/api/test_category.py b/tests/api/test_category.py
--- a/tests/api/test_category.py
+++ b/tests/api/test_category.py
@@ -54,8 +54,8 @@ def test_category_create_mutation(
                     slug: $slug
                     description: $description
                     backgroundImage: $backgroundImage
-                    parent: $parentId
-                }
+                },
+                parent: $parentId
             ) {
                 category {
                     id
diff --git a/tests/api/test_core.py b/tests/api/test_core.py
--- a/tests/api/test_core.py
+++ b/tests/api/test_core.py
@@ -22,7 +22,7 @@ def test_user_error_field_name_for_related_object(
         staff_api_client, permission_manage_products):
     query = """
     mutation {
-        categoryCreate(input: {name: "Test", parent: "123456"}) {
+        categoryCreate(input: {name: "Test"}, parent: "123456") {
             errors {
                 field
                 message