Source code for shuup.campaigns.utils.matcher

# -*- coding: utf-8 -*-
# This file is part of Shuup.
#
# Copyright (c) 2012-2021, Shuup Commerce Inc. All rights reserved.
#
# This source code is licensed under the OSL-3.0 license found in the
# LICENSE file in the root directory of this source tree.
from django.db.models import ManyToManyField

from shuup.apps.provides import get_provide_objects
from shuup.core.models import Category, Product, ProductType, ShopProduct


[docs]def get_matching_for_product(shop_product, provide_category, skippable_classes=None): """ Get matching ids for shop product based on provide category For example: matching_ids = get_matching_for_product(shop_product, "campaign_catalog_filter") :param shop_product: A Shop Product :type shop_product: shuup.core.models.ShopProduct :param provide_category: Provide category name :type provide_category: str :param skip: Classes to skip :type skip: None or list :return: list of collected item ids :rtype: list[int] """ collected = set() matcher = ProductCampaignMatcher(shop_product) for item in get_provide_objects(provide_category): if skippable_classes: objects = item._meta.model.objects.not_instance_of(*skippable_classes).all() else: objects = item._meta.model.objects.all() for obj in objects: if matcher.matches(obj): collected.add(obj.pk) return collected
[docs]class ProductCampaignMatcher(object): """ A class to match shop product to existing campaigns """ def __init__(self, shop_product, **kwargs): """ :param shop_product: a Shop Product to find matches for :type shop_product: shuup.core.models.ShopProduct """ assert isinstance(shop_product, ShopProduct) self.shop = shop_product.shop self.product = shop_product.product self.shop_product = shop_product
[docs] def matches(self, obj): """ Tries to match filters, conditions, effects etc to shop product :return: True or False based on the match :rtype: bool """ _matches = [] for field in obj._meta.get_fields(): related_model = field.related_model if self._is_product_kind(related_model): _matches.append(self.product_matcher(field, obj)) elif self._is_category(related_model): _matches.append(self.category_matcher(field, obj)) elif self._is_product_type(related_model): _matches.append(self.product_type_matcher(field, obj)) else: _matches.append(False) return any(_matches)
[docs] def product_type_matcher(self, field, obj): if isinstance(field, ManyToManyField): return self._product_types_match(field.name, obj) else: return self._product_type_matches(field.name, obj)
[docs] def category_matcher(self, field, obj): if isinstance(field, ManyToManyField): return self._categories_match(field.name, obj) else: return self._category_matches(field.name, obj)
[docs] def product_matcher(self, field, obj): if isinstance(field, ManyToManyField): return self._products_match(field, obj) else: return self._product_matches(field, obj)
def _product_types_match(self, field_name, obj): return getattr(obj, field_name).filter(pk=self.product.type.pk).exists() def _product_type_matches(self, field_name, obj): attr = getattr(obj, field_name) if not attr: return False return self.product.type.pk == attr.pk def _products_match(self, field, obj): attr = getattr(obj, field.name) if not attr: return False if self._is_product(field.related_model): return attr.filter(pk=self.product.pk).exists() elif self._is_shop_product(field.related_model): return attr.filter(product__id=self.product.pk).exists() return False def _product_matches(self, field, obj): attr = getattr(obj, field.name) if not attr: return False if self._is_product(field.related_model): return self.product.pk == attr.pk elif self._is_shop_product(field.related_model): return self.product.pk == attr.product.pk return False def _categories_match(self, field_name, obj): existing = set(getattr(obj, field_name).values_list("pk", flat=True)) if self.shop_product.primary_category and self.shop_product.primary_category.pk in existing: return True cats = set(self.shop_product.categories.values_list("pk", flat=True)) if cats.intersection(existing): return True return False def _category_matches(self, field_name, obj): attr = getattr(obj, field_name) if not attr: return False if self.shop_product.primary_category and self.shop_product.primary_category.pk == attr.pk: return True return self.shop_product.categories.filter(pk=attr.pk).exists() def _is_product_kind(self, model): return self._is_product(model) or self._is_shop_product(model) def _is_product(self, model): if model is None: return False return isinstance(model(), Product) def _is_shop_product(self, model): if model is None: return False return isinstance(model(), ShopProduct) def _is_category(self, model): if model is None: return False return isinstance(model(), Category) def _is_product_type(self, model): if model is None: return False return isinstance(model(), ProductType)