fix: Discount Filters and Web templates
- Fixed discount filters (didn’t work after js render change)
- Fix Item Card Group template height and style
- Add placeholder to missing images in Product Category Cards template
- Code cleanup
diff --git a/erpnext/e_commerce/product_configurator/utils.py b/erpnext/e_commerce/product_configurator/utils.py
index 9faaa5d..5ea32f9 100644
--- a/erpnext/e_commerce/product_configurator/utils.py
+++ b/erpnext/e_commerce/product_configurator/utils.py
@@ -115,7 +115,7 @@
next_attribute = attribute
break
- valid_options_for_attributes = frappe._dict({})
+ valid_options_for_attributes = frappe._dict()
for a in attribute_list:
valid_options_for_attributes[a] = set()
diff --git a/erpnext/e_commerce/product_grid.js b/erpnext/e_commerce/product_grid.js
index 6f7b9cc..a716efa 100644
--- a/erpnext/e_commerce/product_grid.js
+++ b/erpnext/e_commerce/product_grid.js
@@ -12,6 +12,7 @@
this.products_section.addClass("hidden");
}
+ this.products_section.empty();
this.make();
}
diff --git a/erpnext/e_commerce/product_list.js b/erpnext/e_commerce/product_list.js
index 2836c3a..fa35c7e 100644
--- a/erpnext/e_commerce/product_list.js
+++ b/erpnext/e_commerce/product_list.js
@@ -12,6 +12,7 @@
this.products_section.addClass("hidden");
}
+ this.products_section.empty();
this.make();
}
diff --git a/erpnext/e_commerce/product_query.py b/erpnext/e_commerce/product_query.py
index 608e9d9..ed3c18b 100644
--- a/erpnext/e_commerce/product_query.py
+++ b/erpnext/e_commerce/product_query.py
@@ -106,7 +106,7 @@
def query_items(self, conditions, or_conditions, substitutions, start=0, with_attributes=False):
"""Build a query to fetch Website Items based on field filters."""
- self.query_fields = (", ").join(self.fields)
+ self.query_fields = ", ".join(self.fields)
attribute_table = ", `tabItem Variant Attribute` iva" if with_attributes else ""
@@ -119,9 +119,9 @@
{conditions}
{or_conditions}
limit {self.page_length} offset {start}
- """,
- tuple(substitutions),
- as_dict=1)
+ """,
+ tuple(substitutions),
+ as_dict=1)
def query_items_with_attributes(self, attributes, start=0):
"""Build a query to fetch Website Items based on field & attribute filters."""
@@ -147,7 +147,7 @@
all_items.append(set(items_dict.keys()))
- result = [items_dict.get(item) for item in list(set.intersection(*all_items))]
+ result = [items_dict.get(item) for item in set.intersection(*all_items)]
return result
def build_fields_filters(self, filters):
@@ -192,11 +192,8 @@
# Join the meta fields and default fields set
search_fields = default_fields.union(meta_fields)
- try:
- if frappe.db.count('Item', cache=True) > 50000:
- search_fields.remove('description')
- except KeyError:
- pass
+ if frappe.db.count('Item', cache=True) > 50000:
+ search_fields.discard('description')
# Build or filters for query
search = '%{}%'.format(search_term)
diff --git a/erpnext/e_commerce/product_view.js b/erpnext/e_commerce/product_view.js
index 372e722..a64d55f 100644
--- a/erpnext/e_commerce/product_view.js
+++ b/erpnext/e_commerce/product_view.js
@@ -6,8 +6,11 @@
*/
constructor(options) {
Object.assign(this, options);
- this.preference = "List View";
+ this.preference = this.view_type;
+ this.make();
+ }
+ make() {
this.products_section.empty();
this.prepare_view_toggler();
this.get_item_filter_data();
@@ -22,12 +25,12 @@
}
get_item_filter_data() {
- // Get and render all Items related components
+ // Get and render all Product related views
let me = this;
let args = this.get_query_filters();
- $('#list').prop('disabled', true);
- $('#image-view').prop('disabled', true);
+ this.disable_view_toggler(true);
+
frappe.call({
method: 'erpnext.e_commerce.doctype.website_item.website_item.get_product_filter_data',
args: args,
@@ -55,14 +58,20 @@
me.render_no_products_section();
}
- $('#list').prop('disabled', false);
- $('#image-view').prop('disabled', false);
+ me.disable_view_toggler(false);
}
});
}
+ disable_view_toggler(disable=false) {
+ $('#list').prop('disabled', disable);
+ $('#image-view').prop('disabled', disable);
+ }
+
render_filters(filter_data) {
this.get_discount_filter_html(filter_data.discount_filters);
+ this.bind_filters();
+ this.restore_filters_state();
}
render_grid_view(items, settings) {
@@ -226,9 +235,11 @@
html += `
<div class="checkbox">
<label data-value="${ filter[0] }">
- <input type="radio" class="product-filter discount-filter"
+ <input type="radio"
+ class="product-filter discount-filter"
name="discount" id="${ filter[0] }"
- data-filter-name="discount" data-filter-value="${ filter[0] }"
+ data-filter-name="discount"
+ data-filter-value="${ filter[0] }"
>
<span class="label-area" for="${ filter[0] }">
${ filter[1] }
@@ -243,6 +254,97 @@
}
}
+ bind_filters() {
+ let me = this;
+ this.field_filters = {};
+ this.attribute_filters = {};
+
+ $('.product-filter').on('change', (e) => {
+ const $checkbox = $(e.target);
+ const is_checked = $checkbox.is(':checked');
+
+ if ($checkbox.is('.attribute-filter')) {
+ const {
+ attributeName: attribute_name,
+ attributeValue: attribute_value
+ } = $checkbox.data();
+
+ if (is_checked) {
+ this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name] || [];
+ this.attribute_filters[attribute_name].push(attribute_value);
+ } else {
+ this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name] || [];
+ this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name].filter(v => v !== attribute_value);
+ }
+
+ if (this.attribute_filters[attribute_name].length === 0) {
+ delete this.attribute_filters[attribute_name];
+ }
+ } else if ($checkbox.is('.field-filter') || $checkbox.is('.discount-filter')) {
+ const {
+ filterName: filter_name,
+ filterValue: filter_value
+ } = $checkbox.data();
+
+ if ($checkbox.is('.discount-filter')) {
+ // clear previous discount filter to accomodate new
+ delete this.field_filters["discount"];
+ }
+ if (is_checked) {
+ this.field_filters[filter_name] = this.field_filters[filter_name] || [];
+ this.field_filters[filter_name].push(filter_value);
+ } else {
+ this.field_filters[filter_name] = this.field_filters[filter_name] || [];
+ this.field_filters[filter_name] = this.field_filters[filter_name].filter(v => v !== filter_value);
+ }
+
+ if (this.field_filters[filter_name].length === 0) {
+ delete this.field_filters[filter_name];
+ }
+ }
+
+ let route_params = frappe.utils.get_query_params();
+ const query_string = get_query_string({
+ start: if_key_exists(route_params.start) || 0,
+ field_filters: JSON.stringify(if_key_exists(this.field_filters)),
+ attribute_filters: JSON.stringify(if_key_exists(this.attribute_filters)),
+ });
+ window.history.pushState('filters', '', `${location.pathname}?` + query_string);
+
+ $('.page_content input').prop('disabled', true);
+ me.make();
+ $('.page_content input').prop('disabled', false);
+ });
+ }
+
+ restore_filters_state() {
+ const filters = frappe.utils.get_query_params();
+ let {field_filters, attribute_filters} = filters;
+
+ if (field_filters) {
+ field_filters = JSON.parse(field_filters);
+ for (let fieldname in field_filters) {
+ const values = field_filters[fieldname];
+ const selector = values.map(value => {
+ return `input[data-filter-name="${fieldname}"][data-filter-value="${value}"]`;
+ }).join(',');
+ $(selector).prop('checked', true);
+ }
+ this.field_filters = field_filters;
+ }
+ if (attribute_filters) {
+ attribute_filters = JSON.parse(attribute_filters);
+ for (let attribute in attribute_filters) {
+ const values = attribute_filters[attribute];
+ const selector = values.map(value => {
+ return `input[data-attribute-name="${attribute}"][data-attribute-value="${value}"]`;
+ }).join(',');
+ $(selector).prop('checked', true);
+ }
+ this.attribute_filters = attribute_filters;
+ }
+ }
+
render_no_products_section() {
this.products_section.append(`
<br><br><br>
@@ -278,4 +380,26 @@
$("#product-listing").prepend(sub_group_html);
}
}
-};
\ No newline at end of file
+};
+
+function get_query_string(object) {
+ const url = new URLSearchParams();
+ for (let key in object) {
+ const value = object[key];
+ if (value) {
+ url.append(key, value);
+ }
+ }
+ return url.toString();
+}
+
+function if_key_exists(obj) {
+ let exists = false;
+ for (let key in obj) {
+ if (obj.hasOwnProperty(key) && obj[key]) {
+ exists = true;
+ break;
+ }
+ }
+ return exists ? obj : undefined;
+}
\ No newline at end of file
diff --git a/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.html b/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.html
index 06b76af..6d75a8b 100644
--- a/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.html
+++ b/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.html
@@ -6,8 +6,15 @@
}) -%}
<div class="card h-100">
{% if image %}
- <img class="card-img-top" src="{{ image }}" alt="{{ title }}">
+ <img class="card-img-top" src="{{ image }}" alt="{{ title }}" style="max-height: 200px;">
+ {% else %}
+ <div class="placeholder-div" style="max-height: 200px;">
+ <span class="placeholder">
+ {{ frappe.utils.get_abbr(title or '') }}
+ </span>
+ </div>
{% endif %}
+
<div class="card-body text-center text-muted small">
{{ title or '' }}
</div>
diff --git a/erpnext/patches/v13_0/create_website_items.py b/erpnext/patches/v13_0/create_website_items.py
index 2e1f305..78e9dda 100644
--- a/erpnext/patches/v13_0/create_website_items.py
+++ b/erpnext/patches/v13_0/create_website_items.py
@@ -54,12 +54,12 @@
for doctype in ("Website Item Group", "Item Website Specification"):
web_item, item = website_item.name, item.item_code
frappe.db.sql(f"""
- Update `tab{doctype}`
+ Update
+ `tab{doctype}`
set
parenttype = 'Website Item',
parent = '{web_item}'
where
parenttype = 'Item'
and parent = '{item}'
- """
- )
\ No newline at end of file
+ """)
\ No newline at end of file
diff --git a/erpnext/public/js/wishlist.js b/erpnext/public/js/wishlist.js
index 1c5bee6..8264772 100644
--- a/erpnext/public/js/wishlist.js
+++ b/erpnext/public/js/wishlist.js
@@ -84,8 +84,8 @@
const $wish_icon = $btn.find('.wish-icon');
let me = this;
- if(frappe.session.user==="Guest") {
- if(localStorage) {
+ if (frappe.session.user==="Guest") {
+ if (localStorage) {
localStorage.setItem("last_visited", window.location.pathname);
}
window.location.href = "/login";
@@ -137,7 +137,7 @@
failure_action: method to execute on failure,
async: make call asynchronously (true/false). */
if (frappe.session.user==="Guest") {
- if(localStorage) {
+ if (localStorage) {
localStorage.setItem("last_visited", window.location.pathname);
}
window.location.href = "/login";
diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss
index 33bd88f..df0d51c 100644
--- a/erpnext/public/scss/shopping_cart.scss
+++ b/erpnext/public/scss/shopping_cart.scss
@@ -782,3 +782,16 @@
#toggle-view {
float: right;
}
+
+.placeholder-div {
+ height:80%;
+ width: -webkit-fill-available;
+ padding: 50px;
+ text-align: center;
+ background-color: #F9FAFA;
+ border-top-left-radius: calc(0.75rem - 1px);
+ border-top-right-radius: calc(0.75rem - 1px);
+}
+.placeholder {
+ font-size: 72px;
+}
diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html
index aec23a2..e3511de 100644
--- a/erpnext/templates/includes/macros.html
+++ b/erpnext/templates/includes/macros.html
@@ -73,10 +73,10 @@
{% if is_featured %}
<div class="col-sm-{{ col_size*2 }} item-card">
- <div class="card featured-item {{ align_items_class }}">
+ <div class="card featured-item {{ align_items_class }}" style="height: 360px;">
{% if image %}
<div class="row no-gutters">
- <div class="col-md-6">
+ <div class="col-md-5 ml-4">
<img class="card-img" src="{{ image }}" alt="{{ title }}">
</div>
<div class="col-md-6">
@@ -92,7 +92,7 @@
</div>
{% else %}
<div class="col-sm-{{ col_size }} item-card">
- <div class="card {{ align_items_class }}">
+ <div class="card {{ align_items_class }}" style="height: 360px;">
{% if image %}
<div class="card-img-container">
<a href="/{{ item.route or '#' }}" style="text-decoration: none;">
@@ -119,19 +119,17 @@
'text-left': align == 'Left' or is_featured,
}) -%}
<div class="card-body {{ align_class }}" style="width:100%">
- <div style="margin-top: 16px; display: flex;">
+ <div class="mt-4">
<a href="/{{ item.route or '#' }}">
<div class="product-title">
{{ title or '' }}
- {% if item.in_stock %}
- <span class="indicator {{ item.in_stock }} card-indicator"></span>
- {% endif %}
</div>
</a>
</div>
{% if is_featured %}
- <div class="product-price">{{ item.formatted_price or '' }}</div>
- <div class="product-description ellipsis">{{ description or '' }}</div>
+ <div class="product-description ellipsis text-muted" style="white-space: normal;">
+ {{ description or '' }}
+ </div>
{% else %}
<div class="product-category">{{ item.item_group or '' }}</div>
{% endif %}
diff --git a/erpnext/templates/pages/customer_reviews.py b/erpnext/templates/pages/customer_reviews.py
index 3bb0142..b9c8a01 100644
--- a/erpnext/templates/pages/customer_reviews.py
+++ b/erpnext/templates/pages/customer_reviews.py
@@ -1,15 +1,13 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
-from __future__ import unicode_literals
-
-no_cache = 1
-
import frappe
from erpnext.e_commerce.doctype.item_review.item_review import get_item_reviews
def get_context(context):
+ context.no_cache = 1
context.full_page = True
context.reviews = None
+
if frappe.form_dict and frappe.form_dict.get("item_code"):
context.item_code = frappe.form_dict.get("item_code")
context.web_item = frappe.db.get_value("Website Item", {"item_code": context.item_code}, "name")
diff --git a/erpnext/templates/pages/wishlist.py b/erpnext/templates/pages/wishlist.py
index e534a23..9bbec33 100644
--- a/erpnext/templates/pages/wishlist.py
+++ b/erpnext/templates/pages/wishlist.py
@@ -1,9 +1,5 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
-from __future__ import unicode_literals
-
-no_cache = 1
-
import frappe
from erpnext.utilities.product import get_price
from erpnext.e_commerce.shopping_cart.cart import _set_price_list
@@ -32,6 +28,7 @@
context.items = items
context.settings = settings
+ context.no_cache = 1
def get_stock_availability(item_code, warehouse):
stock_qty = frappe.utils.flt(
@@ -42,7 +39,7 @@
},
"actual_qty")
)
- return True if stock_qty else False
+ return bool(stock_qty)
def get_wishlist_items():
if frappe.db.exists("Wishlist", frappe.session.user):
@@ -53,5 +50,5 @@
from
`tabWishlist Items`
where
- parent=%(user)s""" % {"user": frappe.db.escape(frappe.session.user)}, as_dict=1)
+ parent=%(user)s""", {"user": frappe.session.user}, as_dict=1)
return
\ No newline at end of file
diff --git a/erpnext/www/all-products/index.js b/erpnext/www/all-products/index.js
index acf42f2..336aa9a 100644
--- a/erpnext/www/all-products/index.js
+++ b/erpnext/www/all-products/index.js
@@ -5,86 +5,18 @@
let is_item_group_page = $(".item-group-content").data("item-group");
this.item_group = is_item_group_page || null;
- // Render Products and Discount Filters
+ let view_type = "List View";
+
+ // Render Product Views and setup Filters
frappe.require('assets/js/e-commerce.min.js', function() {
new erpnext.ProductView({
- view_type: "List",
+ view_type: view_type,
products_section: $('#product-listing'),
item_group: me.item_group
});
});
- this.bind_filters();
this.bind_card_actions();
- this.bind_search();
- this.restore_filters_state();
- }
-
- bind_filters() {
- let me = this;
- this.field_filters = {};
- this.attribute_filters = {};
-
- $('.product-filter').on('change', frappe.utils.debounce((e) => {
- const $checkbox = $(e.target);
- const is_checked = $checkbox.is(':checked');
-
- if ($checkbox.is('.attribute-filter')) {
- const {
- attributeName: attribute_name,
- attributeValue: attribute_value
- } = $checkbox.data();
-
- if (is_checked) {
- this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name] || [];
- this.attribute_filters[attribute_name].push(attribute_value);
- } else {
- this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name] || [];
- this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name].filter(v => v !== attribute_value);
- }
-
- if (this.attribute_filters[attribute_name].length === 0) {
- delete this.attribute_filters[attribute_name];
- }
- } else if ($checkbox.is('.field-filter') || $checkbox.is('.discount-filter')) {
- const {
- filterName: filter_name,
- filterValue: filter_value
- } = $checkbox.data();
-
- if ($checkbox.is('.discount-filter')) {
- // clear previous discount filter to accomodate new
- delete this.field_filters["discount"];
- }
- if (is_checked) {
- this.field_filters[filter_name] = this.field_filters[filter_name] || [];
- this.field_filters[filter_name].push(filter_value);
- } else {
- this.field_filters[filter_name] = this.field_filters[filter_name] || [];
- this.field_filters[filter_name] = this.field_filters[filter_name].filter(v => v !== filter_value);
- }
-
- if (this.field_filters[filter_name].length === 0) {
- delete this.field_filters[filter_name];
- }
- }
-
- const query_string = get_query_string({
- field_filters: JSON.stringify(if_key_exists(this.field_filters)),
- attribute_filters: JSON.stringify(if_key_exists(this.attribute_filters)),
- });
- window.history.pushState('filters', '', `${location.pathname}?` + query_string);
-
- $('.page_content input').prop('disabled', true);
- frappe.require('assets/js/e-commerce.min.js', function() {
- new erpnext.ProductView({
- view_type: "List",
- products_section: $('#product-listing'),
- item_group: me.item_group
- });
- $('.page_content input').prop('disabled', false);
- });
- }, 1000));
}
bind_card_actions() {
@@ -92,70 +24,20 @@
e_commerce.wishlist.bind_wishlist_action();
}
- bind_search() {
- $('input[type=search]').on('keydown', (e) => {
- if (e.keyCode === 13) {
- // Enter
- const value = e.target.value;
- if (value) {
- window.location.search = 'search=' + e.target.value;
- } else {
- window.location.search = '';
- }
- }
- });
- }
-
- restore_filters_state() {
- const filters = frappe.utils.get_query_params();
- let {field_filters, attribute_filters} = filters;
-
- if (field_filters) {
- field_filters = JSON.parse(field_filters);
- for (let fieldname in field_filters) {
- const values = field_filters[fieldname];
- const selector = values.map(value => {
- return `input[data-filter-name="${fieldname}"][data-filter-value="${value}"]`;
- }).join(',');
- $(selector).prop('checked', true);
- }
- this.field_filters = field_filters;
- }
- if (attribute_filters) {
- attribute_filters = JSON.parse(attribute_filters);
- for (let attribute in attribute_filters) {
- const values = attribute_filters[attribute];
- const selector = values.map(value => {
- return `input[data-attribute-name="${attribute}"][data-attribute-value="${value}"]`;
- }).join(',');
- $(selector).prop('checked', true);
- }
- this.attribute_filters = attribute_filters;
- }
- }
+ // bind_search() {
+ // $('input[type=search]').on('keydown', (e) => {
+ // if (e.keyCode === 13) {
+ // // Enter
+ // const value = e.target.value;
+ // if (value) {
+ // window.location.search = 'search=' + e.target.value;
+ // } else {
+ // window.location.search = '';
+ // }
+ // }
+ // });
+ // }
}
new ProductListing();
-
- function get_query_string(object) {
- const url = new URLSearchParams();
- for (let key in object) {
- const value = object[key];
- if (value) {
- url.append(key, value);
- }
- }
- return url.toString();
- }
-
- function if_key_exists(obj) {
- let exists = false;
- for (let key in obj) {
- if (obj.hasOwnProperty(key) && obj[key]) {
- exists = true;
- break;
- }
- }
- return exists ? obj : undefined;
- }
});
diff --git a/erpnext/www/shop-by-category/index.html b/erpnext/www/shop-by-category/index.html
index ac0b317..04d2d57 100644
--- a/erpnext/www/shop-by-category/index.html
+++ b/erpnext/www/shop-by-category/index.html
@@ -11,18 +11,6 @@
width: 300px !important;
margin: 30px !important;
}
- .placeholder-div {
- height:80%;
- width: -webkit-fill-available;
- padding: 50px;
- text-align: center;
- background-color: #F9FAFA;
- border-top-left-radius: calc(0.75rem - 1px);
- border-top-right-radius: calc(0.75rem - 1px);
- }
- .placeholder {
- font-size: 72px;
- }
</style>
{% endblock %}
diff --git a/erpnext/www/shop-by-category/index.py b/erpnext/www/shop-by-category/index.py
index c295335..700d5b2 100644
--- a/erpnext/www/shop-by-category/index.py
+++ b/erpnext/www/shop-by-category/index.py
@@ -4,7 +4,7 @@
sitemap = 1
def get_context(context):
- settings = frappe.get_doc("E Commerce Settings")
+ settings = frappe.get_cached_doc("E Commerce Settings")
context.categories_enabled = settings.enable_field_filters
if context.categories_enabled:
@@ -23,9 +23,9 @@
'rounded': 1,
'slider_name': "Categories"
}
- slideshow = frappe.get_doc("Website Slideshow", slideshow)
+ slideshow = frappe.get_cached_doc("Website Slideshow", slideshow)
slides = slideshow.get({"doctype": "Website Slideshow Item"})
- for index, slide in enumerate(slides):
+ for index, slide in enumerate(slides, start=1):
values[f"slide_{index + 1}_image"] = slide.image
values[f"slide_{index + 1}_title"] = slide.heading
values[f"slide_{index + 1}_subtitle"] = slide.description
@@ -41,7 +41,7 @@
}
categorical_data = get_category_records(categories)
- for index, tab in enumerate(categorical_data):
+ for index, tab in enumerate(categorical_data, start=1):
tab_values[f"tab_{index + 1}_title"] = frappe.unscrub(tab)
# pre-render cards for each tab
tab_values[f"tab_{index + 1}_content"] = frappe.render_template(
@@ -55,19 +55,24 @@
for category in categories:
if category == "item_group":
categorical_data["item_group"] = frappe.db.sql("""
- Select name, parent_item_group, is_group, image, route
- from `tabItem Group`
- where parent_item_group='All Item Groups'
- and show_in_website=1""", as_dict=1)
+ Select
+ name, parent_item_group, is_group, image, route
+ from
+ `tabItem Group`
+ where
+ parent_item_group = 'All Item Groups'
+ and show_in_website = 1
+ """,
+ as_dict=1)
else:
doctype = frappe.unscrub(category)
fields = ["name"]
if frappe.get_meta(doctype, cached=True).get_field("image"):
fields += ["image"]
- categorical_data[category] = frappe.db.sql("""
- Select {fields}
- from `tab{doctype}`""".format(doctype=doctype, fields=",".join(fields)), as_dict=1)
+ categorical_data[category] = frappe.db.sql(f"""
+ Select {",".join(fields)}
+ from `tab{doctype}`""", as_dict=1)
return categorical_data