Skip to content

Commit

Permalink
Make parent stuff work
Browse files Browse the repository at this point in the history
  • Loading branch information
viggo-devries committed Dec 20, 2023
1 parent 6b07963 commit 2c9e2a5
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 35 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ lint: fail-if-no-virtualenv
pylint oscar_odin/

test: fail-if-no-virtualenv
python3 runtests.py test tests.reverse.test_reallifecase
python3 runtests.py test tests.reverse.test_catalogue

black:
@black oscar_odin/
Expand Down
2 changes: 2 additions & 0 deletions oscar_odin/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class OscarOdinException(TypeError):
pass
39 changes: 17 additions & 22 deletions oscar_odin/mappings/catalogue.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,19 +217,6 @@ def attributes(self) -> Dict[str, Any]:
for item in self.source.get_attribute_values()
}

@odin.assign_field
def children(self) -> Tuple[Optional[List[resources.catalogue.Product]]]:
"""Children of parent products."""

if self.context.get("include_children", False) and self.source.is_parent:
# Return a tuple as an optional list causes problems.
return (
map_queryset(
ProductToResource, self.source.children, context=self.context
),
)
return (None,)

@odin.assign_field(to_field=("price", "currency", "availability"))
def map_stock_price(self) -> Tuple[Decimal, str, int]:
"""Resolve stock price using strategy and decompose into price/currency/availability."""
Expand Down Expand Up @@ -258,15 +245,17 @@ def images(self, values) -> List[ProductImageModel]:
"""Map related image. We save these later in bulk"""
return ProductImageToModel.apply(values)

@odin.map_field
def parent(self, parent):
if parent:
return ParentToModel.apply(parent)

return None

@odin.map_list_field
def categories(self, values) -> List[CategoryModel]:
return CategoryToModel.apply(values)

@odin.map_list_field
def children(self, values) -> List[ProductModel]:
"""Map related image."""
return []

@odin.map_list_field(
from_field=["price", "availability", "currency", "upc", "partner"]
)
Expand All @@ -288,13 +277,19 @@ def stockrecords(

@odin.map_field
def product_class(self, value) -> ProductClassModel:
if not value and self.source.structure == ProductModel.CHILD:
return None

return ProductClassToModel.apply(value)
# if isinstance(value, self.to_obj):


# return value
#
# return ProductClassToModel.apply(value)
class ParentToModel(odin.Mapping):
from_obj = resources.catalogue.ParentProduct
to_obj = ProductModel

@odin.assign_field
def structure(self):
return ProductModel.PARENT


def product_to_resource_with_strategy(
Expand Down
6 changes: 5 additions & 1 deletion oscar_odin/mappings/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

Product = get_model("catalogue", "Product")
Category = get_model("catalogue", "Category")
StockRecord = get_model("partner", "StockRecord")
ProductClass = get_model("catalogue", "ProductClass")
ProductImage = get_model("catalogue", "ProductImage")
StockRecord = get_model("partner", "StockRecord")
Partner = get_model("partner", "Partner")

PRODUCT_STRUCTURE = "Product.structure"
PRODUCT_IS_PUBLIC = "Product.is_public"
Expand All @@ -16,6 +17,7 @@
PRODUCT_META_TITLE = "Product.meta_title"
PRODUCT_META_DESCRIPTION = "Product.meta_description"
PRODUCT_PRODUCT_CLASS = "Product.product_class"
PRODUCT_PARENT = "Product.parent"
PRODUCT_IS_DISCOUNTABLE = "Product.is_discountable"

CATEGORY_NAME = "Category.name"
Expand Down Expand Up @@ -51,6 +53,7 @@
PRODUCT_META_DESCRIPTION,
PRODUCT_PRODUCT_CLASS,
PRODUCT_IS_DISCOUNTABLE,
PRODUCT_PARENT,
]

ALL_CATEGORY_FIELDS = [
Expand Down Expand Up @@ -91,4 +94,5 @@
StockRecord: ("product_id",),
ProductClass: ("slug",),
ProductImage: ("code",),
Partner: ("slug",),
}
20 changes: 17 additions & 3 deletions oscar_odin/mappings/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
from odin.utils import getmeta

from oscar_odin.utils import in_bulk
from oscar_odin.exceptions import OscarOdinException

from oscar.core.loading import get_model

Product = get_model("catalogue", "Product")


def get_instances_to_create_or_update(Model, instances, identifier_mapping):
Expand Down Expand Up @@ -71,7 +76,7 @@ def add_instances_to_o2m_relation(self, relation, instances):
self.one_to_many_items[relation] += [instances]

def add_instance_to_fk_items(self, field, instance):
if not instance.pk:
if instance is not None and not instance.pk:
self.foreign_key_items[field] += [instance]

def get_fields_to_update(self, Model):
Expand Down Expand Up @@ -126,8 +131,17 @@ def get_fk_relations(self):
relation.related_model, instances, self.identifier_mapping
)

to_create[relation].extend(instances_to_create)
to_update[relation].extend(instances_to_update)
if relation.related_model == Product:
if instances_to_create:
raise OscarOdinException(
"Cannot create parents this way. Please create all parents first seperately, then create the childs while linking the parents using the `oscar_odin.resources.catalogue.ParentProduct`"
)

for instance in instances_to_update:
instance.refresh_from_db()
else:
to_create[relation].extend(instances_to_create)
to_update[relation].extend(instances_to_update)

return (to_create, to_update)

Expand Down
19 changes: 12 additions & 7 deletions oscar_odin/resources/catalogue.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ class ProductAttributeValue(OscarCatalogue):
value: Any


class ParentProduct(OscarCatalogue):
upc: str


class Product(OscarCatalogue):
"""A product within Django Oscar."""

Expand All @@ -101,25 +105,26 @@ class Product(OscarCatalogue):
structure: Structure
title: str
slug: str
description: str = odin.Options(empty=True)
description: str = ""
meta_title: Optional[str]
images: List[Image] = odin.Options(empty=True)
rating: Optional[float]
is_discountable: bool
is_public: bool
is_discountable: bool = True
is_public: bool = True
parent: Optional[ParentProduct]

# Price information
price: Decimal = DecimalField()
currency: str
availability: Optional[int]
partner: Optional[Any]

product_class: ProductClass
product_class: Optional[ProductClass] = None
attributes: Dict[str, Any]
categories: List[Category]
children: Optional[List["Product"]] = odin.ListOf.delayed(
lambda: Product, null=True
)
# children: Optional[List["Product"]] = odin.ListOf.delayed(
# lambda: Product, null=True
# )

date_created: datetime
date_updated: datetime
94 changes: 93 additions & 1 deletion tests/reverse/test_catalogue.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
ProductClass as ProductClassResource,
Category as CategoryResource,
ProductAttributeValue as ProductAttributeValueResource,
ParentProduct as ParentProductResource,
)

from oscar_odin.exceptions import OscarOdinException
from oscar_odin.mappings.constants import (
STOCKRECORD_PRICE,
STOCKRECORD_NUM_IN_STOCK,
Expand Down Expand Up @@ -404,3 +405,94 @@ def test_create_product_with_related_fields(self):

self.assertEqual(prd2.attr.henk, "Klaas")
self.assertEqual(prd2.attr.harrie, 1)


class ParentChildTest(TestCase):
def test_parent_childs(self):
Category.add_root(name="henk", slug="klaas", is_public=True, code="2")
ProductClass.objects.create(
name="Klaas", slug="klaas", requires_shipping=True, track_stock=True
)
product_class = ProductClassResource(slug="klaas")
partner = Partner.objects.create(name="klaas")

prds = ProductResource(
upc="1234323-2",
title="asdf2",
slug="asdf-asdfasdf2",
description="description",
structure=Product.PARENT,
product_class=product_class,
categories=[CategoryResource(code="2")],
)

products_to_db(prds)

prd = Product.objects.get(upc="1234323-2")

self.assertEquals(prd.structure, Product.PARENT)
self.assertEquals(prd.product_class.slug, "klaas")

child_product = ProductResource(
parent=ParentProductResource(upc="1234323-2"),
upc="1234323-child",
title="asdf2 child",
slug="asdf-asdfasdf2-child",
structure=Product.CHILD,
price=D("20"),
availability=2,
currency="EUR",
partner=Partner.objects.create(name="klaas"),
)

products_to_db(child_product)

prd = Product.objects.get(upc="1234323-2")

self.assertEquals(prd.structure, Product.PARENT)
self.assertEquals(prd.product_class.slug, "klaas")

child = Product.objects.get(upc="1234323-child")

self.assertEquals(child.structure, Product.CHILD)
self.assertEquals(child.parent.pk, prd.pk)

def test_non_existing_parent_childs(self):
Category.add_root(name="henk", slug="klaas", is_public=True, code="2")
ProductClass.objects.create(
name="Klaas", slug="klaas", requires_shipping=True, track_stock=True
)
product_class = ProductClassResource(slug="klaas")
partner = Partner.objects.create(name="klaas")

prds = ProductResource(
upc="1234323-2",
title="asdf2",
slug="asdf-asdfasdf2",
description="description",
structure=Product.PARENT,
product_class=product_class,
categories=[CategoryResource(code="2")],
)

products_to_db(prds)

prd = Product.objects.get(upc="1234323-2")

self.assertEquals(prd.structure, Product.PARENT)
self.assertEquals(prd.product_class.slug, "klaas")

child_product = ProductResource(
parent=ParentProductResource(upc="1234323-654"),
upc="1234323-child",
title="asdf2 child",
slug="asdf-asdfasdf2-child",
structure=Product.CHILD,
price=D("20"),
availability=2,
currency="EUR",
partner=Partner.objects.create(name="klaas"),
)

with self.assertRaises(OscarOdinException):
products_to_db(child_product)
5 changes: 5 additions & 0 deletions tests/reverse/test_reallifecase.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ def is_discountable(self):

class RealLifeTest(TestCase):
def test_mapping(self):
for partner_id in ["1049", "1052", "1053", "1049"]:
Partner.objects.get_or_create(
code=partner_id, defaults={"name": partner_id}
)

# Create product class
product_class, _ = ProductClass.objects.get_or_create(
slug="standard",
Expand Down

0 comments on commit 2c9e2a5

Please sign in to comment.