tgshops-integrations 0.3__py3-none-any.whl → 1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tgshops_integrations/middlewares/gateway.py +56 -13
- tgshops_integrations/models/products.py +0 -6
- tgshops_integrations/nocodb_connector/categories.py +82 -16
- tgshops_integrations/nocodb_connector/client.py +115 -25
- tgshops_integrations/nocodb_connector/model_mapping.py +49 -45
- tgshops_integrations/nocodb_connector/products.py +20 -3
- tgshops_integrations-1.0.dist-info/METADATA +81 -0
- tgshops_integrations-1.0.dist-info/RECORD +16 -0
- tgshops_integrations-0.3.dist-info/METADATA +0 -75
- tgshops_integrations-0.3.dist-info/RECORD +0 -16
- {tgshops_integrations-0.3.dist-info → tgshops_integrations-1.0.dist-info}/WHEEL +0 -0
- {tgshops_integrations-0.3.dist-info → tgshops_integrations-1.0.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,6 @@
|
|
1
1
|
from typing import List,Optional
|
2
|
+
import importlib.util
|
3
|
+
from pathlib import Path
|
2
4
|
|
3
5
|
from aiocache import cached
|
4
6
|
from tgshops_integrations.models.products import ProductModel
|
@@ -10,46 +12,88 @@ from tgshops_integrations.nocodb_connector.model_mapping import dump_product_dat
|
|
10
12
|
from tgshops_integrations.nocodb_connector.categories import CategoryManager
|
11
13
|
from tgshops_integrations.nocodb_connector.products import ProductManager
|
12
14
|
from tgshops_integrations.nocodb_connector.tables import *
|
15
|
+
# from .config import NOCODB_CATEGORIES,NOCODB_PRODUCTS,NOCODB_STATUSES,NOCODB_BOT_MESSAGES,NOCODB_ORDERS
|
16
|
+
|
13
17
|
from loguru import logger
|
14
|
-
import hashlib
|
15
18
|
|
19
|
+
# Step 1: Define the path to config.py (one level above)
|
20
|
+
config_path = Path(__file__).resolve().parent.parent / '../config.py'
|
21
|
+
|
22
|
+
# Step 2: Load config.py dynamically using importlib
|
23
|
+
spec = importlib.util.spec_from_file_location("config", config_path)
|
24
|
+
config = importlib.util.module_from_spec(spec)
|
25
|
+
spec.loader.exec_module(config)
|
26
|
+
|
27
|
+
# Step 3: Access variables from config.py
|
28
|
+
NOCODB_CATEGORIES = config.NOCODB_CATEGORIES
|
29
|
+
NOCODB_PRODUCTS = config.NOCODB_PRODUCTS
|
30
|
+
NOCODB_STATUSES = config.NOCODB_STATUSES
|
31
|
+
NOCODB_BOT_MESSAGES = config.NOCODB_BOT_MESSAGES
|
32
|
+
NOCODB_ORDERS = config.NOCODB_ORDERS
|
16
33
|
|
17
34
|
class Gateway(NocodbClient):
|
18
35
|
|
19
|
-
def __init__(self,logging=False,NOCODB_HOST=None,NOCODB_API_KEY=None,SOURCE=None):
|
36
|
+
def __init__(self,logging=False,NOCODB_HOST=None,NOCODB_API_KEY=None,SOURCE=None,filter_buttons=[],special_attributes=False):
|
20
37
|
super().__init__(NOCODB_HOST=NOCODB_HOST,NOCODB_API_KEY=NOCODB_API_KEY,SOURCE=SOURCE)
|
21
38
|
self.NOCODB_HOST = NOCODB_HOST
|
22
39
|
self.NOCODB_API_KEY = NOCODB_API_KEY
|
23
40
|
self.logging=logging
|
24
41
|
self.required_fields = [PRODUCT_NAME_FIELD, PRODUCT_PRICE_FIELD]
|
25
42
|
self.projection = []
|
26
|
-
|
43
|
+
self.special_attributes=special_attributes
|
44
|
+
self.filter_buttons=filter_buttons
|
27
45
|
|
28
46
|
async def load_data(self,SOURCE=None):
|
29
47
|
self.SOURCE=SOURCE
|
30
48
|
await self.get_all_tables()
|
31
|
-
self.category_manager=CategoryManager(table_id=self.tables_list[NOCODB_CATEGORIES],NOCODB_HOST=self.NOCODB_HOST,NOCODB_API_KEY=self.NOCODB_API_KEY)
|
32
|
-
self.product_manager=ProductManager(table_id=self.tables_list[NOCODB_PRODUCTS],NOCODB_HOST=self.NOCODB_HOST,NOCODB_API_KEY=self.NOCODB_API_KEY)
|
49
|
+
self.category_manager=CategoryManager(table_id=self.tables_list[NOCODB_CATEGORIES],NOCODB_HOST=self.NOCODB_HOST,NOCODB_API_KEY=self.NOCODB_API_KEY,logging=True,filter_buttons=self.filter_buttons)
|
50
|
+
self.product_manager=ProductManager(table_id=self.tables_list[NOCODB_PRODUCTS],NOCODB_HOST=self.NOCODB_HOST,NOCODB_API_KEY=self.NOCODB_API_KEY,logging=True)
|
33
51
|
|
34
52
|
async def create_product(self,product: ProductModel) -> ProductModel:
|
35
53
|
products_table = self.tables_list[NOCODB_PRODUCTS]
|
36
54
|
data = dump_product_data_with_check(data=product ,data_check=self.category_manager.categories)
|
37
55
|
# product_json = dump_product_data_with_check(data=product,data_check=self.categories)
|
38
|
-
data.pop("ID")
|
56
|
+
external_id = data.pop("ID")
|
57
|
+
metadata = await self.get_table_meta(self.tables_list["Products"])
|
58
|
+
images_column=[column["id"] for column in metadata["columns"] if column["column_name"] == "Images"][0]
|
59
|
+
data["ExternalImages"]=[image['title'] for image in data['Images']]
|
60
|
+
|
61
|
+
for num,item in enumerate(data['Images']):
|
62
|
+
url_before=item['url']
|
63
|
+
image_name=item['title']
|
64
|
+
data['Images'][num]['url']=await self.save_image_to_nocodb(source_column_id=self.SOURCE,image_url=url_before,image_name=image_name,product_table_name=products_table,images_column_id=images_column)
|
65
|
+
|
39
66
|
record = await self.create_table_record(table_name=products_table, record=data)
|
40
|
-
logger.info(f"Created product {record['id']}")
|
41
|
-
|
67
|
+
# logger.info(f"Created product {record['id']}")
|
68
|
+
logger.info(f"Created product {external_id}")
|
42
69
|
|
70
|
+
async def get_all_products(self):
|
71
|
+
actual_products=[]
|
72
|
+
products_portion=[]
|
73
|
+
portion=200
|
74
|
+
# TODO Check once busy
|
75
|
+
for i in range(10):
|
76
|
+
products_portion=await self.product_manager.get_products_v2(offset=i*portion,limit=portion)
|
77
|
+
actual_products.extend(products_portion)
|
78
|
+
if len(products_portion) < 200:
|
79
|
+
break
|
80
|
+
return actual_products
|
81
|
+
|
43
82
|
async def update_products(self, external_products: List[ProductModel]):
|
44
83
|
products_table = self.tables_list[NOCODB_PRODUCTS]
|
45
|
-
|
84
|
+
await self.product_manager.update_attributes(products=external_products)
|
85
|
+
# Updates categories if there were a new ones created
|
86
|
+
external_products=await self.category_manager.map_categories(external_products=external_products)
|
87
|
+
self.product_manager.actual_products=await self.get_all_products()
|
88
|
+
# self.product_manager.actual_products = await self.product_manager.get_products_v2(offset=0,limit=200)
|
89
|
+
|
46
90
|
self.ids_mapping={product.external_id : product.id for product in self.product_manager.actual_products}
|
47
91
|
products_meta= {product.external_id : product for product in self.product_manager.actual_products}
|
48
92
|
|
49
93
|
for product in external_products:
|
50
94
|
if product.external_id in self.ids_mapping.keys():
|
51
95
|
product.id=self.ids_mapping[product.external_id]
|
52
|
-
if self.product_manager.hash_product(product)!=self.product_manager.hash_product(products_meta[product.external_id]):
|
96
|
+
if self.product_manager.hash_product(product,special_attributes=self.special_attributes)!=self.product_manager.hash_product(products_meta[product.external_id],special_attributes=self.special_attributes):
|
53
97
|
await self.update_product(product=product)
|
54
98
|
else:
|
55
99
|
await self.create_product(product=product)
|
@@ -62,7 +106,7 @@ class Gateway(NocodbClient):
|
|
62
106
|
table_name=products_table,
|
63
107
|
record_id=product.id,
|
64
108
|
updated_data=data)
|
65
|
-
logger.info(f"Updated product {product.
|
109
|
+
logger.info(f"Updated product {product.external_id}")
|
66
110
|
|
67
111
|
|
68
112
|
def find_product_id_by_name(self,name: str):
|
@@ -99,13 +143,12 @@ class Gateway(NocodbClient):
|
|
99
143
|
},
|
100
144
|
headers=headers
|
101
145
|
)
|
102
|
-
|
103
146
|
logger.info(response.text())
|
104
147
|
|
105
148
|
return response.json()
|
106
149
|
|
107
150
|
async def delete_all_products(self):
|
108
|
-
items = await self.product_manager.get_products_v2(offset=0,limit=
|
151
|
+
items = await self.product_manager.get_products_v2(offset=0,limit=200)
|
109
152
|
products_table = self.tables_list[NOCODB_PRODUCTS]
|
110
153
|
for num,item in enumerate(items):
|
111
154
|
await self.delete_table_record(products_table, item.id)
|
@@ -29,7 +29,6 @@ class ExternalProductModel(BaseModel):
|
|
29
29
|
class ProductModel(BaseModel):
|
30
30
|
id: Optional[str]
|
31
31
|
external_id: Optional[str]
|
32
|
-
|
33
32
|
category: Optional[List[str]]
|
34
33
|
category_name: Optional[List[str]]
|
35
34
|
name: str
|
@@ -43,8 +42,3 @@ class ProductModel(BaseModel):
|
|
43
42
|
updated: datetime = Field(default=datetime.now, hidden_field=True)
|
44
43
|
preview_url: List[str] = []
|
45
44
|
extra_attributes: List[ExtraAttribute] = []
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
class ProductModel(ExternalProductModel):
|
50
|
-
id: str
|
@@ -6,20 +6,24 @@ from tgshops_integrations.models.categories import CategoryModel,CategoryRespons
|
|
6
6
|
from tgshops_integrations.models.products import ProductModel
|
7
7
|
from tgshops_integrations.nocodb_connector.client import custom_key_builder, NocodbClient
|
8
8
|
from tgshops_integrations.nocodb_connector.model_mapping import CATEGORY_IMAGE_FIELD, CATEGORY_NAME_FIELD, CATEGORY_PARENT_FIELD, \
|
9
|
-
CATEGORY_PARENT_ID_FIELD, PRODUCT_NAME_FIELD, dump_category_data, get_pagination_info, parse_category_data
|
9
|
+
CATEGORY_PARENT_ID_FIELD, PRODUCT_NAME_FIELD,CATEGORY_ID_OF_CATEGORY_FIELD, dump_category_data, get_pagination_info, parse_category_data
|
10
10
|
|
11
11
|
|
12
12
|
class CategoryManager(NocodbClient):
|
13
|
-
def __init__(self,table_id=None,logging=False,NOCODB_HOST=None,NOCODB_API_KEY=None,SOURCE=None):
|
13
|
+
def __init__(self,table_id=None,logging=False,config_type=None,NOCODB_HOST=None,NOCODB_API_KEY=None,SOURCE=None,filter_buttons=[]):
|
14
14
|
super().__init__(NOCODB_HOST=NOCODB_HOST,NOCODB_API_KEY=NOCODB_API_KEY,SOURCE=SOURCE)
|
15
15
|
self.NOCODB_HOST = NOCODB_HOST
|
16
16
|
self.NOCODB_API_KEY = NOCODB_API_KEY
|
17
17
|
self.SOURCE=SOURCE
|
18
|
+
self.CONFIG_TYPE=config_type
|
18
19
|
self.categories_table=table_id
|
19
20
|
self.external_categories={}
|
20
21
|
self.logging=logging
|
22
|
+
self.filter_categories=[]
|
23
|
+
self.filter_buttons=filter_buttons
|
21
24
|
self.required_fields = [CATEGORY_NAME_FIELD]
|
22
|
-
self.projection = ["Id", CATEGORY_NAME_FIELD, CATEGORY_PARENT_ID_FIELD,
|
25
|
+
self.projection = ["Id", CATEGORY_NAME_FIELD, CATEGORY_PARENT_ID_FIELD, CATEGORY_ID_OF_CATEGORY_FIELD]
|
26
|
+
# self.projection = ["Id"]
|
23
27
|
|
24
28
|
@cached(ttl=30, key_builder=custom_key_builder)
|
25
29
|
async def get_categories(self, table_id: str) -> List[CategoryModel]:
|
@@ -81,22 +85,84 @@ class CategoryManager(NocodbClient):
|
|
81
85
|
page_info = get_pagination_info(page_info=response['pageInfo'])
|
82
86
|
return CategoryModel(categories=categories, page_info=page_info)
|
83
87
|
|
84
|
-
async def update_categories(self,external_products: List[ProductModel]):
|
88
|
+
async def update_categories(self,external_products: List[ProductModel]) -> List[ProductModel]:
|
85
89
|
# Get the names of the tables from the DB for further handling
|
86
|
-
await self.get_product_categories(table_id=self.categories_table, table_name=PRODUCT_NAME_FIELD)
|
87
|
-
|
88
|
-
|
90
|
+
self.categories=await self.get_product_categories(table_id=self.categories_table, table_name=PRODUCT_NAME_FIELD)
|
91
|
+
|
92
|
+
categories_list=[*self.categories.keys()]
|
93
|
+
categories_to_create=[]
|
94
|
+
|
89
95
|
for product in external_products:
|
90
|
-
for
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
+
# Check for new categories
|
97
|
+
# IF some category number exists on external side
|
98
|
+
if product.category:
|
99
|
+
for external_category_id,category_name in zip(product.category,product.category_name):
|
100
|
+
if category_name not in [*self.categories.keys()]:
|
101
|
+
new_category= await self.create_product_category(table_id=self.categories_table,category_name=category_name,category_id=external_category_id,table_name=PRODUCT_NAME_FIELD)
|
102
|
+
if self.logging:
|
103
|
+
logger.info(f"New Category {new_category}")
|
104
|
+
self.external_categories[new_category["Id"]]=external_category_id
|
105
|
+
self.categories=await self.get_product_categories(table_id=self.categories_table, table_name=PRODUCT_NAME_FIELD)
|
106
|
+
#Else if there is just the name, create the category with new id
|
107
|
+
else:
|
108
|
+
#Needs the buttons to be initialized, can connect items to buttons , which allows filtered acces through the menu
|
109
|
+
for num,category_name in enumerate(product.category_name):
|
110
|
+
if category_name not in categories_list:
|
111
|
+
if self.filter_buttons:
|
112
|
+
parent_id=self.categories[self.filter_buttons[num]]
|
113
|
+
else:
|
114
|
+
parent_id=self.categories[product.category_II_name[0]]
|
115
|
+
categories_to_create.append([category_name,parent_id])
|
116
|
+
categories_list.append(category_name)
|
117
|
+
|
118
|
+
if categories_to_create:
|
119
|
+
categories_to_create.sort(key=lambda x: x[0])
|
120
|
+
if [*self.categories.values()]:
|
121
|
+
new_id=max(self.categories.values())+1
|
122
|
+
else:
|
123
|
+
new_id=1
|
124
|
+
for category,parent_id in categories_to_create:
|
125
|
+
new_category= await self.create_product_category(table_id=self.categories_table,category_name=category,category_id=new_id,table_name=PRODUCT_NAME_FIELD)
|
126
|
+
if self.logging:
|
127
|
+
logger.info(f"New Category {new_category}")
|
128
|
+
# if self.filter_buttons:
|
129
|
+
# #Rewind categories
|
130
|
+
# await self.link_categories(parent_id=parent_id,child_id=new_id)
|
131
|
+
new_id+=1
|
132
|
+
|
133
|
+
async def link_categories(self,parent_id:int,child_id:int):
|
134
|
+
metadata = await self.get_table_meta(self.categories_table)
|
135
|
+
|
136
|
+
linked_column = None
|
137
|
+
for col in metadata['columns']:
|
138
|
+
if (col["title"]=="Set parent category" and col["uidt"] == "Links"):
|
139
|
+
linked_column = col
|
140
|
+
break
|
141
|
+
await self.link_table_record(
|
142
|
+
linked_column["base_id"],
|
143
|
+
linked_column["fk_model_id"],
|
144
|
+
child_id,
|
145
|
+
linked_column["id"],
|
146
|
+
parent_id)
|
147
|
+
|
148
|
+
async def unlink_categories(self,parent_id:int,child_id:int):
|
149
|
+
metadata = await self.get_table_meta(self.categories_table)
|
150
|
+
|
151
|
+
linked_column = None
|
152
|
+
for col in metadata['columns']:
|
153
|
+
if col["uidt"] == "Links":
|
154
|
+
linked_column = col
|
155
|
+
break
|
156
|
+
await self.unlink_table_record(
|
157
|
+
linked_column["base_id"],
|
158
|
+
linked_column["fk_model_id"],
|
159
|
+
parent_id,
|
160
|
+
linked_column["id"],
|
161
|
+
child_id)
|
96
162
|
|
97
163
|
async def map_categories(self,external_products: List[ProductModel]) -> List[ProductModel]:
|
98
|
-
for product in external_products:
|
164
|
+
for num,product in enumerate(external_products):
|
99
165
|
if not product.category:
|
100
|
-
|
101
|
-
|
166
|
+
if product.category_name:
|
167
|
+
external_products[num].category=[str(self.categories[category_name]) for category_name in product.category_name]
|
102
168
|
return external_products
|
@@ -2,6 +2,8 @@ from typing import List,Optional
|
|
2
2
|
|
3
3
|
import httpx
|
4
4
|
import requests
|
5
|
+
import io
|
6
|
+
|
5
7
|
from loguru import logger
|
6
8
|
|
7
9
|
from tgshops_integrations.nocodb_connector.model_mapping import ID_FIELD
|
@@ -20,7 +22,7 @@ class NocodbClient:
|
|
20
22
|
self.NOCODB_HOST = NOCODB_HOST
|
21
23
|
self.NOCODB_API_KEY = NOCODB_API_KEY
|
22
24
|
self.SOURCE=SOURCE
|
23
|
-
self.httpx_client = httpx.AsyncClient()
|
25
|
+
self.httpx_client = httpx.AsyncClient(timeout=60.0)
|
24
26
|
self.httpx_client.headers = {
|
25
27
|
"xc-token": self.NOCODB_API_KEY
|
26
28
|
}
|
@@ -76,12 +78,6 @@ class NocodbClient:
|
|
76
78
|
if response.status_code == 200:
|
77
79
|
return response.json()
|
78
80
|
raise Exception(response.text)
|
79
|
-
|
80
|
-
# class ProductModel(BaseProductModel):
|
81
|
-
# extra_option_choice_required: bool = False
|
82
|
-
# extra_option_categories: List[ExtraOptionCategoriesResponseModel] = []
|
83
|
-
# related_products: List[BaseProductModel] = None
|
84
|
-
# metadata : ProductModel
|
85
81
|
|
86
82
|
async def get_table_record(self,
|
87
83
|
table_name: str,
|
@@ -112,9 +108,11 @@ class NocodbClient:
|
|
112
108
|
return response.json().get("count", 0)
|
113
109
|
raise Exception(response.text)
|
114
110
|
|
115
|
-
async def update_table_record(self, table_name: str, record_id: str, updated_data: dict) -> bool:
|
111
|
+
async def update_table_record(self, table_name: str, record_id: str, updated_data: dict) -> bool:
|
116
112
|
url = f"{self.NOCODB_HOST}/tables/{table_name}/records"
|
117
|
-
updated_data[ID_FIELD] = record_id
|
113
|
+
updated_data[ID_FIELD] = int(record_id)
|
114
|
+
if updated_data["ID"]:
|
115
|
+
updated_data.pop("ID")
|
118
116
|
response = await self.httpx_client.patch(url, json=updated_data)
|
119
117
|
if response.status_code == 200:
|
120
118
|
return True
|
@@ -135,8 +133,12 @@ class NocodbClient:
|
|
135
133
|
response = await self.httpx_client.get(url, params=extra_params)
|
136
134
|
|
137
135
|
if response.status_code == 200:
|
138
|
-
|
139
|
-
|
136
|
+
categories={category[table_name] : category["Id"] for category in response.json()["list"]}
|
137
|
+
return categories
|
138
|
+
raise Exception(response.text)
|
139
|
+
return {}
|
140
|
+
|
141
|
+
|
140
142
|
|
141
143
|
async def create_product_category(self, table_id: str, category_name : str, table_name : str, category_id : int = 0) -> dict:
|
142
144
|
url = f"{self.NOCODB_HOST}/tables/{table_id}/records"
|
@@ -145,10 +147,7 @@ class NocodbClient:
|
|
145
147
|
|
146
148
|
response = await self.httpx_client.post(url, json=record)
|
147
149
|
if response.status_code == 200:
|
148
|
-
|
149
|
-
# if not record["id"]:
|
150
|
-
# record["id"] = response.json().get("Id")
|
151
|
-
await self.get_product_categories(table_id=table_id, table_name=table_name)
|
150
|
+
self.categories = await self.get_product_categories(table_id=table_id, table_name=table_name)
|
152
151
|
return record
|
153
152
|
raise Exception(response.text)
|
154
153
|
|
@@ -171,16 +170,107 @@ class NocodbClient:
|
|
171
170
|
return (await self.httpx_client.get(
|
172
171
|
f"{self.NOCODB_HOST.replace('/api/v2', '/api/v1')}/db/meta/projects/")).json().get(
|
173
172
|
'list', [])
|
173
|
+
|
174
|
+
async def get_table_meta(self, table_name: str):
|
175
|
+
return (await self.httpx_client.get(
|
176
|
+
f"{self.NOCODB_HOST.replace('/api/v2', '/api/v1')}/db/meta/tables/{table_name}")).json()
|
177
|
+
|
178
|
+
|
179
|
+
async def create_table_column(self, table_name: str, name: str):
|
180
|
+
return (await self.httpx_client.post(
|
181
|
+
f"{self.NOCODB_HOST.replace('/api/v2', '/api/v1')}/db/meta/tables/{table_name}/columns",
|
182
|
+
json={
|
183
|
+
"column_name": name,
|
184
|
+
"dt": "character varying",
|
185
|
+
"dtx": "specificType",
|
186
|
+
"ct": "varchar(45)",
|
187
|
+
"clen": 45,
|
188
|
+
"dtxp": "45",
|
189
|
+
"dtxs": "",
|
190
|
+
"altered": 1,
|
191
|
+
"uidt": "SingleLineText",
|
192
|
+
"uip": "",
|
193
|
+
"uicn": "",
|
194
|
+
"title": name
|
195
|
+
})).json()
|
174
196
|
|
175
|
-
def
|
197
|
+
async def link_table_record(
|
198
|
+
self,
|
199
|
+
base_id: str,
|
200
|
+
fk_model_id: str,
|
201
|
+
record_id: str,
|
202
|
+
source_column_id: str,
|
203
|
+
linked_record_id: str) -> dict:
|
176
204
|
"""
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
205
|
+
base_id
|
206
|
+
fk_model_id - ID of linked column
|
207
|
+
record_id - ID of source record to be linked
|
208
|
+
source_column_id -ID of source column
|
209
|
+
linked_record_id - ID of linked record
|
210
|
+
|
211
|
+
POST /api/v1/db/data/noco/pwb8m0yee7nvw6m/mtk2pg9eiix11qs/242/mm/ct5sskewp6sg54q/91
|
212
|
+
/fk_model- smr8uvm11kurzprp
|
184
213
|
"""
|
185
|
-
url = f"{self.NOCODB_HOST.replace('/api/v2', '/api/v1')}/db/data/noco/{
|
186
|
-
|
214
|
+
url = f"{self.NOCODB_HOST.replace('/api/v2', '/api/v1')}/db/data/noco/{base_id}/{fk_model_id}/{record_id}/mm/{source_column_id}/{linked_record_id}"
|
215
|
+
response = await self.httpx_client.post(url,headers=self.httpx_client.headers)
|
216
|
+
if response.status_code == 200:
|
217
|
+
return response.json()
|
218
|
+
raise Exception(response.text)
|
219
|
+
|
220
|
+
async def unlink_table_record(
|
221
|
+
self,
|
222
|
+
base_id: str,
|
223
|
+
fk_model_id: str,
|
224
|
+
record_id: str,
|
225
|
+
source_column_id: str,
|
226
|
+
linked_record_id: str) -> dict:
|
227
|
+
"""
|
228
|
+
base_id
|
229
|
+
fk_model_id - ID of linked column
|
230
|
+
record_id - ID of source record to be linked
|
231
|
+
source_column_id -ID of source column
|
232
|
+
linked_record_id - ID of linked record
|
233
|
+
|
234
|
+
POST /api/v1/db/data/noco/pwb8m0yee7nvw6m/mtk2pg9eiix11qs/242/mm/ct5sskewp6sg54q/91
|
235
|
+
"""
|
236
|
+
path = f"{self.NOCODB_HOST.replace('/api/v2', '/api/v1')}/db/data/noco/{base_id}/{fk_model_id}/{record_id}/mm/{source_column_id}/{linked_record_id}"
|
237
|
+
response = await self.httpx_client.delete(path)
|
238
|
+
if response.status_code == 200:
|
239
|
+
return response.json()
|
240
|
+
raise Exception(response.text)
|
241
|
+
|
242
|
+
async def save_image_to_nocodb(
|
243
|
+
self,
|
244
|
+
image_url: str,
|
245
|
+
image_name: str,
|
246
|
+
source_column_id: str,
|
247
|
+
product_table_name: str,
|
248
|
+
images_column_id: str) -> dict:
|
249
|
+
"""
|
250
|
+
source
|
251
|
+
fk_model_id - ID of linked column
|
252
|
+
record_id - ID of source record to be linked
|
253
|
+
source_column_id -ID of source column
|
254
|
+
linked_record_id - ID of linked record
|
255
|
+
"""
|
256
|
+
|
257
|
+
response = requests.get(image_url)
|
258
|
+
if response.status_code == 200:
|
259
|
+
file = io.BytesIO(response.content)
|
260
|
+
else:
|
261
|
+
raise Exception(f"Failed to fetch the image. Status code: {response.status_code}")
|
262
|
+
|
263
|
+
file_size = file.getbuffer().nbytes
|
264
|
+
|
265
|
+
if file_size:
|
266
|
+
|
267
|
+
files = {'file': (image_name, file, 'image/jpeg')}
|
268
|
+
|
269
|
+
url = f"{self.NOCODB_HOST.replace('/api/v2', '/api/v1')}/db/storage/upload?path=noco/{source_column_id}/{product_table_name}/{images_column_id}"
|
270
|
+
timeout = httpx.Timeout(100.0)
|
271
|
+
response = await self.httpx_client.post(url,files=files,headers=self.httpx_client.headers,timeout=timeout)
|
272
|
+
if response.status_code == 200:
|
273
|
+
return response.json()[0]['url']
|
274
|
+
raise Exception(response.text)
|
275
|
+
else:
|
276
|
+
return ""
|
@@ -8,6 +8,45 @@ from tgshops_integrations.models.products import ExtraAttribute, ProductModel
|
|
8
8
|
from tgshops_integrations.models.categories import CategoryResponseModel,PaginationResponseModel
|
9
9
|
from tgshops_integrations.models.products import ProductModel
|
10
10
|
|
11
|
+
import importlib.util
|
12
|
+
from pathlib import Path
|
13
|
+
|
14
|
+
# Step 1: Define the path to config.py (one level above)
|
15
|
+
config_path = Path(__file__).resolve().parent.parent / '../config.py'
|
16
|
+
|
17
|
+
# Step 2: Load config.py dynamically using importlib
|
18
|
+
spec = importlib.util.spec_from_file_location("config", config_path)
|
19
|
+
config = importlib.util.module_from_spec(spec)
|
20
|
+
spec.loader.exec_module(config)
|
21
|
+
|
22
|
+
# Step 3: Load all required constants from config.py
|
23
|
+
CATEGORY_IMAGE_FIELD = config.CATEGORY_IMAGE_FIELD
|
24
|
+
ID_FIELD = config.ID_FIELD
|
25
|
+
CATEGORY_NAME_FIELD = config.CATEGORY_NAME_FIELD
|
26
|
+
CATEGORY_PARENT_ID_FIELD = config.CATEGORY_PARENT_ID_FIELD
|
27
|
+
CATEGORY_PARENT_FIELD = config.CATEGORY_PARENT_FIELD
|
28
|
+
|
29
|
+
PRODUCT_NAME_FIELD = config.PRODUCT_NAME_FIELD
|
30
|
+
PRODUCT_DESCRIPTION_FIELD = config.PRODUCT_DESCRIPTION_FIELD
|
31
|
+
PRODUCT_PRICE_FIELD = config.PRODUCT_PRICE_FIELD
|
32
|
+
PRODUCT_CURRENCY_FIELD = config.PRODUCT_CURRENCY_FIELD
|
33
|
+
PRODUCT_STOCK_FIELD = config.PRODUCT_STOCK_FIELD
|
34
|
+
PRODUCT_CATEGORY_NAME_FIELD = config.PRODUCT_CATEGORY_NAME_FIELD
|
35
|
+
PRODUCT_CATEGORY_ID_FIELD = config.PRODUCT_CATEGORY_ID_FIELD
|
36
|
+
PRODUCT_IMAGE_FIELD = config.PRODUCT_IMAGE_FIELD
|
37
|
+
PRODUCT_DISCOUNT_PRICE_FIELD = config.PRODUCT_DISCOUNT_PRICE_FIELD
|
38
|
+
PRODUCT_CATEGORY_ID_LOOKUP_FIELD = config.PRODUCT_CATEGORY_ID_LOOKUP_FIELD
|
39
|
+
PRODUCT_REQUIRED_OPTIONS_FIELD = config.PRODUCT_REQUIRED_OPTIONS_FIELD
|
40
|
+
PRODUCT_CATEGORIES_EXTRA_OPTIONS_FIELD = config.PRODUCT_CATEGORIES_EXTRA_OPTIONS_FIELD
|
41
|
+
PRODUCT_CATEGORIES_EXTRA_OPTION_NAMES_FIELD = config.PRODUCT_CATEGORIES_EXTRA_OPTION_NAMES_FIELD
|
42
|
+
PRODUCT_EXTRA_CHOICE_REQUIRED_FIELD = config.PRODUCT_EXTRA_CHOICE_REQUIRED_FIELD
|
43
|
+
PRODUCT_ID_FIELD = config.PRODUCT_ID_FIELD
|
44
|
+
PRODUCT_EXTERNAL_ID = config.PRODUCT_EXTERNAL_ID
|
45
|
+
PRODUCT_CHECKOUT_MODE = config.PRODUCT_CHECKOUT_MODE
|
46
|
+
NEW_ID_FIELD = config.NEW_ID_FIELD
|
47
|
+
|
48
|
+
NOCODB_CHECKOUT_MODES = config.NOCODB_CHECKOUT_MODES
|
49
|
+
|
11
50
|
|
12
51
|
def get_pagination_info(page_info: dict) -> PaginationResponseModel:
|
13
52
|
page_info = PaginationResponseModel(total_rows=page_info['totalRows'],
|
@@ -17,15 +56,6 @@ def get_pagination_info(page_info: dict) -> PaginationResponseModel:
|
|
17
56
|
is_last_page=page_info['isLastPage'])
|
18
57
|
return page_info
|
19
58
|
|
20
|
-
|
21
|
-
ID_FIELD = "Id"
|
22
|
-
NEW_ID_FIELD = "id"
|
23
|
-
CATEGORY_IMAGE_FIELD = "Изображение"
|
24
|
-
CATEGORY_NAME_FIELD = "Название"
|
25
|
-
CATEGORY_PARENT_FIELD = "Назначить родительскую категорию"
|
26
|
-
CATEGORY_PARENT_ID_FIELD = "ID родительской категории"
|
27
|
-
|
28
|
-
|
29
59
|
def parse_category_data(data: dict) -> CategoryResponseModel:
|
30
60
|
preview_url = ""
|
31
61
|
if data.get(CATEGORY_IMAGE_FIELD):
|
@@ -47,37 +77,8 @@ def dump_category_data(data: CategoryModel) -> dict:
|
|
47
77
|
}
|
48
78
|
|
49
79
|
|
50
|
-
# PRODUCT_IMAGE_FIELD = "Изображения"
|
51
|
-
# PRODUCT_NAME_FIELD = "Название"
|
52
|
-
# PRODUCT_STOCK_FIELD = "Доступное количество"
|
53
|
-
# PRODUCT_PRICE_FIELD = "Стоимость"
|
54
|
-
# PRODUCT_CURRENCY_FIELD = "Валюта"
|
55
|
-
# PRODUCT_DESCRIPTION_FIELD = "Описание"
|
56
|
-
# PRODUCT_CATEGORY_NAME_FIELD = "Название категорий"
|
57
|
-
# PRODUCT_DISCOUNT_PRICE_FIELD = "Стоимость со скидкой"
|
58
|
-
|
59
|
-
PRODUCT_IMAGE_FIELD="Images"
|
60
|
-
PRODUCT_NAME_FIELD="Name"
|
61
|
-
PRODUCT_DESCRIPTION_FIELD = "Description"
|
62
|
-
PRODUCT_ID_FIELD="ID"
|
63
|
-
PRODUCT_EXTERNAL_ID="ExternalId"
|
64
|
-
PRODUCT_PRICE_FIELD="Price"
|
65
|
-
PRODUCT_CURRENCY_FIELD = "Currency"
|
66
|
-
PRODUCT_STOCK_FIELD = "Number of pieces"
|
67
|
-
PRODUCT_CATEGORY_ID_FIELD = "Category"
|
68
|
-
PRODUCT_DISCOUNT_PRICE_FIELD = "Discounted price"
|
69
|
-
PRODUCT_CATEGORY_NAME_FIELD = "Name of categories"
|
70
|
-
# PRODUCT_CATEGORY_ID_LOOKUP_FIELD = "ID Категории"
|
71
|
-
PRODUCT_CATEGORY_ID_LOOKUP_FIELD = "ID of category"
|
72
|
-
PRODUCT_REQUIRED_OPTIONS_FIELD = "Выбор обязательных опций"
|
73
|
-
PRODUCT_CATEGORIES_EXTRA_OPTIONS_FIELD = "Выбор категории доп опций"
|
74
|
-
PRODUCT_CATEGORIES_EXTRA_OPTION_NAMES_FIELD = "Названия категорий доп опций"
|
75
|
-
PRODUCT_EXTRA_CHOICE_REQUIRED_FIELD = "Обязательный выбор?"
|
76
|
-
|
77
|
-
|
78
80
|
def dump_product_data(data: ProductModel) -> dict:
|
79
|
-
|
80
|
-
print("Hoi")
|
81
|
+
|
81
82
|
preview_url = ([{'url': image_url,
|
82
83
|
'title': f'{secrets.token_hex(6)}.jpeg',
|
83
84
|
'mimetype': 'image/jpeg'}
|
@@ -91,9 +92,7 @@ def dump_product_data(data: ProductModel) -> dict:
|
|
91
92
|
PRODUCT_PRICE_FIELD: data.price,
|
92
93
|
PRODUCT_CURRENCY_FIELD: data.currency,
|
93
94
|
PRODUCT_STOCK_FIELD: data.stock_qty,
|
94
|
-
#TODO Add for several categories
|
95
95
|
PRODUCT_CATEGORY_NAME_FIELD:[data.category_name] if data.category_name else None,
|
96
|
-
# PRODUCT_CATEGORY_ID_FIELD: [{"id": int(data.category[0])}] if data.category else None,
|
97
96
|
PRODUCT_CATEGORY_ID_FIELD: [{'Id': data.category}] if data.category else None,
|
98
97
|
PRODUCT_IMAGE_FIELD: preview_url,
|
99
98
|
PRODUCT_DISCOUNT_PRICE_FIELD: data.final_price
|
@@ -107,6 +106,9 @@ def dump_product_data_with_check(data: ProductModel, data_check: dict) -> dict:
|
|
107
106
|
for image_url in data.preview_url]
|
108
107
|
if data.preview_url
|
109
108
|
else [])
|
109
|
+
|
110
|
+
extra_data = {item.name : item.description for item in data.extra_attributes}
|
111
|
+
|
110
112
|
product_data = {
|
111
113
|
PRODUCT_ID_FIELD: data.id,
|
112
114
|
PRODUCT_EXTERNAL_ID: data.external_id,
|
@@ -115,13 +117,15 @@ def dump_product_data_with_check(data: ProductModel, data_check: dict) -> dict:
|
|
115
117
|
PRODUCT_PRICE_FIELD: data.price,
|
116
118
|
PRODUCT_CURRENCY_FIELD: data.currency,
|
117
119
|
PRODUCT_STOCK_FIELD: data.stock_qty,
|
118
|
-
#TODO Add for several categories
|
119
120
|
PRODUCT_CATEGORY_NAME_FIELD:[data.category_name] if data.category_name else None,
|
120
|
-
|
121
|
-
PRODUCT_CATEGORY_ID_FIELD: [{'Id': data_check[data.category_name[0]]}] if data.category else None,
|
121
|
+
PRODUCT_CATEGORY_ID_FIELD: [{'Id': data_check[item]} for item in data.category_name] if data.category else None,
|
122
122
|
PRODUCT_IMAGE_FIELD: preview_url,
|
123
|
-
|
123
|
+
PRODUCT_CHECKOUT_MODE: NOCODB_CHECKOUT_MODES,
|
124
|
+
PRODUCT_DISCOUNT_PRICE_FIELD: data.final_price,
|
124
125
|
}
|
126
|
+
|
127
|
+
if len(extra_data)>0:
|
128
|
+
product_data.update(extra_data)
|
125
129
|
return product_data
|
126
130
|
|
127
131
|
|
@@ -26,10 +26,16 @@ class ProductManager(NocodbClient):
|
|
26
26
|
self.external_categories={}
|
27
27
|
self.products_table=table_id
|
28
28
|
self.actual_products=[]
|
29
|
+
self.columns=[]
|
29
30
|
|
30
|
-
def hash_product(self,product):
|
31
|
+
def hash_product(self,product,special_attributes=False):
|
32
|
+
if special_attributes:
|
33
|
+
hash_string = ''.join(attr.description for attr in product.extra_attributes if attr.name.endswith('*'))
|
34
|
+
# hash_string = f"{product.external_id}{product.price}{product.category_name.sort()}{product.name}{product.description}"
|
35
|
+
else:
|
31
36
|
# Concatenate relevant attributes into a single string
|
32
|
-
|
37
|
+
hash_string = f"{product.external_id}{product.price}{product.category_name.sort()}{product.name}{product.description}"
|
38
|
+
# hash_string = f"{product.external_id}{product.price}{product.category_name}{product.name}{product.description}{product.preview_url}"
|
33
39
|
# Hash the concatenated string
|
34
40
|
hash_object = hashlib.sha256(hash_string.encode())
|
35
41
|
hex_dig = hash_object.hexdigest()
|
@@ -144,7 +150,18 @@ class ProductManager(NocodbClient):
|
|
144
150
|
required_fields=self.required_fields,
|
145
151
|
projection=self.projection,
|
146
152
|
extra_where=f"({ID_FIELD},in,{product_ids_str})")
|
147
|
-
return [parse_product_data(record) for record in records]
|
153
|
+
return [parse_product_data(record) for record in records]
|
154
|
+
|
155
|
+
@cached(ttl=60, key_builder=custom_key_builder)
|
156
|
+
async def update_attributes(self,products:List[ProductModel]):
|
157
|
+
for item in products:
|
158
|
+
attributes=await self.get_table_meta(table_name=self.products_table)
|
159
|
+
self.columns =[item['title'].lower() for item in attributes.get('columns', [])]
|
160
|
+
for attribute in item.extra_attributes:
|
161
|
+
if attribute.name.rstrip().lower() not in self.columns:
|
162
|
+
response =await self.create_table_column(table_name=self.products_table,name=attribute.name.lower())
|
163
|
+
logger.info(f"Created attribute: {attribute.name.lower()}")
|
164
|
+
|
148
165
|
|
149
166
|
def find_product_id_by_name(self,name: str):
|
150
167
|
for product in self.actual_products.products:
|
@@ -0,0 +1,81 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: tgshops-integrations
|
3
|
+
Version: 1.0
|
4
|
+
Summary: Library is intended to provide the integration of the external service or CRM system with the TelegramShops/It allows to configure the relationship between NocoDB list of the products used further to display in the shop/As a resultss the products can be synchronized and updated uppon the request.
|
5
|
+
Home-page: https://git.the-devs.com/virtual-shops/shop-system/shop-backend-integrations/integration-library/integration-library
|
6
|
+
Author: Dimi Latoff
|
7
|
+
Author-email: drpozd@gmail.com
|
8
|
+
License: MIT
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
11
|
+
Classifier: Operating System :: OS Independent
|
12
|
+
Requires-Python: >=3.10
|
13
|
+
Description-Content-Type: text/markdown
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
```python
|
21
|
+
|
22
|
+
from typing import List
|
23
|
+
import asyncio
|
24
|
+
|
25
|
+
from config import NocoDBConfig
|
26
|
+
from tgshops_integrations.middlewares.gateway import Gateway
|
27
|
+
from services.bitrix.client import BitrixClient
|
28
|
+
|
29
|
+
# Your credentials are here and source of the target table
|
30
|
+
NOCODB_HOST = NocoDBConfig.HOST
|
31
|
+
NOCODB_API_KEY = NocoDBConfig.API_KEY
|
32
|
+
SOURCE=NocoDBConfig.source_table
|
33
|
+
|
34
|
+
async def main():
|
35
|
+
|
36
|
+
# Here is your client to upload data from your service
|
37
|
+
bitrixService=BitrixClient()
|
38
|
+
|
39
|
+
# Products have to be in a according to the ProductModel
|
40
|
+
# class ProductModel(BaseModel):
|
41
|
+
# id: Optional[str]
|
42
|
+
# external_id: Optional[str]
|
43
|
+
# category: Optional[List[str]]
|
44
|
+
# category_name: Optional[List[str]]
|
45
|
+
# name: str
|
46
|
+
# description: Optional[str]
|
47
|
+
# price: Optional[float]
|
48
|
+
# final_price: Optional[float]
|
49
|
+
# currency: Optional[str]
|
50
|
+
# stock_qty: int
|
51
|
+
# orders_qty: int = Field(0, hidden_field=True)
|
52
|
+
# created: datetime = Field(default=datetime.now, hidden_field=True)
|
53
|
+
# updated: datetime = Field(default=datetime.now, hidden_field=True)
|
54
|
+
# preview_url: List[str] = []
|
55
|
+
# extra_attributes: List[ExtraAttribute] = []
|
56
|
+
|
57
|
+
bitrix_product_list=await bitrixService.get_crm_product_list()
|
58
|
+
|
59
|
+
NocoGateway = Gateway(NOCODB_HOST=NOCODB_HOST,NOCODB_API_KEY=NOCODB_API_KEY)
|
60
|
+
|
61
|
+
# Example how to clean your table
|
62
|
+
# await NocoGateway.load_data(SOURCE=SOURCE)
|
63
|
+
# await NocoGateway.delete_all_products()
|
64
|
+
|
65
|
+
|
66
|
+
# In order to obtain data from the table need to call load data, to obtain it for further comparation
|
67
|
+
await NocoGateway.load_data(SOURCE=SOURCE)
|
68
|
+
# Initializes any missing categories
|
69
|
+
await NocoGateway.category_manager.update_categories(external_products=bitrix_product_list)
|
70
|
+
|
71
|
+
# Creates or updates the products
|
72
|
+
await NocoGateway.update_products(external_products=bitrix_product_list)
|
73
|
+
|
74
|
+
|
75
|
+
asyncio.run(main())
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
|
81
|
+
```
|
@@ -0,0 +1,16 @@
|
|
1
|
+
tgshops_integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
tgshops_integrations/middlewares/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
tgshops_integrations/middlewares/gateway.py,sha256=GxXs-lVtrdWq3WTh_j0Hi1ffGsIOCCKJfOu3eyiRI2g,7441
|
4
|
+
tgshops_integrations/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
+
tgshops_integrations/models/categories.py,sha256=EG6C8g5dOfXB2MH-vtqH13aqB7_VyOobY2FHpDb-fsY,977
|
6
|
+
tgshops_integrations/models/products.py,sha256=i0vP_eJMVCB-W25BCoodIB0AhsMTqYiDO48N-B6Ueo0,1379
|
7
|
+
tgshops_integrations/nocodb_connector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
+
tgshops_integrations/nocodb_connector/categories.py,sha256=YfQQ8UAkOonNZKO-oTH36vODu0eE-ElG-Me2bH5LdEM,9072
|
9
|
+
tgshops_integrations/nocodb_connector/client.py,sha256=MD08P25jYWg0ZoLGk9cZ7cH8WJ6vtMn9oXW1N0LuyZ0,11504
|
10
|
+
tgshops_integrations/nocodb_connector/model_mapping.py,sha256=xLmfHGZBM4aEf2ZxVuitx5ckXQFSF9VpYms6qy2ao8Y,7653
|
11
|
+
tgshops_integrations/nocodb_connector/products.py,sha256=23uXnmznJN6fZw3tZ3a7dJg06LkI2QaNfVhSKochPn4,8677
|
12
|
+
tgshops_integrations/nocodb_connector/tables.py,sha256=ha_QXZXd93mht0fR5E1nM0wUpz1ePon-pIdO2HI67l8,356
|
13
|
+
tgshops_integrations-1.0.dist-info/METADATA,sha256=7D9nxdGywF15S1rf_XBFr3DKX3skyvq_P9O-EQ1oeyo,2774
|
14
|
+
tgshops_integrations-1.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
15
|
+
tgshops_integrations-1.0.dist-info/top_level.txt,sha256=HFNtxqDpzmlF4ZLnMiwhbU7pOa_YozxU2zBl0bnUmcY,21
|
16
|
+
tgshops_integrations-1.0.dist-info/RECORD,,
|
@@ -1,75 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.1
|
2
|
-
Name: tgshops-integrations
|
3
|
-
Version: 0.3
|
4
|
-
Summary: Library is intended to provide the integration of the external service or CRM system with the TelegramShops/It allows to configure the relationship between NocoDB list of the products used further to display in the shop/As a resultss the products can be synchronized and updated uppon the request.
|
5
|
-
Home-page: https://git.the-devs.com/virtual-shops/shop-system/shop-backend-integrations/integration-library/integration-library
|
6
|
-
Author: Dimi Latoff
|
7
|
-
Author-email: drpozd@gmail.com
|
8
|
-
License: MIT
|
9
|
-
Classifier: Programming Language :: Python :: 3
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
11
|
-
Classifier: Operating System :: OS Independent
|
12
|
-
Requires-Python: >=3.10
|
13
|
-
Description-Content-Type: text/markdown
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
```python
|
21
|
-
|
22
|
-
import tgshops_integrations
|
23
|
-
import ExternalProductModel, ExternalCategoryModel from tgshops_integrations.models
|
24
|
-
|
25
|
-
# Load external data
|
26
|
-
external_categories = [ExternalCategoryModel(
|
27
|
-
name="Drinks",
|
28
|
-
image_url="https://example.com/image.jpg",
|
29
|
-
external_id="0"
|
30
|
-
), CategoryModel(
|
31
|
-
name="Coffee",
|
32
|
-
image_url="https://example.com/image.jpg",
|
33
|
-
external_id="1"
|
34
|
-
)]
|
35
|
-
external_products = [ExternalProductModel(
|
36
|
-
name="Coffee",
|
37
|
-
description="",
|
38
|
-
price=10.0,
|
39
|
-
currency="USD",
|
40
|
-
image_url="https://example.com/image.jpg",
|
41
|
-
category=List["0", "1"],
|
42
|
-
external_id="0"
|
43
|
-
)]
|
44
|
-
|
45
|
-
|
46
|
-
# Initialise
|
47
|
-
product_service = tgshops_integrations.ProductService(token="your_token_here")
|
48
|
-
|
49
|
-
await product_service.update_categories(
|
50
|
-
external_categories=external_categories
|
51
|
-
)
|
52
|
-
|
53
|
-
await product_service.update_products(
|
54
|
-
external_products=external_products
|
55
|
-
)
|
56
|
-
|
57
|
-
# Here is the the custom integration of your service, which has to return products according to the ExternalProductModel
|
58
|
-
bitrixService=BitrixClient()
|
59
|
-
bitrix_product_list=await bitrixService.get_crm_product_list()
|
60
|
-
|
61
|
-
# One gateway can work with several table / DBs
|
62
|
-
NocoGateway = Gateway(NOCODB_HOST=NOCODB_HOST,NOCODB_API_KEY=NOCODB_API_KEY)
|
63
|
-
|
64
|
-
# await NocoGateway.load_data()
|
65
|
-
# await NocoGateway.delete_all_products()
|
66
|
-
|
67
|
-
# Load data provides the access to the certain table and allows to obtain the data about the products or catergories
|
68
|
-
await NocoGateway.load_data(SOURCE=SOURCE)
|
69
|
-
await NocoGateway.category_manager.update_categories(external_products=bitrix_product_list)
|
70
|
-
await NocoGateway.update_products(external_products=bitrix_product_list)
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
```
|
@@ -1,16 +0,0 @@
|
|
1
|
-
tgshops_integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
tgshops_integrations/middlewares/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
tgshops_integrations/middlewares/gateway.py,sha256=tZMJZJBRVmmIrlKmspm5KMbmNDCv4RgXFjydcayx56k,5042
|
4
|
-
tgshops_integrations/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
tgshops_integrations/models/categories.py,sha256=EG6C8g5dOfXB2MH-vtqH13aqB7_VyOobY2FHpDb-fsY,977
|
6
|
-
tgshops_integrations/models/products.py,sha256=rRVwEo1NP5pvarzvbwD9NbkTqtqxRDfIHCpNfx54Aus,1440
|
7
|
-
tgshops_integrations/nocodb_connector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
-
tgshops_integrations/nocodb_connector/categories.py,sha256=GHB7vEns9viK3NbgR4fY42UzMCUVnr4R1HKP1NORaHg,5900
|
9
|
-
tgshops_integrations/nocodb_connector/client.py,sha256=qq2UQDbRCOqcH0ng49l1xOihY2-Zc3tMm2yqQUMk_pk,8307
|
10
|
-
tgshops_integrations/nocodb_connector/model_mapping.py,sha256=9199eOz4ISqduDv5BkxWgPXt-QT6fd5dtCGVMZoOAN4,7471
|
11
|
-
tgshops_integrations/nocodb_connector/products.py,sha256=TZGBfEfH11NxOeeqZQoEiMi1WEuSMpmTJ7JDu8KtgNw,7548
|
12
|
-
tgshops_integrations/nocodb_connector/tables.py,sha256=ha_QXZXd93mht0fR5E1nM0wUpz1ePon-pIdO2HI67l8,356
|
13
|
-
tgshops_integrations-0.3.dist-info/METADATA,sha256=A5rJekXxjrEVJ6aXaR42G-fH4pgOwUntpyn4YYqH5NY,2391
|
14
|
-
tgshops_integrations-0.3.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
15
|
-
tgshops_integrations-0.3.dist-info/top_level.txt,sha256=HFNtxqDpzmlF4ZLnMiwhbU7pOa_YozxU2zBl0bnUmcY,21
|
16
|
-
tgshops_integrations-0.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|