Skip to content

Commit 8e4caea

Browse files
committed
add create product image
see saleor/saleor#5337
1 parent 0916ffd commit 8e4caea

File tree

3 files changed

+158
-2
lines changed

3 files changed

+158
-2
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ As of now, `saleor-gql-loader` allows to create the following entities:
1414
- [x] category
1515
- [x] product
1616
- [x] product_variant
17+
- [x] product_image
1718

1819
PR for supporting more graphQL mutations and/or queries are more than welcome.
1920

@@ -28,7 +29,7 @@ team/community._
2829
using Pypi:
2930

3031
```bash
31-
pip install saleor-gql-loader
32+
pip install saleor-gql-loader requests-toolbelt Django
3233
```
3334

3435
Or cloning the repo:

saleor_gql_loader/data_loader.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
project for easier testing.
1111
1212
"""
13-
from .utils import graphql_request, override_dict, handle_errors
13+
from .utils import graphql_request, graphql_multipart_request, override_dict, handle_errors, get_payload
1414

1515

1616
class ETLDataLoader:
@@ -517,3 +517,33 @@ def create_product_variant(self, product_id, **kwargs):
517517
handle_errors(errors)
518518

519519
return response["data"]["productVariantCreate"]["productVariant"]["id"]
520+
521+
def create_product_image(self, product_id, file_path):
522+
"""create a product image.
523+
524+
Parameters
525+
----------
526+
product_id : str
527+
id for which the product image will be created.
528+
file_path : str
529+
path to the image to upload.
530+
531+
Returns
532+
-------
533+
id : str
534+
the id of the product image created.
535+
536+
Raises
537+
------
538+
Exception
539+
when productErrors is not an empty list.
540+
"""
541+
body = get_payload(product_id, file_path)
542+
543+
response = graphql_multipart_request(
544+
body, self.headers, self.endpoint_url)
545+
546+
errors = response["data"]["productImageCreate"]["productErrors"]
547+
handle_errors(errors)
548+
549+
return response["data"]["productImageCreate"]["image"]["id"]

saleor_gql_loader/utils.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
"""
88
import requests
99
import json
10+
from pathlib import Path
11+
from requests_toolbelt import MultipartEncoder
12+
from django.core.serializers.json import DjangoJSONEncoder
1013

1114
GQL_DEFAULT_ENDPOINT = "http://localhost:8000/graphql/"
1215

@@ -55,6 +58,45 @@ def graphql_request(query, variables={}, headers={},
5558
return parsed_response
5659

5760

61+
def graphql_multipart_request(body, headers, endpoint=GQL_DEFAULT_ENDPOINT):
62+
"""Execute a multipart graphQL query with `body` provided on the `endpoint`.
63+
64+
Parameters
65+
----------
66+
body : str
67+
payloads of graphQL query.
68+
headers : dict, optional
69+
headers added to the request (important for authentication).
70+
endpoint : str, optional
71+
the graphQL endpoint url that will be queried, default is
72+
`GQL_DEFAULT_ENDPOINT`.
73+
74+
Returns
75+
-------
76+
response : dict
77+
a dictionary corresponding to the parsed JSON graphQL response.
78+
79+
Raises
80+
------
81+
Exception
82+
when `response.status_code` is not 200.
83+
"""
84+
bodyEncoder = MultipartEncoder(body)
85+
base_headers = {
86+
"Content-Type": bodyEncoder.content_type,
87+
}
88+
override_dict(headers, base_headers)
89+
90+
response = requests.post(endpoint, data=bodyEncoder, headers=headers, timeout=90)
91+
92+
parsed_response = json.loads(response.text)
93+
if response.status_code != 200:
94+
raise Exception("{message}\n extensions: {extensions}".format(
95+
**parsed_response["errors"][0]))
96+
else:
97+
return parsed_response
98+
99+
58100
def override_dict(a, overrides):
59101
"""Override a dict with another one **only first non nested keys**.
60102
@@ -99,3 +141,86 @@ def handle_errors(errors):
99141
txt_list = [
100142
"{field} : {message}".format(**error) for error in errors]
101143
raise Exception("\n".join(txt_list))
144+
145+
def get_operations(product_id):
146+
"""Get ProductImageCreate operations
147+
148+
Parameters
149+
----------
150+
product_id : str
151+
id for which the product image will be created.
152+
153+
Returns
154+
-------
155+
query : str
156+
variables: dict
157+
"""
158+
query = """mutation ProductImageCreate($product: ID!, $image: Upload!, $alt: String) {
159+
productImageCreate(input: {alt: $alt, image: $image, product: $product}) {
160+
image{
161+
id
162+
}
163+
productErrors {
164+
field
165+
message
166+
}
167+
}
168+
}"""
169+
variables = {
170+
"product": product_id,
171+
"image": "0",
172+
"alt": ''
173+
}
174+
return {"query": query, "variables": variables}
175+
176+
def get_operations(product_id):
177+
"""Get ProductImageCreate operations
178+
179+
Parameters
180+
----------
181+
product_id : str
182+
id for which the product image will be created.
183+
184+
Returns
185+
-------
186+
query : str
187+
variables: dict
188+
"""
189+
query = """mutation ProductImageCreate($product: ID!, $image: Upload!, $alt: String) {
190+
productImageCreate(input: {alt: $alt, image: $image, product: $product}) {
191+
image{
192+
id
193+
}
194+
productErrors {
195+
field
196+
message
197+
}
198+
}
199+
}"""
200+
variables = {
201+
"product": product_id,
202+
"image": "0",
203+
"alt": ''
204+
}
205+
return {"query": query, "variables": variables}
206+
207+
def get_payload(product_id, file_path):
208+
"""Get ProductImageCreate operations
209+
210+
Parameters
211+
----------
212+
product_id : str
213+
id for which the product image will be created.
214+
215+
Returns
216+
-------
217+
query : str
218+
variables: dict
219+
"""
220+
return {
221+
"operations": json.dumps(
222+
get_operations(product_id), cls=DjangoJSONEncoder
223+
),
224+
"map": json.dumps({'0': ["variables.image"]}, cls=DjangoJSONEncoder),
225+
"0": (Path(file_path).name, open(file_path, 'rb'), 'image/png')
226+
}

0 commit comments

Comments
 (0)