Merge pull request #30091 from marination/item-group-filters-listing

feat: Include child item group products in Item Group Page & cleanup
This commit is contained in:
Marica 2022-03-09 16:46:01 +05:30 committed by GitHub
commit 2661cb2c14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 118 additions and 47 deletions

View File

@ -1,5 +1,6 @@
from frappe import _ from frappe import _
def get_data(): def get_data():
return { return {
'non_standard_fieldnames': { 'non_standard_fieldnames': {

View File

@ -48,7 +48,6 @@ def get_product_filter_data(query_args=None):
sub_categories = [] sub_categories = []
if item_group: if item_group:
field_filters['item_group'] = item_group
sub_categories = get_child_groups_for_website(item_group, immediate=True) sub_categories = get_child_groups_for_website(item_group, immediate=True)
engine = ProductQuery() engine = ProductQuery()

View File

@ -14,6 +14,8 @@ class ProductFiltersBuilder:
self.item_group = item_group self.item_group = item_group
def get_field_filters(self): def get_field_filters(self):
from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_website
if not self.item_group and not self.doc.enable_field_filters: if not self.item_group and not self.doc.enable_field_filters:
return return
@ -25,18 +27,26 @@ class ProductFiltersBuilder:
fields = [item_meta.get_field(field) for field in filter_fields if item_meta.has_field(field)] fields = [item_meta.get_field(field) for field in filter_fields if item_meta.has_field(field)]
for df in fields: for df in fields:
item_filters, item_or_filters = {}, [] item_filters, item_or_filters = {"published_in_website": 1}, []
link_doctype_values = self.get_filtered_link_doctype_records(df) link_doctype_values = self.get_filtered_link_doctype_records(df)
if df.fieldtype == "Link": if df.fieldtype == "Link":
if self.item_group: if self.item_group:
item_or_filters.extend([ include_child = frappe.db.get_value("Item Group", self.item_group, "include_descendants")
["item_group", "=", self.item_group], if include_child:
["Website Item Group", "item_group", "=", self.item_group] # consider website item groups include_groups = get_child_groups_for_website(self.item_group, include_self=True)
]) include_groups = [x.name for x in include_groups]
item_or_filters.extend([
["item_group", "in", include_groups],
["Website Item Group", "item_group", "=", self.item_group] # consider website item groups
])
else:
item_or_filters.extend([
["item_group", "=", self.item_group],
["Website Item Group", "item_group", "=", self.item_group] # consider website item groups
])
# Get link field values attached to published items # Get link field values attached to published items
item_filters['published_in_website'] = 1
item_values = frappe.get_all( item_values = frappe.get_all(
"Item", "Item",
fields=[df.fieldname], fields=[df.fieldname],

View File

@ -46,10 +46,10 @@ class ProductQuery:
self.filter_with_discount = bool(fields.get("discount")) self.filter_with_discount = bool(fields.get("discount"))
result, discount_list, website_item_groups, cart_items, count = [], [], [], [], 0 result, discount_list, website_item_groups, cart_items, count = [], [], [], [], 0
website_item_groups = self.get_website_item_group_results(item_group, website_item_groups)
if fields: if fields:
self.build_fields_filters(fields) self.build_fields_filters(fields)
if item_group:
self.build_item_group_filters(item_group)
if search_term: if search_term:
self.build_search_filters(search_term) self.build_search_filters(search_term)
if self.settings.hide_variants: if self.settings.hide_variants:
@ -61,8 +61,6 @@ class ProductQuery:
else: else:
result, count = self.query_items(start=start) result, count = self.query_items(start=start)
result = self.combine_web_item_group_results(item_group, result, website_item_groups)
# sort combined results by ranking # sort combined results by ranking
result = sorted(result, key=lambda x: x.get("ranking"), reverse=True) result = sorted(result, key=lambda x: x.get("ranking"), reverse=True)
@ -167,6 +165,25 @@ class ProductQuery:
# `=` will be faster than `IN` for most cases # `=` will be faster than `IN` for most cases
self.filters.append([field, "=", values]) self.filters.append([field, "=", values])
def build_item_group_filters(self, item_group):
"Add filters for Item group page and include Website Item Groups."
from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_website
item_group_filters = []
item_group_filters.append(["Website Item", "item_group", "=", item_group])
# Consider Website Item Groups
item_group_filters.append(["Website Item Group", "item_group", "=", item_group])
if frappe.db.get_value("Item Group", item_group, "include_descendants"):
# include child item group's items as well
# eg. Group Node A, will show items of child 1 and child 2 as well
# on it's web page
include_groups = get_child_groups_for_website(item_group, include_self=True)
include_groups = [x.name for x in include_groups]
item_group_filters.append(["Website Item", "item_group", "in", include_groups])
self.or_filters.extend(item_group_filters)
def build_search_filters(self, search_term): def build_search_filters(self, search_term):
"""Query search term in specified fields """Query search term in specified fields
@ -190,19 +207,6 @@ class ProductQuery:
for field in search_fields: for field in search_fields:
self.or_filters.append([field, "like", search]) self.or_filters.append([field, "like", search])
def get_website_item_group_results(self, item_group, website_item_groups):
"""Get Web Items for Item Group Page via Website Item Groups."""
if item_group:
website_item_groups = frappe.db.get_all(
"Website Item",
fields=self.fields + ["`tabWebsite Item Group`.parent as wig_parent"],
filters=[
["Website Item Group", "item_group", "=", item_group],
["published", "=", 1]
]
)
return website_item_groups
def add_display_details(self, result, discount_list, cart_items): def add_display_details(self, result, discount_list, cart_items):
"""Add price and availability details in result.""" """Add price and availability details in result."""
for item in result: for item in result:
@ -278,16 +282,6 @@ class ProductQuery:
return [] return []
def combine_web_item_group_results(self, item_group, result, website_item_groups):
"""Combine results with context of website item groups into item results."""
if item_group and website_item_groups:
items_list = {row.name for row in result}
for row in website_item_groups:
if row.wig_parent not in items_list:
result.append(row)
return result
def filter_results_by_discount(self, fields, result): def filter_results_by_discount(self, fields, result):
if fields and fields.get("discount"): if fields and fields.get("discount"):
discount_percent = frappe.utils.flt(fields["discount"][0]) discount_percent = frappe.utils.flt(fields["discount"][0])

View File

@ -13,8 +13,7 @@ test_dependencies = ["Item", "Item Group"]
class TestItemGroupProductDataEngine(unittest.TestCase): class TestItemGroupProductDataEngine(unittest.TestCase):
"Test Products & Sub-Category Querying for Product Listing on Item Group Page." "Test Products & Sub-Category Querying for Product Listing on Item Group Page."
@classmethod def setUp(self):
def setUpClass(cls):
item_codes = [ item_codes = [
("Test Mobile A", "_Test Item Group B"), ("Test Mobile A", "_Test Item Group B"),
("Test Mobile B", "_Test Item Group B"), ("Test Mobile B", "_Test Item Group B"),
@ -28,8 +27,10 @@ class TestItemGroupProductDataEngine(unittest.TestCase):
if not frappe.db.exists("Website Item", {"item_code": item_code}): if not frappe.db.exists("Website Item", {"item_code": item_code}):
create_regular_web_item(item_code, item_args=item_args) create_regular_web_item(item_code, item_args=item_args)
@classmethod frappe.db.set_value("Item Group", "_Test Item Group B - 1", "show_in_website", 1)
def tearDownClass(cls): frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 1)
def tearDown(self):
frappe.db.rollback() frappe.db.rollback()
def test_product_listing_in_item_group(self): def test_product_listing_in_item_group(self):
@ -87,7 +88,6 @@ class TestItemGroupProductDataEngine(unittest.TestCase):
def test_item_group_with_sub_groups(self): def test_item_group_with_sub_groups(self):
"Test Valid Sub Item Groups in Item Group Page." "Test Valid Sub Item Groups in Item Group Page."
frappe.db.set_value("Item Group", "_Test Item Group B - 1", "show_in_website", 1)
frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 0) frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 0)
result = get_product_filter_data(query_args={ result = get_product_filter_data(query_args={
@ -114,4 +114,45 @@ class TestItemGroupProductDataEngine(unittest.TestCase):
# check if child group is fetched if shown in website # check if child group is fetched if shown in website
self.assertIn("_Test Item Group B - 1", child_groups) self.assertIn("_Test Item Group B - 1", child_groups)
self.assertIn("_Test Item Group B - 2", child_groups) self.assertIn("_Test Item Group B - 2", child_groups)
def test_item_group_page_with_descendants_included(self):
"""
Test if 'include_descendants' pulls Items belonging to descendant Item Groups (Level 2 & 3).
> _Test Item Group B [Level 1]
> _Test Item Group B - 1 [Level 2]
> _Test Item Group B - 1 - 1 [Level 3]
"""
frappe.get_doc({ # create Level 3 nested child group
"doctype": "Item Group",
"is_group": 1,
"item_group_name": "_Test Item Group B - 1 - 1",
"parent_item_group": "_Test Item Group B - 1"
}).insert()
create_regular_web_item( # create an item belonging to level 3 item group
"Test Mobile F",
item_args={"item_group": "_Test Item Group B - 1 - 1"}
)
frappe.db.set_value("Item Group", "_Test Item Group B - 1 - 1", "show_in_website", 1)
# enable 'include descendants' in Level 1
frappe.db.set_value("Item Group", "_Test Item Group B", "include_descendants", 1)
result = get_product_filter_data(query_args={
"field_filters": {},
"attribute_filters": {},
"start": 0,
"item_group": "_Test Item Group B"
})
items = result.get("items")
item_codes = [item.get("item_code") for item in items]
# check if all sub groups' items are pulled
self.assertEqual(len(items), 6)
self.assertIn("Test Mobile A", item_codes)
self.assertIn("Test Mobile C", item_codes)
self.assertIn("Test Mobile E", item_codes)
self.assertIn("Test Mobile F", item_codes)

View File

@ -20,12 +20,14 @@
"sec_break_taxes", "sec_break_taxes",
"taxes", "taxes",
"sb9", "sb9",
"show_in_website",
"route", "route",
"weightage",
"slideshow",
"website_title", "website_title",
"description", "description",
"show_in_website",
"include_descendants",
"column_break_16",
"weightage",
"slideshow",
"website_specifications", "website_specifications",
"website_filters_section", "website_filters_section",
"filter_fields", "filter_fields",
@ -111,7 +113,7 @@
}, },
{ {
"default": "0", "default": "0",
"description": "Check this if you want to show in website", "description": "Make Item Group visible in website",
"fieldname": "show_in_website", "fieldname": "show_in_website",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Show in Website" "label": "Show in Website"
@ -124,6 +126,7 @@
"unique": 1 "unique": 1
}, },
{ {
"depends_on": "show_in_website",
"fieldname": "weightage", "fieldname": "weightage",
"fieldtype": "Int", "fieldtype": "Int",
"label": "Weightage" "label": "Weightage"
@ -186,6 +189,8 @@
"report_hide": 1 "report_hide": 1
}, },
{ {
"collapsible": 1,
"depends_on": "show_in_website",
"fieldname": "website_filters_section", "fieldname": "website_filters_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Website Filters" "label": "Website Filters"
@ -203,9 +208,22 @@
"options": "Website Attribute" "options": "Website Attribute"
}, },
{ {
"depends_on": "show_in_website",
"fieldname": "website_title", "fieldname": "website_title",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Title" "label": "Title"
},
{
"fieldname": "column_break_16",
"fieldtype": "Column Break"
},
{
"default": "0",
"depends_on": "show_in_website",
"description": "Include Website Items belonging to child Item Groups",
"fieldname": "include_descendants",
"fieldtype": "Check",
"label": "Include Descendants"
} }
], ],
"icon": "fa fa-sitemap", "icon": "fa fa-sitemap",
@ -214,11 +232,12 @@
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"max_attachments": 3, "max_attachments": 3,
"modified": "2021-02-18 13:40:30.049650", "modified": "2022-03-09 12:27:11.055782",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Item Group", "name": "Item Group",
"name_case": "Title Case", "name_case": "Title Case",
"naming_rule": "By fieldname",
"nsm_parent_field": "parent_item_group", "nsm_parent_field": "parent_item_group",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
@ -285,5 +304,6 @@
"search_fields": "parent_item_group", "search_fields": "parent_item_group",
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC" "sort_order": "DESC",
"states": []
} }

View File

@ -111,7 +111,7 @@ class ItemGroup(NestedSet, WebsiteGenerator):
from erpnext.stock.doctype.item.item import validate_item_default_company_links from erpnext.stock.doctype.item.item import validate_item_default_company_links
validate_item_default_company_links(self.item_group_defaults) validate_item_default_company_links(self.item_group_defaults)
def get_child_groups_for_website(item_group_name, immediate=False): def get_child_groups_for_website(item_group_name, immediate=False, include_self=False):
"""Returns child item groups *excluding* passed group.""" """Returns child item groups *excluding* passed group."""
item_group = frappe.get_cached_value("Item Group", item_group_name, ["lft", "rgt"], as_dict=1) item_group = frappe.get_cached_value("Item Group", item_group_name, ["lft", "rgt"], as_dict=1)
filters = { filters = {
@ -123,6 +123,12 @@ def get_child_groups_for_website(item_group_name, immediate=False):
if immediate: if immediate:
filters["parent_item_group"] = item_group_name filters["parent_item_group"] = item_group_name
if include_self:
filters.update({
"lft": [">=", item_group.lft],
"rgt": ["<=", item_group.rgt]
})
return frappe.get_all( return frappe.get_all(
"Item Group", "Item Group",
filters=filters, filters=filters,