725 lines
25 KiB
Python
725 lines
25 KiB
Python
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
# this repository contains the full copyright notices and license terms.
|
|
import re
|
|
from decimal import Decimal
|
|
from urllib.parse import urljoin
|
|
|
|
import shopify
|
|
from sql.conditionals import NullIf
|
|
from sql.operators import Equal
|
|
|
|
from trytond.i18n import gettext
|
|
from trytond.model import Exclude, ModelSQL, ModelView, fields
|
|
from trytond.modules.product.exceptions import TemplateValidationError
|
|
from trytond.pool import Pool, PoolMeta
|
|
from trytond.pyson import Bool, Eval, If
|
|
from trytond.tools import grouped_slice, slugify
|
|
from trytond.transaction import Transaction
|
|
|
|
from . import graphql
|
|
from .common import IdentifiersMixin, IdentifiersUpdateMixin, id2gid
|
|
|
|
QUERY_COLLECTION = '''\
|
|
query GetCollection($id: ID!) {
|
|
collection(id: $id) %(fields)s
|
|
}'''
|
|
|
|
QUERY_PRODUCT = '''\
|
|
query GetProduct($id: ID!) {
|
|
product(id: $id) %(fields)s
|
|
}'''
|
|
|
|
QUERY_VARIANT = '''\
|
|
query GetProductVariant($id: ID!) {
|
|
productVariant(id: $id) %(fields)s
|
|
}'''
|
|
|
|
|
|
class Category(IdentifiersMixin, metaclass=PoolMeta):
|
|
__name__ = 'product.category'
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls._shopify_fields.add('name')
|
|
|
|
def get_shopify(self, shop):
|
|
shopify_id = self.get_shopify_identifier(shop)
|
|
if shopify_id:
|
|
shopify_id = id2gid('Collection', shopify_id)
|
|
collection = shopify.GraphQL().execute(
|
|
QUERY_COLLECTION % {
|
|
'fields': graphql.selection({
|
|
'id': None,
|
|
}),
|
|
}, {'id': shopify_id})['data']['collection'] or {}
|
|
else:
|
|
collection = {}
|
|
collection['title'] = self.name[:255]
|
|
collection['metafields'] = metafields = []
|
|
managed_metafields = shop.managed_metafields()
|
|
for key, value in self.get_shopify_metafields(shop).items():
|
|
if key not in managed_metafields:
|
|
continue
|
|
namespace, key = key.split('.', 1)
|
|
metafields.append({
|
|
'namespace': namespace,
|
|
'key': key,
|
|
'value': value,
|
|
})
|
|
return collection
|
|
|
|
def get_shopify_metafields(self, shop):
|
|
return {}
|
|
|
|
|
|
class TemplateCategory(IdentifiersUpdateMixin, metaclass=PoolMeta):
|
|
__name__ = 'product.template-product.category'
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls._shopify_fields.update(['template', 'category'])
|
|
|
|
@classmethod
|
|
def get_shopify_identifier_to_update(cls, records):
|
|
return sum((list(r.template.shopify_identifiers) for r in records), [])
|
|
|
|
|
|
class Template(IdentifiersMixin, metaclass=PoolMeta):
|
|
__name__ = 'product.template'
|
|
|
|
shopify_uom = fields.Many2One(
|
|
'product.uom', "Shopify UoM",
|
|
states={
|
|
'readonly': Bool(Eval('shopify_identifiers', [-1])),
|
|
'invisible': ~Eval('salable', False),
|
|
},
|
|
help="The Unit of Measure of the product on Shopify.")
|
|
shopify_handle = fields.Char(
|
|
"Shopify Handle",
|
|
states={
|
|
'invisible': ~Eval('salable', False),
|
|
},
|
|
help="The string that's used to identify the product in URLs.\n"
|
|
"Leave empty to let Shopify generate one.")
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
t = cls.__table__()
|
|
cls._sql_constraints += [
|
|
('shopify_handle_unique',
|
|
Exclude(t,
|
|
(NullIf(t.shopify_handle, ''), Equal)),
|
|
'web_shop_shopify.msg_template_shopify_handle_unique'),
|
|
]
|
|
cls._shopify_fields.update([
|
|
'name', 'web_shop_description', 'attribute_set',
|
|
'customs_category', 'tariff_codes_category',
|
|
'country_of_origin', 'weight', 'weight_uom'])
|
|
categories = cls._shopify_uom_categories()
|
|
cls.shopify_uom.domain = [
|
|
('category', 'in', [Eval(c, -1) for c in categories]),
|
|
('digits', '=', 0),
|
|
]
|
|
|
|
@classmethod
|
|
def _shopify_uom_categories(cls):
|
|
return ['default_uom_category']
|
|
|
|
def get_shopify_uom(self):
|
|
return self.sale_uom
|
|
|
|
@classmethod
|
|
def get_shopify_identifier_to_update(cls, templates):
|
|
pool = Pool()
|
|
Product = pool.get('product.product')
|
|
products = [p for t in templates for p in t.products]
|
|
return (super().get_shopify_identifier_to_update(templates)
|
|
+ Product.get_shopify_identifier_to_update(products))
|
|
|
|
def get_shopify(self, shop, categories):
|
|
shopify_id = self.get_shopify_identifier(shop)
|
|
product = {}
|
|
if shopify_id:
|
|
shopify_id = id2gid('Product', shopify_id)
|
|
product = shopify.GraphQL().execute(
|
|
QUERY_PRODUCT % {
|
|
'fields': graphql.selection({
|
|
'id': None,
|
|
'status': None,
|
|
}),
|
|
}, {'id': shopify_id})['data']['product'] or {}
|
|
if product.get('status') == 'ARCHIVED':
|
|
product['status'] = 'ACTIVE'
|
|
product['title'] = self.name
|
|
if self.web_shop_description:
|
|
product['descriptionHtml'] = self.web_shop_description
|
|
if self.shopify_handle:
|
|
product['handle'] = self.shopify_handle
|
|
|
|
product['productOptions'] = options = []
|
|
for i, attribute in enumerate(self.shopify_attributes, 1):
|
|
values = set()
|
|
for p in self.products:
|
|
if p.attributes and attribute.name in p.attributes:
|
|
values.add(p.attributes.get(attribute.name))
|
|
values = [
|
|
{'name': attribute.format(value)}
|
|
for value in sorted(values)]
|
|
options.append({
|
|
'name': attribute.string,
|
|
'position': i,
|
|
'values': values,
|
|
})
|
|
|
|
product['collections'] = collections = []
|
|
for category in categories:
|
|
if collection_id := category.get_shopify_identifier(shop):
|
|
collections.append(id2gid(
|
|
'Collection', collection_id))
|
|
|
|
product['metafields'] = metafields = []
|
|
managed_metafields = shop.managed_metafields()
|
|
for key, value in self.get_shopify_metafields(shop).items():
|
|
if key not in managed_metafields:
|
|
continue
|
|
namespace, key = key.split('.', 1)
|
|
metafields.append({
|
|
'namespace': namespace,
|
|
'key': key,
|
|
**value
|
|
})
|
|
return product
|
|
|
|
def get_shopify_metafields(self, shop):
|
|
return {}
|
|
|
|
@property
|
|
def shopify_attributes(self):
|
|
if not self.attribute_set:
|
|
return []
|
|
return filter(None, [
|
|
self.attribute_set.shopify_option1,
|
|
self.attribute_set.shopify_option2,
|
|
self.attribute_set.shopify_option3])
|
|
|
|
@classmethod
|
|
def validate_fields(cls, templates, field_names):
|
|
super().validate_fields(templates, field_names)
|
|
cls.check_shopify_handle(templates, field_names)
|
|
|
|
@classmethod
|
|
def check_shopify_handle(cls, templates, field_names):
|
|
if field_names and 'shopify_handle' not in field_names:
|
|
return
|
|
for template in templates:
|
|
if (template.shopify_handle
|
|
and not re.fullmatch(
|
|
r'[a-z0-9-]+', template.shopify_handle)):
|
|
raise TemplateValidationError(gettext(
|
|
'web_shop_shopify.msg_template_shopify_handle_invalid',
|
|
template=template.rec_name,
|
|
handle=template.shopify_handle,
|
|
))
|
|
|
|
|
|
class Template_SaleSecondaryUnit(metaclass=PoolMeta):
|
|
__name__ = 'product.template'
|
|
|
|
@classmethod
|
|
def _shopify_uom_categories(cls):
|
|
return super()._shopify_uom_categories() + [
|
|
'sale_secondary_uom_category']
|
|
|
|
def get_shopify_uom(self):
|
|
uom = super().get_shopify_uom()
|
|
if self.sale_secondary_uom and not self.sale_secondary_uom.digits:
|
|
uom = self.sale_secondary_uom
|
|
return uom
|
|
|
|
|
|
class Product(IdentifiersMixin, metaclass=PoolMeta):
|
|
__name__ = 'product.product'
|
|
|
|
shopify_sku = fields.Function(
|
|
fields.Char("SKU"), 'get_shopify_sku', searcher='search_shopify_sku')
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls._shopify_fields.update(['code', 'attributes', 'position'])
|
|
|
|
@classmethod
|
|
def get_shopify_identifier_to_update(cls, records):
|
|
pool = Pool()
|
|
InventoryItem = pool.get('product.shopify_inventory_item')
|
|
items = InventoryItem.browse(records)
|
|
return (super().get_shopify_identifier_to_update(records)
|
|
+ sum((list(i.shopify_identifiers) for i in items), []))
|
|
|
|
def set_shopify_identifier(self, web_shop, identifier=None):
|
|
pool = Pool()
|
|
InventoryItem = pool.get('product.shopify_inventory_item')
|
|
if not identifier:
|
|
inventory_item = InventoryItem(self.id)
|
|
inventory_item.set_shopify_identifier(web_shop)
|
|
return super().set_shopify_identifier(web_shop, identifier=identifier)
|
|
|
|
def get_shopify_sku(self, name):
|
|
return self.code
|
|
|
|
@classmethod
|
|
def search_shopify_sku(cls, name, clause):
|
|
return [('code',) + tuple(clause[1:])]
|
|
|
|
def get_shopify(
|
|
self, shop, sale_price, sale_tax, price, tax,
|
|
shop_taxes_included=True):
|
|
shopify_id = self.get_shopify_identifier(shop)
|
|
if shopify_id:
|
|
shopify_id = id2gid('ProductVariant', shopify_id)
|
|
variant = shopify.GraphQL().execute(
|
|
QUERY_VARIANT % {
|
|
'fields': graphql.selection({
|
|
'id': None,
|
|
}),
|
|
}, {'id': shopify_id})['data']['productVariant'] or {}
|
|
else:
|
|
variant = {}
|
|
sale_price = self.shopify_price(
|
|
sale_price, sale_tax, taxes_included=shop_taxes_included)
|
|
if sale_price is not None:
|
|
variant['price'] = str(sale_price.quantize(Decimal('.00')))
|
|
else:
|
|
variant['price'] = None
|
|
price = self.shopify_price(
|
|
price, tax, taxes_included=shop_taxes_included)
|
|
if price is not None:
|
|
variant['compareAtPrice'] = str(
|
|
price.quantize(Decimal('.00')))
|
|
else:
|
|
variant['compareAtPrice'] = None
|
|
variant['taxable'] = bool(sale_tax)
|
|
|
|
for identifier in self.identifiers:
|
|
if identifier.type == 'ean':
|
|
variant['barcode'] = identifier.code
|
|
break
|
|
|
|
variant['optionValues'] = options = []
|
|
attributes = self.attributes or {}
|
|
for attribute in self.template.shopify_attributes:
|
|
value = attributes.get(attribute.name)
|
|
value = attribute.format(value)
|
|
options.append({
|
|
'optionName': attribute.string,
|
|
'name': value,
|
|
})
|
|
|
|
variant['metafields'] = metafields = []
|
|
managed_metafields = shop.managed_metafields()
|
|
for key, value in self.get_shopify_metafields(shop).items():
|
|
if key not in managed_metafields:
|
|
continue
|
|
namespace, key = key.split('.', 1)
|
|
metafields.append({
|
|
'namespace': namespace,
|
|
'key': key,
|
|
'value': value,
|
|
})
|
|
return variant
|
|
|
|
def get_shopify_metafields(self, shop):
|
|
return {}
|
|
|
|
def shopify_price(self, price, tax, taxes_included=True):
|
|
pool = Pool()
|
|
Uom = pool.get('product.uom')
|
|
if price is None or tax is None:
|
|
return None
|
|
if taxes_included:
|
|
price += tax
|
|
return Uom.compute_price(
|
|
self.sale_uom, price, self.shopify_uom,
|
|
factor=self.shopify_uom_factor, rate=self.shopify_uom_rate)
|
|
|
|
@property
|
|
def shopify_uom_factor(self):
|
|
return None
|
|
|
|
@property
|
|
def shopify_uom_rate(self):
|
|
return None
|
|
|
|
@property
|
|
def shopify_quantity(self):
|
|
pool = Pool()
|
|
Uom = pool.get('product.uom')
|
|
quantity = self.forecast_quantity
|
|
if quantity < 0:
|
|
quantity = 0
|
|
return Uom.compute_qty(
|
|
self.default_uom, quantity, self.shopify_uom, round=True,
|
|
factor=self.shopify_uom_factor, rate=self.shopify_uom_rate)
|
|
|
|
|
|
class ProductURL(metaclass=PoolMeta):
|
|
__name__ = 'product.web_shop_url'
|
|
|
|
def get_url(self, name):
|
|
url = super().get_url(name)
|
|
if (self.shop.type == 'shopify'
|
|
and (handle := self.product.template.shopify_handle)):
|
|
url = urljoin(self.shop.shopify_url + '/', f'products/{handle}')
|
|
return url
|
|
|
|
|
|
class ShopifyInventoryItem(IdentifiersMixin, ModelSQL, ModelView):
|
|
__name__ = 'product.shopify_inventory_item'
|
|
|
|
product = fields.Function(
|
|
fields.Many2One('product.product', "Product"), 'get_product')
|
|
|
|
@classmethod
|
|
def table_query(cls):
|
|
return Pool().get('product.product').__table__()
|
|
|
|
def get_product(self, name):
|
|
return self.id
|
|
|
|
def get_shopify(self, shop, shop_weight_unit=None):
|
|
pool = Pool()
|
|
SaleLine = pool.get('sale.line')
|
|
ModelData = pool.get('ir.model.data')
|
|
Uom = pool.get('product.uom')
|
|
|
|
movable_types = SaleLine.movable_types()
|
|
|
|
inventory_item = {}
|
|
inventory_item['sku'] = self.product.shopify_sku
|
|
inventory_item['tracked'] = (
|
|
self.product.type in movable_types and not self.product.consumable)
|
|
inventory_item['requiresShipping'] = (
|
|
self.product.type in movable_types)
|
|
|
|
if getattr(self.product, 'weight', None) and shop_weight_unit:
|
|
units = {}
|
|
units['KILOGRAMS'] = ModelData.get_id('product', 'uom_kilogram')
|
|
units['GRAMS'] = ModelData.get_id('product', 'uom_gram')
|
|
units['POUNDS'] = ModelData.get_id('product', 'uom_pound')
|
|
units['OUNCES'] = ModelData.get_id('product', 'uom_ounce')
|
|
weight = self.product.weight
|
|
weight_unit = self.product.weight_uom
|
|
if self.product.weight_uom.id not in units.values():
|
|
weight_unit = Uom(units[shop_weight_unit])
|
|
weight = Uom.compute_qty(
|
|
self.product.weight_uom, weight, weight_unit)
|
|
weight_unit = {
|
|
v: k for k, v in units.items()}[weight_unit.id]
|
|
inventory_item['measurement'] = {
|
|
'weight': {
|
|
'unit': weight_unit,
|
|
'value': weight,
|
|
},
|
|
}
|
|
|
|
return inventory_item
|
|
|
|
|
|
class ShopifyInventoryItem_Customs(metaclass=PoolMeta):
|
|
__name__ = 'product.shopify_inventory_item'
|
|
|
|
def get_shopify(self, shop, shop_weight_unit=None):
|
|
pool = Pool()
|
|
Date = pool.get('ir.date')
|
|
inventory_item = super().get_shopify(
|
|
shop, shop_weight_unit=shop_weight_unit)
|
|
with Transaction().set_context(company=shop.company.id):
|
|
today = Date.today()
|
|
inventory_item['countryCodeOfOrigin'] = (
|
|
self.product.country_of_origin.code
|
|
if self.product.country_of_origin else None)
|
|
tariff_code = self.product.get_tariff_code(
|
|
{'date': today, 'country': None})
|
|
inventory_item['harmonizedSystemCode'] = (
|
|
tariff_code.code if tariff_code else None)
|
|
country_harmonized_system_codes = []
|
|
countries = set()
|
|
for tariff_code in self.product.get_tariff_codes({'date': today}):
|
|
if (tariff_code.country
|
|
and tariff_code.country not in countries):
|
|
country_harmonized_system_codes.append({
|
|
'harmonizedSystemCode': tariff_code.code,
|
|
'countryCode': tariff_code.country.code,
|
|
})
|
|
countries.add(tariff_code.country)
|
|
inventory_item['countryHarmonizedSystemCodes'] = (
|
|
country_harmonized_system_codes)
|
|
return inventory_item
|
|
|
|
|
|
class Product_TariffCode(IdentifiersUpdateMixin, metaclass=PoolMeta):
|
|
__name__ = 'product-customs.tariff.code'
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls._shopify_fields.update(['product', 'tariff_code'])
|
|
|
|
@classmethod
|
|
def get_shopify_identifier_to_update(cls, records):
|
|
pool = Pool()
|
|
Template = pool.get('product.template')
|
|
Category = pool.get('product.category')
|
|
templates = set()
|
|
categories = set()
|
|
for record in records:
|
|
if isinstance(record.product, Template):
|
|
templates.add(record.product)
|
|
elif isinstance(record.product, Category):
|
|
categories.add(record.product)
|
|
if categories:
|
|
for sub_categories in grouped_slice(list(categories)):
|
|
templates.update(Template.search([
|
|
('customs_category', 'in',
|
|
[c.id for c in sub_categories]),
|
|
]))
|
|
templates = Template.browse(list(templates))
|
|
return Template.get_shopify_identifier_to_update(templates)
|
|
|
|
|
|
class ProductIdentifier(IdentifiersUpdateMixin, metaclass=PoolMeta):
|
|
__name__ = 'product.identifier'
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls._shopify_fields.update(['product', 'code'])
|
|
|
|
@classmethod
|
|
def get_shopify_identifier_to_update(cls, identifiers):
|
|
return sum((
|
|
list(i.product.shopify_identifiers) for i in identifiers), [])
|
|
|
|
|
|
class Product_SaleSecondaryUnit(metaclass=PoolMeta):
|
|
__name__ = 'product.product'
|
|
|
|
@property
|
|
def shopify_uom_factor(self):
|
|
factor = super().shopify_uom_factor
|
|
if (self.sale_secondary_uom
|
|
and self.shopify_uom.category
|
|
== self.sale_secondary_uom.category):
|
|
factor = self.sale_secondary_uom_normal_factor
|
|
return factor
|
|
|
|
@property
|
|
def shopify_uom_rate(self):
|
|
rate = super().shopify_uom_rate
|
|
if (self.sale_secondary_uom
|
|
and self.shopify_uom.category
|
|
== self.sale_secondary_uom.category):
|
|
rate = self.sale_secondary_uom_normal_rate
|
|
return rate
|
|
|
|
|
|
class AttributeSet(IdentifiersUpdateMixin, metaclass=PoolMeta):
|
|
__name__ = 'product.attribute.set'
|
|
|
|
shopify_option1 = fields.Many2One(
|
|
'product.attribute', "Option 1",
|
|
domain=[
|
|
('id', 'in', Eval('attributes', [])),
|
|
If(Eval('shopify_option2'),
|
|
('id', '!=', Eval('shopify_option2')),
|
|
()),
|
|
If(Eval('shopify_option3'),
|
|
('id', '!=', Eval('shopify_option3')),
|
|
()),
|
|
])
|
|
shopify_option2 = fields.Many2One(
|
|
'product.attribute', "Option 2",
|
|
domain=[
|
|
('id', 'in', Eval('attributes', [])),
|
|
If(Eval('shopify_option1'),
|
|
('id', '!=', Eval('shopify_option1')),
|
|
('id', '=', None)),
|
|
If(Eval('shopify_option3'),
|
|
('id', '!=', Eval('shopify_option3')),
|
|
()),
|
|
],
|
|
states={
|
|
'invisible': ~Eval('shopify_option1'),
|
|
})
|
|
shopify_option3 = fields.Many2One(
|
|
'product.attribute', "Option 3",
|
|
domain=[
|
|
('id', 'in', Eval('attributes', [])),
|
|
If(Eval('shopify_option1'),
|
|
('id', '!=', Eval('shopify_option1')),
|
|
()),
|
|
If(Eval('shopify_option2'),
|
|
('id', '!=', Eval('shopify_option2')),
|
|
('id', '=', None)),
|
|
],
|
|
states={
|
|
'invisible': ~Eval('shopify_option2'),
|
|
})
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls._shopify_fields.update(
|
|
['shopify_option1', 'shopify_option2', 'shopify_option3'])
|
|
|
|
@classmethod
|
|
def get_shopify_identifier_to_update(cls, sets):
|
|
pool = Pool()
|
|
Template = pool.get('product.template')
|
|
templates = []
|
|
for sub_sets in grouped_slice(sets):
|
|
templates.extend(Template.search([
|
|
('attribute_set', 'in', [s.id for s in sub_sets]),
|
|
]))
|
|
return Template.get_shopify_identifier_to_update(templates)
|
|
|
|
|
|
class Attribute(IdentifiersUpdateMixin, metaclass=PoolMeta):
|
|
__name__ = 'product.attribute'
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls._shopify_fields.add('selection')
|
|
domain = [
|
|
('type', '!=', 'shopify'),
|
|
]
|
|
if cls.web_shops.domain:
|
|
cls.web_shops.domain = [cls.web_shops.domain, domain]
|
|
else:
|
|
cls.web_shops.domain = domain
|
|
|
|
@classmethod
|
|
def get_shopify_identifier_to_update(cls, attributes):
|
|
pool = Pool()
|
|
Set = pool.get('product.attribute.set')
|
|
sets = Set.browse(sum((a.sets for a in attributes), ()))
|
|
return Set.get_shopify_identifier_to_update(sets)
|
|
|
|
|
|
class Template_Image(metaclass=PoolMeta):
|
|
__name__ = 'product.template'
|
|
|
|
@property
|
|
def shopify_images(self):
|
|
for image in self.images_used:
|
|
if image.web_shop:
|
|
yield image
|
|
|
|
def get_shopify(self, shop, categories):
|
|
product = super().get_shopify(shop, categories)
|
|
product['files'] = files = []
|
|
for image in self.shopify_images:
|
|
file = {
|
|
'alt': image.description,
|
|
'contentType': 'IMAGE',
|
|
'filename': image.shopify_name,
|
|
}
|
|
if image_id := image.get_shopify_identifier(shop):
|
|
file['id'] = id2gid('MediaImage', image_id)
|
|
else:
|
|
file['originalSource'] = self.get_image_url(
|
|
_external=True, id=image.id)
|
|
files.append(file)
|
|
return product
|
|
|
|
|
|
class Product_Image(metaclass=PoolMeta):
|
|
__name__ = 'product.product'
|
|
|
|
@property
|
|
def shopify_images(self):
|
|
for image in self.images_used:
|
|
if image.web_shop:
|
|
yield image
|
|
|
|
def get_shopify(
|
|
self, shop, sale_price, sale_tax, price, tax,
|
|
shop_taxes_included=True):
|
|
variant = super().get_shopify(
|
|
shop, sale_price, sale_tax, price, tax,
|
|
shop_taxes_included=shop_taxes_included)
|
|
for image in self.shopify_images:
|
|
file = {
|
|
'alt': image.description,
|
|
'contentType': 'IMAGE',
|
|
'filename': image.shopify_name,
|
|
}
|
|
if image_id := image.get_shopify_identifier(shop):
|
|
file['id'] = id2gid('MediaImage', image_id)
|
|
else:
|
|
file['originalSource'] = self.get_image_url(
|
|
_external=True, id=image.id)
|
|
variant['file'] = file
|
|
break
|
|
else:
|
|
variant['file'] = None
|
|
return variant
|
|
|
|
|
|
class Image(IdentifiersMixin, metaclass=PoolMeta):
|
|
__name__ = 'product.image'
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls._shopify_fields.update(['template', 'product', 'attributes'])
|
|
|
|
@classmethod
|
|
def on_write(cls, images, values):
|
|
pool = Pool()
|
|
Identifier = pool.get('web.shop.shopify_identifier')
|
|
callback = super().on_write(images, values)
|
|
if values.keys() & {'image', 'template', 'web_shop'}:
|
|
to_delete = []
|
|
for image in images:
|
|
to_delete.extend(image.shopify_identifiers)
|
|
if to_delete:
|
|
callback.append(lambda: Identifier.delete(to_delete))
|
|
return callback
|
|
|
|
@classmethod
|
|
def get_shopify_identifier_to_update(cls, images):
|
|
return (
|
|
sum((list(i.template.shopify_identifiers) for i in images), [])
|
|
+ sum(
|
|
(list(p.shopify_identifiers)
|
|
for i in images for p in i.template.products), []))
|
|
|
|
@property
|
|
def shopify_name(self):
|
|
if self.product:
|
|
name = self.product.name
|
|
else:
|
|
name = self.template.name
|
|
name = slugify(name)
|
|
return f'{name}.jpg'
|
|
|
|
|
|
class Image_Attribute(metaclass=PoolMeta):
|
|
__name__ = 'product.image'
|
|
|
|
@property
|
|
def shopify_name(self):
|
|
name = super().shopify_name
|
|
if self.product:
|
|
attributes_name = self.product.attributes_name
|
|
else:
|
|
attributes_name = self.attributes_name
|
|
if attributes_name:
|
|
name += ' ' + attributes_name
|
|
return name
|