Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
django-version: ["3.2", "4.2", "5.2"]
oscar-version: ["3.2", "4.0"]
oscar-version: ["3.2", "4.0", "4.1"]
exclude:
- python-version: "3.13"
django-version: "3.2"
Expand All @@ -59,6 +59,8 @@ jobs:
django-version: "5.2"
- python-version: "3.8"
oscar-version: "4.0"
- python-version: "3.8"
oscar-version: "4.1"
steps:
- name: Download src dir
uses: actions/download-artifact@v4
Expand Down
22 changes: 20 additions & 2 deletions oscarapi/fixtures/product.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<django-objects version="1.0">
<object pk="1" model="catalogue.product">
<field type="CharField" name="structure">parent</field>
<field type="CharField" name="structure">standalone</field>
<field type="CharField" name="upc">1234</field>
<field to="catalogue.product" name="parent" rel="ManyToOneRel">
<None/>
Expand All @@ -18,10 +18,28 @@
<field type="BooleanField" name="is_discountable">True</field>
<field to="catalogue.option" name="product_options" rel="ManyToManyRel"/>
</object>
<object pk="5" model="catalogue.product">
<field type="CharField" name="structure">parent</field>
<field type="CharField" name="upc">parent-1234</field>
<field to="catalogue.product" name="parent" rel="ManyToOneRel">
<None/>
</field>
<field type="CharField" name="title">Oscar T-shirt</field>
<field type="SlugField" name="slug">parent-1234</field>
<field type="TextField" name="description">Hank</field>
<field to="catalogue.productclass" name="product_class" rel="ManyToOneRel">1</field>
<field type="FloatField" name="rating">
<None/>
</field>
<field type="DateTimeField" name="date_created">2013-12-12T16:33:57.426000+00:00</field>
<field type="DateTimeField" name="date_updated">2013-12-12T16:33:57.426000+00:00</field>
<field type="BooleanField" name="is_discountable">True</field>
<field to="catalogue.option" name="product_options" rel="ManyToManyRel"/>
</object>
<object pk="2" model="catalogue.product">
<field type="CharField" name="structure">child</field>
<field type="CharField" name="upc">child-1234</field>
<field to="catalogue.product" name="parent" rel="ManyToOneRel">1</field>
<field to="catalogue.product" name="parent" rel="ManyToOneRel">5</field>
<field type="CharField" name="title"/>
<field type="SlugField" name="slug">oscar-t-shirt-child</field>
<field type="TextField" name="description"/>
Expand Down
19 changes: 16 additions & 3 deletions oscarapi/serializers/admin/product.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,14 @@ def update(self, instance, validated_data):
categories = validated_data.pop("categories", None)
recommended_products = validated_data.pop("recommended_products", None)
children = validated_data.pop("children", None)
structure = validated_data.get("structure", instance.structure)
# Child products are not supposed to have a product class, their product
# class comes from the parent product.
if instance.is_child:
validated_data.pop("product_class", None)
if structure == Product.CHILD:
validated_data["product_class"] = None
else:
# Parent and standalone products are not supposed to have a parent
validated_data["parent"] = None

with transaction.atomic(): # it is all or nothing!
# update instance
Expand All @@ -118,6 +122,9 @@ def update(self, instance, validated_data):
else:
_categories.set(categories)

if instance.is_child:
instance.categories.all().delete()

if recommended_products is not None:
with fake_autocreated(
instance.recommended_products
Expand Down Expand Up @@ -148,11 +155,17 @@ def update(self, instance, validated_data):
self.update_relation("options", _product_options, new_options)

self.update_relation("images", instance.images, images)
self.update_relation("stockrecords", instance.stockrecords, stockrecords)
self.update_relation(
"attributes", instance.attribute_values, attribute_values
)

if instance.is_parent:
instance.stockrecords.all().delete()
else:
self.update_relation(
"stockrecords", instance.stockrecords, stockrecords
)

if (
self.partial
): # we need to clean up all the attributes with wrong product class
Expand Down
19 changes: 17 additions & 2 deletions oscarapi/serializers/product.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,15 +460,30 @@ class BaseProductSerializer(OscarModelSerializer):
)

def validate(self, attrs):
if "structure" in attrs and "parent" in attrs:
structure = attrs.get("structure", None)
if structure and "parent" in attrs:
if attrs["structure"] == Product.CHILD and attrs["parent"] is None:
raise serializers.ValidationError(_("child without parent"))
if "structure" in attrs and "product_class" in attrs:
if structure and "product_class" in attrs:
if attrs["product_class"] is None and attrs["structure"] != Product.CHILD:
raise serializers.ValidationError(
_("product_class can not be empty for structure %(structure)s")
% attrs
)
if (
self.instance
and structure
and structure != Product.PARENT
and self.instance.structure == Product.PARENT
and self.instance.children.exists()
):
raise serializers.ValidationError(
{
"structure": _(
"Cannot convert parent product with children to a non-parent"
)
}
)

return super(BaseProductSerializer, self).validate(attrs)

Expand Down
Loading