Skip to content

Commit 179cbff

Browse files
author
bosd
committed
website_attribute_set: Fixups
1 parent 93af3dc commit 179cbff

File tree

6 files changed

+132
-158
lines changed

6 files changed

+132
-158
lines changed

website_attribute_set/__manifest__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"depends": [
1414
"attribute_set",
1515
"product_attribute_set",
16-
"pim",
16+
# "pim",
1717
"website",
1818
"website_sale",
1919
"website_sale_comparison",

website_attribute_set/controllers/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
from werkzeug.exceptions import NotFound
88

99
from odoo import fields
10+
from odoo.fields import Domain
1011
from odoo.http import request, route
1112
from odoo.models import BaseModel
12-
from odoo.fields import Domain
1313
from odoo.tools import SQL, float_round, groupby, lazy
1414

1515
from odoo.addons.website.controllers.main import QueryURL

website_attribute_set/models/attribute_attribute.py

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
# @author Mohamed Alkobrosli <[email protected]>
33
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
44

5+
56
from odoo import api, fields, models
67
from odoo.exceptions import ValidationError
7-
from odoo.fields import Domain
88
from odoo.tools.safe_eval import safe_eval
99

1010

@@ -24,8 +24,63 @@ def _validate_domain(self):
2424
if record.domain:
2525
try:
2626
domain = safe_eval(record.domain)
27-
# Normalize will raise an error if the domain is invalid
28-
expression.normalize_domain(domain)
27+
if isinstance(domain, list):
28+
# Validate the domain structure manually
29+
# Check for well-formed domain expressions
30+
self._validate_domain_structure(domain)
2931
return True
30-
except Exception as e:
31-
raise ValidationError(f"Invalid domain: {str(e)}") from e
32+
except (Exception, TypeError, ValueError) as e:
33+
raise ValidationError(
34+
self.env._("Invalid domain: %s", str(e))
35+
) from e
36+
37+
@staticmethod
38+
def _validate_domain_structure(domain):
39+
"""Validate the structure of a domain without executing it."""
40+
if not isinstance(domain, list):
41+
raise ValueError("Domain must be a list")
42+
43+
# Empty domain is valid
44+
if not domain:
45+
return True
46+
47+
# In Odoo domains, logical operators ('|', '&', '!') must be at the beginning
48+
# They should precede the conditions they operate on, not appear in the middle
49+
for i, element in enumerate(domain):
50+
if isinstance(element, str) and element in ["|", "&", "!"]:
51+
# If operator is not at position 0, it might be incorrectly placed
52+
# unless it directly follows a condition (which would be wrong)
53+
# Check if this is in an invalid position (middle after conditions)
54+
# A logical operator should only appear at the beginning
55+
if i > 0:
56+
prev_element = domain[i - 1]
57+
# If the operator comes after a condition (list/tuple), invalid
58+
if isinstance(prev_element, list | tuple):
59+
raise ValueError(
60+
f"'{element}' at pos {i} not properly positioned."
61+
f"Operators must precede expressions."
62+
)
63+
64+
elif isinstance(element, list | tuple):
65+
# Condition like ['field', 'operator', 'value']
66+
if len(element) < 2 or len(element) > 3:
67+
raise ValueError(
68+
f"Domain cond at pos {i} must have 2-3 elem, got {len(element)}"
69+
)
70+
71+
field, operator = element[0], element[1]
72+
if not isinstance(field, str):
73+
raise ValueError(
74+
f"Field name at position {i} must be string, got {type(field)}"
75+
)
76+
77+
if not isinstance(operator, str):
78+
raise ValueError(
79+
f"Operator at position {i} must be string, got {type(operator)}"
80+
)
81+
82+
else:
83+
raise ValueError(
84+
f"Domain elem must be operator/condition list, got {type(element)}"
85+
)
86+
return True

website_attribute_set/tests/test_website_attr_set.py

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,78 @@ class TestAttributeSetSearchable(BuildViewCase):
1313
def setUpClass(cls):
1414
super().setUpClass()
1515
cls.product_model = cls.env.ref("product.model_product_template")
16-
cls.attr_set_1 = cls.env.ref("product_attribute_set.computer_attribute_set")
17-
cls.group_1 = cls.env.ref(
18-
"product_attribute_set.computer_technical_attribute_group"
16+
17+
# Create required attribute records directly for test compatibility
18+
cls.group_1 = cls.env["attribute.group"].create(
19+
{
20+
"name": "Technical Group",
21+
"model_id": cls.product_model.id,
22+
"sequence": 1,
23+
}
24+
)
25+
26+
cls.attr_set_1 = cls.env["attribute.set"].create(
27+
{
28+
"name": "Computer Attribute Set",
29+
"model_id": cls.product_model.id,
30+
}
31+
)
32+
33+
cls.attr_1 = cls.env["attribute.attribute"].create(
34+
{
35+
"nature": "custom",
36+
"field_description": "Processor",
37+
"name": "x_processor",
38+
"attribute_type": "select",
39+
"attribute_group_id": cls.group_1.id,
40+
"attribute_set_ids": [(4, cls.attr_set_1.id)],
41+
"model_id": cls.product_model.id,
42+
}
1943
)
20-
cls.attr_1 = cls.env.ref("product_attribute_set.computer_processor_attribute")
21-
cls.attr_2 = cls.env.ref(
22-
"product_attribute_set.computer_tech_description_attribute"
44+
45+
cls.attr_2 = cls.env["attribute.attribute"].create(
46+
{
47+
"nature": "custom",
48+
"field_description": "Technical Description",
49+
"name": "x_technical_description",
50+
"attribute_type": "text",
51+
"attribute_group_id": cls.group_1.id,
52+
"attribute_set_ids": [(4, cls.attr_set_1.id)],
53+
"model_id": cls.product_model.id,
54+
}
2355
)
2456
cls.attr_3 = cls.env["attribute.attribute"].create(
2557
{
2658
"nature": "custom",
2759
"field_description": "Hard Disk",
2860
"name": "x_hard_disk",
2961
"attribute_type": "select",
30-
"attribute_group_id": cls.env.ref(
31-
"product_attribute_set.computer_technical_attribute_group"
32-
).id,
62+
"attribute_group_id": cls.group_1.id, # Using group created directly
3363
"attribute_set_ids": [
3464
(
3565
4,
36-
cls.env.ref("product_attribute_set.computer_attribute_set").id,
66+
cls.attr_set_1.id, # Using the set we created directly
3767
0,
3868
)
3969
],
40-
"model_id": cls.env.ref("product.model_product_template").id,
70+
"model_id": cls.product_model.id, # Using the model we already have
4171
"relation_model_id": cls.product_model.id,
4272
}
4373
)
74+
# Create an attribute with domain capabilities for domain validation test
75+
cls.attr_select = cls.env["attribute.attribute"].create(
76+
{
77+
"nature": "custom",
78+
"field_description": "Test Domain Attribute",
79+
"name": "x_test_domain_attr",
80+
"attribute_type": "select",
81+
"attribute_group_id": cls.group_1.id,
82+
"attribute_set_ids": [(4, cls.attr_set_1.id)],
83+
"model_id": cls.product_model.id,
84+
"relation_model_id": cls.product_model.id,
85+
}
86+
)
87+
4488
cls.product_1 = cls.env["product.template"].create(
4589
{
4690
"name": "Test Smart Product",
@@ -68,9 +112,14 @@ def test_get_extra_attributes(self):
68112
extra_attrs = self.product_1.get_extra_attributes()
69113
self.assertFalse(extra_attrs)
70114
# Assert the method returns only the attributes that are visible in e-com app
71-
self.product_1.x_processor = self.env.ref(
72-
"product_attribute_set.computer_processor_attribute_option_1"
115+
# Create a test option for the processor attribute
116+
test_option = self.env["attribute.option"].create(
117+
{
118+
"name": "Intel i7",
119+
"attribute_id": self.attr_1.id,
120+
}
73121
)
122+
self.product_1.x_processor = test_option
74123
self.product_1.write({"x_technical_description": "Fast processor"})
75124
self.attr_1.write({"e_com_visibility": True})
76125
extra_attrs = self.product_1.get_extra_attributes()

website_attribute_set/views/templates.xml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@
1414
name="t-if"
1515
>attributes or all_tags or additional_attributes</attribute>
1616
</xpath>
17+
<!--
1718
<xpath expr="//div[@t-if='attrib_values or tags']" position="attributes">
1819
<attribute
1920
name="t-if"
2021
>attrib_values or tags or additional_attrib_set</attribute>
2122
</xpath>
2223
<xpath expr="//form" position="inside">
23-
<!-- will create js_additional_attributes class and its related JS -->
24+
will create js_additional_attributes class and its related JS
2425
<t t-foreach="additional_attributes" t-as="attribute_dict">
2526
<t t-cache="attribute_dict,additional_attrib_set">
2627
<t t-set="attribute" t-value="attribute_dict.get('attribute')" />
@@ -40,7 +41,7 @@
4041
class=""
4142
>
4243
<t t-if="attribute.attribute_type == 'select'">
43-
<!-- will create css_additional_attribute_select class and its related JS -->
44+
will create css_additional_attribute_select class and its related JS
4445
<select
4546
class="form-select mb-2"
4647
name="additional_attribute_value"
@@ -64,7 +65,7 @@
6465
</select>
6566
</t>
6667
<t t-elif="attribute.attribute_type == 'multiselect'">
67-
<!-- will create css_additional_attribute_select class and its related JS -->
68+
will create css_additional_attribute_select class and its related JS
6869
<select
6970
class="form-select mb-2"
7071
name="additional_attribute_value"
@@ -110,7 +111,7 @@
110111
</t>
111112
</div>
112113
<t t-else="">
113-
<!-- will create css_additional_attribute_select class and its related JS -->
114+
will create css_additional_attribute_select class and its related JS
114115
<select
115116
class="form-select mb-2"
116117
name="additional_attribute_value"
@@ -132,5 +133,6 @@
132133
</t>
133134
</t>
134135
</xpath>
136+
-->
135137
</template>
136138
</odoo>

website_attribute_set/views/website_sale_comparison_template.xml

Lines changed: 2 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,139 +1,7 @@
11
<?xml version="1.0" encoding="utf-8" ?>
22
<odoo>
3-
<template
4-
id="product_compare_with_additional_attributes"
5-
inherit_id="website_sale_comparison.product_compare"
6-
active="True"
7-
name="Comparator Page"
8-
>
9-
<xpath expr="//table[@id='o_comparelist_table']" position="after">
10-
<table
11-
class="table table-bordered table-hover text-center mt16 table-comparator"
12-
>
13-
<t
14-
t-set="attrib_groups"
15-
t-value="products.sudo()._prepare_additional_attributes_for_display()"
16-
/>
17-
<tbody>
18-
<t t-foreach="attrib_groups" t-as="group">
19-
<t t-if="len(attrib_groups) &gt; 1">
20-
<tr
21-
class="clickable"
22-
data-bs-toggle="collapse"
23-
t-att-data-bs-target="'.o_ws_group_%d' % group.id"
24-
>
25-
<th
26-
class="text-start"
27-
t-att-colspan="len(products) + 1"
28-
>
29-
<i
30-
class="fa fa-chevron-circle-down o_product_comparison_collpase"
31-
role="img"
32-
aria-label="Collapse"
33-
title="Collapse"
34-
/>
35-
<span t-if="group" t-field="group.name" />
36-
<span t-else="">Uncategorized</span>
37-
</th>
38-
</tr>
39-
</t>
40-
<tr
41-
t-foreach="attrib_groups[group]"
42-
t-as="attribute"
43-
t-att-class="'collapse show o_ws_group_%d' % group.id"
44-
>
45-
<td>
46-
<span t-field="attribute.field_description" />
47-
</td>
48-
<td
49-
t-foreach="attrib_groups[group][attribute]"
50-
t-as="product"
51-
>
52-
<t
53-
t-set="values"
54-
t-value="attrib_groups[group][attribute][product]"
55-
/>
56-
<t t-if="attribute.attribute_type == 'multiselect'">
57-
<t t-foreach="values" t-as="value">
58-
<span t-out="value" />
59-
<t t-if="not value_last">, </t>
60-
</t>
61-
</t>
62-
<t
63-
t-if="attribute.attribute_type in ['boolean', 'char', 'text', 'date', 'datetime', 'integer', 'float']"
64-
>
65-
<t t-if="attribute.attribute_type == 'boolean'">
66-
<span t-out="'Yes'" />
67-
</t>
68-
<t t-elif="attribute.attribute_type == 'integer'">
69-
<span
70-
t-out="values"
71-
t-options='{"widget": "integer"}'
72-
/>
73-
</t>
74-
<t t-elif="attribute.attribute_type == 'float'">
75-
<span t-out="values" />
76-
</t>
77-
<t t-if="attribute.attribute_type == 'date'">
78-
<span
79-
t-out="values"
80-
t-options='{"widget": "date"}'
81-
/>
82-
</t>
83-
<t t-if="attribute.attribute_type == 'datetime'">
84-
<t
85-
t-set="tz_value"
86-
t-value="request.env.user.tz or 'UTC'"
87-
/>
88-
<span
89-
t-out="values"
90-
t-options='{"widget": "datetime", "tz_name": tz_value, "format": "short"}'
91-
/>
92-
</t>
93-
<t
94-
t-if="attribute.attribute_type in ['char', 'text', 'select']"
95-
>
96-
<span t-out="values" />
97-
</t>
98-
</t>
99-
</td>
100-
</tr>
101-
</t>
102-
<!-- reference section -->
103-
<tr
104-
class="clickable"
105-
data-bs-toggle="collapse"
106-
t-att-data-bs-target="'.o_ws_group_default_code'"
107-
>
108-
<th class="text-start" t-att-colspan="len(products) + 1">
109-
<i
110-
class="fa fa-chevron-circle-down o_product_comparison_collpase"
111-
role="img"
112-
aria-label="Collapse"
113-
title="Collapse"
114-
/>
115-
<span>Reference Codes</span>
116-
</th>
117-
</tr>
118-
<tr
119-
class="collapse show o_ws_group_default_code"
120-
data-bs-toggle="collapse"
121-
>
122-
<td>
123-
<span>Code</span>
124-
</td>
125-
<t t-foreach="products" t-as="product">
126-
<td>
127-
<t t-if="product.default_code">
128-
<span t-field="product.default_code" />
129-
</t>
130-
</td>
131-
</t>
132-
</tr>
133-
</tbody>
134-
</table>
135-
</xpath>
136-
</template>
3+
<!-- Template temporarily disabled due to Odoo 19 xpath compatibility issue -->
4+
<!-- The original xpath //table[@id='o_comparelist_table'] does not exist in Odoo 19 -->
1375

1386
<template
1397
id="custom_specifications_table"

0 commit comments

Comments
 (0)