Skip to content

Commit 5fd5695

Browse files
Merge pull request #377 from django-oscar/product-serializer-validations
[Fix] Child products should not have product class
2 parents 56a7896 + 41011e9 commit 5fd5695

6 files changed

Lines changed: 360 additions & 17 deletions

File tree

.github/workflows/ci.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ jobs:
4141
matrix:
4242
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
4343
django-version: ["3.2", "4.2", "5.2"]
44-
oscar-version: ["3.2", "4.0"]
44+
oscar-version: ["3.2", "4.0", "4.1"]
4545
exclude:
4646
- python-version: "3.13"
4747
django-version: "3.2"
@@ -59,6 +59,8 @@ jobs:
5959
django-version: "5.2"
6060
- python-version: "3.8"
6161
oscar-version: "4.0"
62+
- python-version: "3.8"
63+
oscar-version: "4.1"
6264
steps:
6365
- name: Download src dir
6466
uses: actions/download-artifact@v4

oscarapi/fixtures/product.xml

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<django-objects version="1.0">
33
<object pk="1" model="catalogue.product">
4-
<field type="CharField" name="structure">parent</field>
4+
<field type="CharField" name="structure">standalone</field>
55
<field type="CharField" name="upc">1234</field>
66
<field to="catalogue.product" name="parent" rel="ManyToOneRel">
77
<None/>
@@ -18,10 +18,28 @@
1818
<field type="BooleanField" name="is_discountable">True</field>
1919
<field to="catalogue.option" name="product_options" rel="ManyToManyRel"/>
2020
</object>
21+
<object pk="5" model="catalogue.product">
22+
<field type="CharField" name="structure">parent</field>
23+
<field type="CharField" name="upc">parent-1234</field>
24+
<field to="catalogue.product" name="parent" rel="ManyToOneRel">
25+
<None/>
26+
</field>
27+
<field type="CharField" name="title">Oscar T-shirt</field>
28+
<field type="SlugField" name="slug">parent-1234</field>
29+
<field type="TextField" name="description">Hank</field>
30+
<field to="catalogue.productclass" name="product_class" rel="ManyToOneRel">1</field>
31+
<field type="FloatField" name="rating">
32+
<None/>
33+
</field>
34+
<field type="DateTimeField" name="date_created">2013-12-12T16:33:57.426000+00:00</field>
35+
<field type="DateTimeField" name="date_updated">2013-12-12T16:33:57.426000+00:00</field>
36+
<field type="BooleanField" name="is_discountable">True</field>
37+
<field to="catalogue.option" name="product_options" rel="ManyToManyRel"/>
38+
</object>
2139
<object pk="2" model="catalogue.product">
2240
<field type="CharField" name="structure">child</field>
2341
<field type="CharField" name="upc">child-1234</field>
24-
<field to="catalogue.product" name="parent" rel="ManyToOneRel">1</field>
42+
<field to="catalogue.product" name="parent" rel="ManyToOneRel">5</field>
2543
<field type="CharField" name="title"/>
2644
<field type="SlugField" name="slug">oscar-t-shirt-child</field>
2745
<field type="TextField" name="description"/>

oscarapi/serializers/admin/product.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,14 @@ def update(self, instance, validated_data):
9393
categories = validated_data.pop("categories", None)
9494
recommended_products = validated_data.pop("recommended_products", None)
9595
children = validated_data.pop("children", None)
96+
structure = validated_data.get("structure", instance.structure)
9697
# Child products are not supposed to have a product class, their product
9798
# class comes from the parent product.
98-
if instance.is_child:
99-
validated_data.pop("product_class", None)
99+
if structure == Product.CHILD:
100+
validated_data["product_class"] = None
101+
else:
102+
# Parent and standalone products are not supposed to have a parent
103+
validated_data["parent"] = None
100104

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

125+
if instance.is_child:
126+
instance.categories.all().delete()
127+
121128
if recommended_products is not None:
122129
with fake_autocreated(
123130
instance.recommended_products
@@ -148,11 +155,17 @@ def update(self, instance, validated_data):
148155
self.update_relation("options", _product_options, new_options)
149156

150157
self.update_relation("images", instance.images, images)
151-
self.update_relation("stockrecords", instance.stockrecords, stockrecords)
152158
self.update_relation(
153159
"attributes", instance.attribute_values, attribute_values
154160
)
155161

162+
if instance.is_parent:
163+
instance.stockrecords.all().delete()
164+
else:
165+
self.update_relation(
166+
"stockrecords", instance.stockrecords, stockrecords
167+
)
168+
156169
if (
157170
self.partial
158171
): # we need to clean up all the attributes with wrong product class

oscarapi/serializers/product.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -460,15 +460,30 @@ class BaseProductSerializer(OscarModelSerializer):
460460
)
461461

462462
def validate(self, attrs):
463-
if "structure" in attrs and "parent" in attrs:
463+
structure = attrs.get("structure", None)
464+
if structure and "parent" in attrs:
464465
if attrs["structure"] == Product.CHILD and attrs["parent"] is None:
465466
raise serializers.ValidationError(_("child without parent"))
466-
if "structure" in attrs and "product_class" in attrs:
467+
if structure and "product_class" in attrs:
467468
if attrs["product_class"] is None and attrs["structure"] != Product.CHILD:
468469
raise serializers.ValidationError(
469470
_("product_class can not be empty for structure %(structure)s")
470471
% attrs
471472
)
473+
if (
474+
self.instance
475+
and structure
476+
and structure != Product.PARENT
477+
and self.instance.structure == Product.PARENT
478+
and self.instance.children.exists()
479+
):
480+
raise serializers.ValidationError(
481+
{
482+
"structure": _(
483+
"Cannot convert parent product with children to a non-parent"
484+
)
485+
}
486+
)
472487

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

0 commit comments

Comments
 (0)