diff --git a/oscarapi/serializers/fields.py b/oscarapi/serializers/fields.py index c250338f..dd9aa18a 100644 --- a/oscarapi/serializers/fields.py +++ b/oscarapi/serializers/fields.py @@ -1,6 +1,7 @@ # pylint: disable=W0212, W0201, W0632 import logging import operator +import warnings from os.path import basename, join from urllib.parse import urlsplit, parse_qs @@ -15,6 +16,7 @@ from rest_framework.fields import get_attribute from oscar.core.loading import get_model, get_class +from oscarapi.utils.deprecations import RemovedInOScarAPI4 from oscarapi import settings from oscarapi.utils.attributes import AttributeFieldBase, attribute_details @@ -103,6 +105,11 @@ class AttributeValueField(AttributeFieldBase, serializers.Field): """ def __init__(self, **kwargs): + warnings.warn( + "AttributeValueField is deprecated and will be removed in a future version of oscarapi", + RemovedInOScarAPI4, + stacklevel=2, + ) # this field always needs the full object kwargs["source"] = "*" super(AttributeValueField, self).__init__(**kwargs) @@ -126,8 +133,8 @@ def get_data_attribute(self, data): code=data["code"], product_class__products__id=data["parent"] ) - def convert_to_internal_value(self, attribute, code, value): - internal_value = super().convert_to_internal_value(attribute, code, value) + def to_attribute_type_value(self, attribute, code, value): + internal_value = super().to_attribute_type_value(attribute, code, value) if attribute.type in [ attribute.IMAGE, attribute.FILE, @@ -147,7 +154,7 @@ def to_internal_value(self, data): # noqa attribute = self.get_data_attribute(data) - internal_value = self.convert_to_internal_value(attribute, code, value) + internal_value = self.to_attribute_type_value(attribute, code, value) # the rest of the attribute types don't need special processing try: diff --git a/oscarapi/serializers/product.py b/oscarapi/serializers/product.py index e0d4d451..cc71d01d 100644 --- a/oscarapi/serializers/product.py +++ b/oscarapi/serializers/product.py @@ -196,6 +196,48 @@ class Meta: class ProductAttributeValueListSerializer(UpdateListSerializer): + # pylint: disable=unused-argument + def shortcut_to_internal_value(self, data, productclass, attributes): + difficult_attributes = { + at.code: at + for at in productclass.attributes.filter( + type__in=[ + ProductAttribute.OPTION, + ProductAttribute.MULTI_OPTION, + ProductAttribute.DATE, + ProductAttribute.DATETIME, + ProductAttribute.ENTITY, + ] + ) + } + cv = AttributeConverter(self.context) + internal_value = [] + for item in data: + code, value = getitems(item, "code", "value") + if code is None: # delegate error state to child serializer + internal_value.append(self.child.to_internal_value(item)) + + if code in difficult_attributes: + attribute = difficult_attributes[code] + converted_value = cv.to_attribute_type_value(attribute, code, value) + internal_value.append( + { + "value": converted_value, + "attribute": attribute, + "product_class": productclass, + } + ) + else: + internal_value.append( + { + "value": value, + "attribute": code, + "product_class": productclass, + } + ) + + return internal_value + def to_internal_value(self, data): productclasses = set() attributes = set() @@ -207,52 +249,13 @@ def to_internal_value(self, data): attributes.add(code) # if all attributes belong to the same productclass, everything is just - # as expected and we can do an optimization by only resolving the productclass to the model instance and nothing else. + # as expected and we can take a shortcut by only resolving the + # productclass to the model instance and nothing else. try: if len(productclasses) == 1 and all(attributes): (product_class,) = productclasses pc = ProductClass.objects.get(slug=product_class) - difficult_attributes = { - at.code: at - for at in pc.attributes.filter( - type__in=[ - ProductAttribute.OPTION, - ProductAttribute.MULTI_OPTION, - ProductAttribute.DATE, - ProductAttribute.DATETIME, - ProductAttribute.ENTITY, - ] - ) - } - cv = AttributeConverter(self.context) - internal_value = [] - for item in data: - code, value = getitems(item, "code", "value") - if code is None: - internal_value.append(self.child.to_internal_value(item)) - - if code in difficult_attributes: - attribute = difficult_attributes[code] - converted_value = cv.convert_to_internal_value( - attribute, code, value - ) - internal_value.append( - { - "value": converted_value, - "attribute": attribute, - "product_class": pc, - } - ) - else: - internal_value.append( - { - "value": value, - "attribute": code, - "product_class": pc, - } - ) - return internal_value - + return self.shortcut_to_internal_value(data, pc, attributes) except ProductClass.DoesNotExist: pass @@ -293,10 +296,10 @@ def update(self, instance, validated_data): if hasattr( attribute, "code" ): # if the attribute is a model instance use the code - product.attr.set(attribute.code, value) + product.attr.set(attribute.code, value, validate_identifier=False) attr_codes.append(attribute.code) else: - product.attr.set(attribute, value) + product.attr.set(attribute, value, validate_identifier=False) attr_codes.append(attribute) # if we don't clear the dirty attributes all parent attributes @@ -304,7 +307,6 @@ def update(self, instance, validated_data): # child product. product.attr._dirty.clear() # pylint: disable=protected-access product.attr.save() - return list(product.attr.get_values().filter(attribute__code__in=attr_codes)) diff --git a/oscarapi/utils/attributes.py b/oscarapi/utils/attributes.py index c97376bd..fdef848c 100644 --- a/oscarapi/utils/attributes.py +++ b/oscarapi/utils/attributes.py @@ -27,7 +27,7 @@ class AttributeFieldBase: ), } - def convert_to_internal_value(self, attribute, code, value): + def to_attribute_type_value(self, attribute, code, value): internal_value = value # pylint: disable=no-member if attribute.required and value is None: diff --git a/oscarapi/utils/deprecations.py b/oscarapi/utils/deprecations.py new file mode 100644 index 00000000..88ccef88 --- /dev/null +++ b/oscarapi/utils/deprecations.py @@ -0,0 +1,2 @@ +class RemovedInOScarAPI4(PendingDeprecationWarning): + pass