|
|
@@ -19,29 +19,42 @@ class ProductAttributesContainer:
|
|
19
|
19
|
self.__dict__ = state
|
|
20
|
20
|
|
|
21
|
21
|
def __init__(self, product):
|
|
22
|
|
- self._product = product
|
|
23
|
|
- self._initialized = False
|
|
|
22
|
+ # use __dict__ directly to avoid triggering __setattr__, which would
|
|
|
23
|
+ # cause a recursion error on _initialized.
|
|
|
24
|
+ self.__dict__.update(
|
|
|
25
|
+ {"_product": product, "_initialized": False, "_dirty": set()}
|
|
|
26
|
+ )
|
|
24
|
27
|
|
|
25
|
28
|
@property
|
|
26
|
29
|
def product(self):
|
|
27
|
30
|
return self._product
|
|
28
|
31
|
|
|
|
32
|
+ @property
|
|
|
33
|
+ def initialized(self):
|
|
|
34
|
+ return self._initialized
|
|
|
35
|
+
|
|
|
36
|
+ @initialized.setter
|
|
|
37
|
+ def initialized(self, value):
|
|
|
38
|
+ # use __dict__ directly to avoid triggering __setattr__, which would
|
|
|
39
|
+ # cause a recursion error.
|
|
|
40
|
+ self.__dict__["_initialized"] = value
|
|
|
41
|
+
|
|
29
|
42
|
def initialize(self):
|
|
30
|
|
- self._initialized = True
|
|
|
43
|
+ self.initialized = True
|
|
31
|
44
|
# initialize should not overwrite any values that have allready been set
|
|
32
|
45
|
attrs = self.__dict__
|
|
33
|
|
- for v in self.get_values().select_related('attribute'):
|
|
|
46
|
+ for v in self.get_values().select_related("attribute"):
|
|
34
|
47
|
attrs.setdefault(v.attribute.code, v.value)
|
|
35
|
48
|
|
|
36
|
49
|
def refresh(self):
|
|
37
|
|
- for v in self.get_values().select_related('attribute'):
|
|
|
50
|
+ for v in self.get_values().select_related("attribute"):
|
|
38
|
51
|
setattr(self, v.attribute.code, v.value)
|
|
39
|
52
|
|
|
40
|
53
|
def __getattribute__(self, name):
|
|
41
|
54
|
try:
|
|
42
|
55
|
return super().__getattribute__(name)
|
|
43
|
56
|
except AttributeError:
|
|
44
|
|
- if self._initialized:
|
|
|
57
|
+ if self.initialized:
|
|
45
|
58
|
raise
|
|
46
|
59
|
else:
|
|
47
|
60
|
self.initialize()
|
|
|
@@ -50,7 +63,13 @@ class ProductAttributesContainer:
|
|
50
|
63
|
|
|
51
|
64
|
def __getattr__(self, name):
|
|
52
|
65
|
raise AttributeError(
|
|
53
|
|
- _("%(obj)s has no attribute named '%(attr)s'") % {'obj': self.product.get_product_class(), 'attr': name})
|
|
|
66
|
+ _("%(obj)s has no attribute named '%(attr)s'")
|
|
|
67
|
+ % {"obj": self.product.get_product_class(), "attr": name}
|
|
|
68
|
+ )
|
|
|
69
|
+
|
|
|
70
|
+ def __setattr__(self, name, value):
|
|
|
71
|
+ self._dirty.add(name)
|
|
|
72
|
+ super().__setattr__(name, value)
|
|
54
|
73
|
|
|
55
|
74
|
def validate_attributes(self):
|
|
56
|
75
|
for attribute in self.get_all_attributes():
|
|
|
@@ -58,15 +77,17 @@ class ProductAttributesContainer:
|
|
58
|
77
|
if value is None:
|
|
59
|
78
|
if attribute.required:
|
|
60
|
79
|
raise ValidationError(
|
|
61
|
|
- _("%(attr)s attribute cannot be blank") %
|
|
62
|
|
- {'attr': attribute.code})
|
|
|
80
|
+ _("%(attr)s attribute cannot be blank")
|
|
|
81
|
+ % {"attr": attribute.code}
|
|
|
82
|
+ )
|
|
63
|
83
|
else:
|
|
64
|
84
|
try:
|
|
65
|
85
|
attribute.validate_value(value)
|
|
66
|
86
|
except ValidationError as e:
|
|
67
|
87
|
raise ValidationError(
|
|
68
|
|
- _("%(attr)s attribute %(err)s") %
|
|
69
|
|
- {'attr': attribute.code, 'err': e})
|
|
|
88
|
+ _("%(attr)s attribute %(err)s")
|
|
|
89
|
+ % {"attr": attribute.code, "err": e}
|
|
|
90
|
+ )
|
|
70
|
91
|
|
|
71
|
92
|
def get_values(self):
|
|
72
|
93
|
return self.product.get_attribute_values()
|
|
|
@@ -87,15 +108,18 @@ class ProductAttributesContainer:
|
|
87
|
108
|
for attribute in self.get_all_attributes():
|
|
88
|
109
|
if hasattr(self, attribute.code):
|
|
89
|
110
|
value = getattr(self, attribute.code)
|
|
90
|
|
- # Make sure that if a value comes from a parent product, it is not
|
|
91
|
|
- # copied to the child, we do this by checking if a value has been
|
|
92
|
|
- # changed, which would not be the case if the value comes from the
|
|
93
|
|
- # parent.
|
|
94
|
|
- try:
|
|
95
|
|
- attribute_value_current = self.get_value_by_attribute(attribute)
|
|
96
|
|
- if attribute_value_current.value == value:
|
|
97
|
|
- continue # no new value needs to be saved
|
|
98
|
|
- except ObjectDoesNotExist:
|
|
99
|
|
- pass # there is no existing value, so a value needs to be saved.
|
|
|
111
|
+ if attribute.code not in self._dirty:
|
|
|
112
|
+ # Make sure that if a value comes from a parent product, it is not
|
|
|
113
|
+ # copied to the child, we do this by checking if a value has been
|
|
|
114
|
+ # changed, which would not be the case if the value comes from the
|
|
|
115
|
+ # parent.
|
|
|
116
|
+ # for attributes are are set explicitly (_dirty), this check is not
|
|
|
117
|
+ # needed and should always be saved.
|
|
|
118
|
+ try:
|
|
|
119
|
+ attribute_value_current = self.get_value_by_attribute(attribute)
|
|
|
120
|
+ if attribute_value_current.value == value:
|
|
|
121
|
+ continue # no new value needs to be saved
|
|
|
122
|
+ except ObjectDoesNotExist:
|
|
|
123
|
+ pass # there is no existing value, so a value needs to be saved.
|
|
100
|
124
|
|
|
101
|
125
|
attribute.save_value(self.product, value)
|