From 8a0a811821a86bd9b884fc0cba67fe057561988e Mon Sep 17 00:00:00 2001 From: Viggo de Vries Date: Fri, 23 Feb 2024 14:37:25 +0100 Subject: [PATCH] Implement productrecommendations --- oscar_odin/mappings/catalogue.py | 12 +++++++ oscar_odin/mappings/context.py | 30 ++++++++++------ oscar_odin/resources/catalogue.py | 6 ++++ tests/reverse/test_catalogue.py | 60 +++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 10 deletions(-) diff --git a/oscar_odin/mappings/catalogue.py b/oscar_odin/mappings/catalogue.py index 1fa6dd7..7de086e 100644 --- a/oscar_odin/mappings/catalogue.py +++ b/oscar_odin/mappings/catalogue.py @@ -289,6 +289,13 @@ def stockrecords( return [] + @odin.map_list_field + def recommended_products(self, values): + if values: + return RecommendedProductToModel.apply(values) + + return [] + @odin.map_field def product_class(self, value) -> ProductClassModel: if not value or self.source.structure == ProductModel.CHILD: @@ -297,6 +304,11 @@ def product_class(self, value) -> ProductClassModel: return ProductClassToModel.apply(value) +class RecommendedProductToModel(OscarBaseMapping): + from_obj = resources.catalogue.ProductRecommentation + to_obj = ProductModel + + class ParentToModel(OscarBaseMapping): from_obj = resources.catalogue.ParentProduct to_obj = ProductModel diff --git a/oscar_odin/mappings/context.py b/oscar_odin/mappings/context.py index 3bc8678..cf0fd4d 100644 --- a/oscar_odin/mappings/context.py +++ b/oscar_odin/mappings/context.py @@ -278,21 +278,23 @@ def bulk_update_or_create_many_to_many(self): m2m_to_create, m2m_to_update, _ = self.get_all_m2m_relations # Create many to many's - for relation, instances in m2m_to_create.items(): + for relation, instances_to_create in m2m_to_create.items(): fields = self.get_fields_to_update(relation.related_model) if fields is not None: - validated_m2m_instances = self.validate_instances(instances) - relation.related_model.objects.bulk_create(validated_m2m_instances) + if relation.related_model != self.Model: + instances_to_create = self.validate_instances(instances_to_create) + relation.related_model.objects.bulk_create(instances_to_create) # Update many to many's - for relation, instances in m2m_to_update.items(): + for relation, instances_to_update in m2m_to_update.items(): fields = self.get_fields_to_update(relation.related_model) if fields is not None: - validated_instances_to_update = self.validate_instances( - instances, fields=fields - ) + if relation.related_model != self.Model: + instances_to_update = self.validate_instances( + instances_to_update, fields=fields + ) relation.related_model.objects.bulk_update( - validated_instances_to_update, fields=fields + instances_to_update, fields=fields ) for relation, values in self.many_to_many_items.items(): @@ -319,7 +321,10 @@ def bulk_update_or_create_many_to_many(self): # Delete throughs if no instances are passed for the field if self.delete_related: Through.objects.filter( - product_id__in=to_delete_throughs_product_ids + **{ + "%s_id__in" + % relation.m2m_field_name(): to_delete_throughs_product_ids + } ).all().delete() if throughs: @@ -341,7 +346,12 @@ def bulk_update_or_create_many_to_many(self): # Delete remaining non-existing through models if self.delete_related: Through.objects.filter( - product_id__in=[item[0] for item in bulk_troughs.keys()] + **{ + "%s_id__in" + % relation.m2m_field_name(): [ + item[0] for item in bulk_troughs.keys() + ] + } ).exclude(id__in=bulk_troughs.values()).delete() # Save only new through models diff --git a/oscar_odin/resources/catalogue.py b/oscar_odin/resources/catalogue.py index a731336..04f439b 100644 --- a/oscar_odin/resources/catalogue.py +++ b/oscar_odin/resources/catalogue.py @@ -85,6 +85,10 @@ class ParentProduct(OscarCatalogue): upc: str +class ProductRecommentation(OscarCatalogue): + upc: str + + class Product(OscarCatalogue): """A product within Django Oscar.""" @@ -111,6 +115,8 @@ class Product(OscarCatalogue): attributes: Dict[str, Union[Any, None]] categories: List[Category] + recommended_products: List[ProductRecommentation] + date_created: Optional[datetime] date_updated: Optional[datetime] diff --git a/tests/reverse/test_catalogue.py b/tests/reverse/test_catalogue.py index 99ee1f7..917acdd 100644 --- a/tests/reverse/test_catalogue.py +++ b/tests/reverse/test_catalogue.py @@ -15,6 +15,7 @@ ProductClass as ProductClassResource, Category as CategoryResource, ParentProduct as ParentProductResource, + ProductRecommentation as ProductRecommentationResource, ) from oscar_odin.exceptions import OscarOdinException from oscar_odin.mappings.constants import ( @@ -23,6 +24,7 @@ STOCKRECORD_NUM_ALLOCATED, PRODUCTIMAGE_ORIGINAL, PRODUCT_TITLE, + PRODUCT_UPC, PRODUCT_DESCRIPTION, PRODUCTCLASS_REQUIRESSHIPPING, ) @@ -33,6 +35,7 @@ ProductImage = get_model("catalogue", "ProductImage") Category = get_model("catalogue", "Category") Partner = get_model("partner", "Partner") +ProductRecommendation = get_model("catalogue", "ProductRecommendation") class SingleProductReverseTest(TestCase): @@ -482,6 +485,63 @@ def test_create_product_with_related_fields(self): self.assertEqual(prd2.attr.harrie, 1) +class ProductRecommendationTest(TestCase): + def setUp(self): + super().setUp() + ProductClass.objects.create( + name="Klaas", slug="klaas", requires_shipping=True, track_stock=True + ) + Partner.objects.create(name="klaas") + + def test_recommendation(self): + product_resource = [ + ProductResource( + upc="recommended_product1", + title="asdf2", + slug="asdf-asdfasdf2", + description="description", + structure=Product.STANDALONE, + product_class=ProductClassResource(slug="klaas"), + ), + ProductResource( + upc="recommended_product2", + title="asdf2", + slug="asdf-asdasdfasdf2", + description="description", + structure=Product.STANDALONE, + product_class=ProductClassResource(slug="klaas"), + ), + ] + + _, errors = products_to_db(product_resource) + self.assertEqual(len(errors), 0) + + product_resource = ProductResource( + upc="harses", + title="asdf2", + slug="asdf-asdfas23df2", + description="description", + structure=Product.STANDALONE, + product_class=ProductClassResource(slug="klaas"), + recommended_products=[ + ProductRecommentationResource(upc="recommended_product1"), + ProductRecommentationResource(upc="recommended_product2"), + ], + ) + + _, errors = products_to_db(product_resource, fields_to_update=[PRODUCT_UPC]) + self.assertEqual(len(errors), 0) + + prd = Product.objects.get(upc="harses") + + self.assertEquals(ProductRecommendation.objects.count(), 2) + self.assertEquals(prd.recommended_products.count(), 2) + self.assertEquals( + sorted(list(prd.recommended_products.values_list("upc", flat=True))), + sorted(["recommended_product1", "recommended_product2"]), + ) + + class ParentChildTest(TestCase): def setUp(self): super().setUp()