chore: Drive E-commerce via Website Item

- Removed Shopping Cart Settings
- Portal fully driven via E Commerce Settings
- All Item listing querying will happen via ProductQuery engine only
- Product Listing via Website Items
- removed redundant code
- Moved all website logic from Item to Website Item
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 2c96749..c5b8b54 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -293,7 +293,7 @@
 		if not status:
 			return
 
-		shopping_cart_settings = frappe.get_doc("Shopping Cart Settings")
+		shopping_cart_settings = frappe.get_doc("E Commerce Settings")
 
 		if status in ["Authorized", "Completed"]:
 			redirect_to = None
@@ -443,7 +443,7 @@
 		return get_payment_gateway_account(args.get("payment_gateway_account"))
 
 	if args.order_type == "Shopping Cart":
-		payment_gateway_account = frappe.get_doc("Shopping Cart Settings").payment_gateway_account
+		payment_gateway_account = frappe.get_doc("E Commerce Settings").payment_gateway_account
 		return get_payment_gateway_account(payment_gateway_account)
 
 	gateway_account = get_payment_gateway_account({"is_default": 1})
diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.py b/erpnext/accounts/doctype/tax_rule/tax_rule.py
index 150498d..9a63dfe 100644
--- a/erpnext/accounts/doctype/tax_rule/tax_rule.py
+++ b/erpnext/accounts/doctype/tax_rule/tax_rule.py
@@ -102,7 +102,7 @@
 	def validate_use_for_shopping_cart(self):
 		'''If shopping cart is enabled and no tax rule exists for shopping cart, enable this one'''
 		if (not self.use_for_shopping_cart
-			and cint(frappe.db.get_single_value('Shopping Cart Settings', 'enabled'))
+			and cint(frappe.db.get_single_value('E Commerce Settings', 'enabled'))
 			and not frappe.db.get_value('Tax Rule', {'use_for_shopping_cart': 1, 'name': ['!=', self.name]})):
 
 			self.use_for_shopping_cart = 1
diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js
index d970f04..131a5e4 100644
--- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js
+++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js
@@ -1,13 +1,34 @@
 // Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
 // For license information, please see license.txt
 
-frappe.ui.form.on('E Commerce Settings', {
+frappe.ui.form.on("E Commerce Settings", {
+	onload: function(frm) {
+		if(frm.doc.__onload && frm.doc.__onload.quotation_series) {
+			frm.fields_dict.quotation_series.df.options = frm.doc.__onload.quotation_series;
+			frm.refresh_field("quotation_series");
+		}
+
+		frm.set_query('payment_gateway_account', function() {
+			return { 'filters': { 'payment_channel': "Email" } };
+		});
+	},
 	refresh: function(frm) {
-		frappe.model.with_doctype('Item', () => {
+		if (frm.doc.enabled) {
+			frm.get_field('store_page_docs').$wrapper.removeClass('hide-control').html(
+				`<div>${__("Follow these steps to create a landing page for your store")}:
+					<a href="https://docs.erpnext.com/docs/user/manual/en/website/store-landing-page"
+						style="color: var(--gray-600)">
+						docs/store-landing-page
+					</a>
+				</div>`
+			);
+		}
+
+		frappe.model.with_doctype("Item", () => {
 			const item_meta = frappe.get_meta('Item');
 
 			const valid_fields = item_meta.fields.filter(
-				df => ['Link', 'Table MultiSelect'].includes(df.fieldtype) && !df.hidden
+				df => ["Link", "Table MultiSelect"].includes(df.fieldtype) && !df.hidden
 			).map(df => ({ label: df.label, value: df.fieldname }));
 
 			frm.fields_dict.filter_fields.grid.update_docfield_property(
@@ -17,5 +38,16 @@
 				'fieldname', 'options', valid_fields
 			);
 		});
+	},
+	enabled: function(frm) {
+		if (frm.doc.enabled === 1) {
+			frm.set_value('enable_variants', 1);
+		}
+		else {
+			frm.set_value('company', '');
+			frm.set_value('price_list', '');
+			frm.set_value('default_customer_group', '');
+			frm.set_value('quotation_series', '');
+		}
 	}
 });
diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json
index 1a45adf..b1b1cae 100644
--- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json
+++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json
@@ -10,6 +10,30 @@
   "hide_variants",
   "column_break_4",
   "products_per_page",
+  "display_settings_section",
+  "show_attachments",
+  "show_price",
+  "show_stock_availability",
+  "enable_variants",
+  "column_break_13",
+  "show_contact_us_button",
+  "show_quantity_in_website",
+  "show_apply_coupon_code_in_website",
+  "allow_items_not_in_stock",
+  "section_break_18",
+  "company",
+  "price_list",
+  "enabled",
+  "store_page_docs",
+  "column_break_21",
+  "default_customer_group",
+  "quotation_series",
+  "checkout_settings_section",
+  "enable_checkout",
+  "save_quotations_as_draft",
+  "column_break_27",
+  "payment_gateway_account",
+  "payment_success_url",
   "filter_categories_section",
   "enable_field_filters",
   "filter_fields",
@@ -37,6 +61,7 @@
    "label": "Products per Page"
   },
   {
+   "collapsible": 1,
    "fieldname": "filter_categories_section",
    "fieldtype": "Section Break",
    "label": "Filters"
@@ -76,12 +101,169 @@
    "fieldtype": "Table",
    "label": "Attributes",
    "options": "Website Attribute"
+  },
+  {
+   "default": "0",
+   "fieldname": "enabled",
+   "fieldtype": "Check",
+   "in_list_view": 1,
+   "label": "Enable Shopping Cart"
+  },
+  {
+   "depends_on": "doc.enabled",
+   "fieldname": "store_page_docs",
+   "fieldtype": "HTML"
+  },
+  {
+   "fieldname": "display_settings_section",
+   "fieldtype": "Section Break",
+   "label": "Display Settings"
+  },
+  {
+   "default": "0",
+   "fieldname": "show_attachments",
+   "fieldtype": "Check",
+   "label": "Show Public Attachments"
+  },
+  {
+   "default": "0",
+   "fieldname": "show_price",
+   "fieldtype": "Check",
+   "label": "Show Price"
+  },
+  {
+   "default": "0",
+   "fieldname": "show_stock_availability",
+   "fieldtype": "Check",
+   "label": "Show Stock Availability"
+  },
+  {
+   "default": "0",
+   "fieldname": "enable_variants",
+   "fieldtype": "Check",
+   "label": "Enable Variants"
+  },
+  {
+   "fieldname": "column_break_13",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "fieldname": "show_contact_us_button",
+   "fieldtype": "Check",
+   "label": "Show Contact Us Button"
+  },
+  {
+   "default": "0",
+   "depends_on": "show_stock_availability",
+   "fieldname": "show_quantity_in_website",
+   "fieldtype": "Check",
+   "label": "Show Stock Quantity"
+  },
+  {
+   "default": "0",
+   "fieldname": "show_apply_coupon_code_in_website",
+   "fieldtype": "Check",
+   "label": "Show Apply Coupon Code"
+  },
+  {
+   "default": "0",
+   "fieldname": "allow_items_not_in_stock",
+   "fieldtype": "Check",
+   "label": "Allow items not in stock to be added to cart"
+  },
+  {
+   "fieldname": "section_break_18",
+   "fieldtype": "Section Break",
+   "label": "Shopping Cart"
+  },
+  {
+   "depends_on": "enabled",
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Company",
+   "mandatory_depends_on": "eval: doc.enabled === 1",
+   "options": "Company",
+   "remember_last_selected_value": 1
+  },
+  {
+   "depends_on": "enabled",
+   "description": "Prices will not be shown if Price List is not set",
+   "fieldname": "price_list",
+   "fieldtype": "Link",
+   "label": "Price List",
+   "mandatory_depends_on": "eval: doc.enabled === 1",
+   "options": "Price List"
+  },
+  {
+   "fieldname": "column_break_21",
+   "fieldtype": "Column Break"
+  },
+  {
+   "depends_on": "enabled",
+   "fieldname": "default_customer_group",
+   "fieldtype": "Link",
+   "ignore_user_permissions": 1,
+   "label": "Default Customer Group",
+   "mandatory_depends_on": "eval: doc.enabled === 1",
+   "options": "Customer Group"
+  },
+  {
+   "depends_on": "enabled",
+   "fieldname": "quotation_series",
+   "fieldtype": "Select",
+   "label": "Quotation Series",
+   "mandatory_depends_on": "eval: doc.enabled === 1"
+  },
+  {
+   "collapsible": 1,
+   "collapsible_depends_on": "eval:doc.enable_checkout",
+   "depends_on": "enabled",
+   "fieldname": "checkout_settings_section",
+   "fieldtype": "Section Break",
+   "label": "Checkout Settings"
+  },
+  {
+   "default": "0",
+   "fieldname": "enable_checkout",
+   "fieldtype": "Check",
+   "label": "Enable Checkout"
+  },
+  {
+   "default": "Orders",
+   "depends_on": "enable_checkout",
+   "description": "After payment completion redirect user to selected page.",
+   "fieldname": "payment_success_url",
+   "fieldtype": "Select",
+   "label": "Payment Success Url",
+   "mandatory_depends_on": "enable_checkout",
+   "options": "\nOrders\nInvoices\nMy Account"
+  },
+  {
+   "fieldname": "column_break_27",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "depends_on": "eval: doc.enable_checkout == 0",
+   "fieldname": "save_quotations_as_draft",
+   "fieldtype": "Check",
+   "label": "Save Quotations as Draft"
+  },
+  {
+   "depends_on": "enable_checkout",
+   "fieldname": "payment_gateway_account",
+   "fieldtype": "Link",
+   "label": "Payment Gateway Account",
+   "mandatory_depends_on": "enable_checkout",
+   "options": "Payment Gateway Account"
   }
  ],
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2021-02-10 19:22:47.154104",
+ "modified": "2021-02-11 18:22:14.556880",
  "modified_by": "Administrator",
  "module": "E-commerce",
  "name": "E Commerce Settings",
diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py
index 90596d6..3fabc1a 100644
--- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py
+++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py
@@ -2,15 +2,18 @@
 # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
 # For license information, please see license.txt
 
-from __future__ import unicode_literals
-
 import frappe
 from frappe import _
 from frappe.model.document import Document
 from frappe.utils import cint
+from frappe.utils import get_datetime, get_datetime_str, now_datetime
 
+class ShoppingCartSetupError(frappe.ValidationError): pass
 
 class ECommerceSettings(Document):
+	def onload(self):
+		self.get("__onload").quotation_series = frappe.get_meta("Quotation").get_options("naming_series")
+
 	def validate(self):
 		if self.home_page_is_products:
 			frappe.db.set_value("Website Settings", None, "home_page", "products")
@@ -19,6 +22,9 @@
 
 		self.validate_field_filters()
 		self.validate_attribute_filters()
+		if self.enabled:
+			self.validate_exchange_rates_exist()
+
 		frappe.clear_document_cache("E Commerce Settings", "E Commerce Settings")
 
 	def validate_field_filters(self):
@@ -37,6 +43,79 @@
 		# if attribute filters are enabled, hide_variants should be disabled
 		self.hide_variants = 0
 
+	def validate_exchange_rates_exist(self):
+		"""check if exchange rates exist for all Price List currencies (to company's currency)"""
+		company_currency = frappe.get_cached_value('Company',  self.company,  "default_currency")
+		if not company_currency:
+			msgprint(_("Please specify currency in Company") + ": " + self.company,
+				raise_exception=ShoppingCartSetupError)
+
+		price_list_currency_map = frappe.db.get_values("Price List",
+			[self.price_list], "currency")
+
+		price_list_currency_map = dict(price_list_currency_map)
+
+		# check if all price lists have a currency
+		for price_list, currency in price_list_currency_map.items():
+			if not currency:
+				frappe.throw(_("Currency is required for Price List {0}").format(price_list))
+
+		expected_to_exist = [currency + "-" + company_currency
+			for currency in price_list_currency_map.values()
+			if currency != company_currency]
+
+		# manqala 20/09/2016: set up selection parameters for query from tabCurrency Exchange
+		from_currency = [currency for currency in price_list_currency_map.values() if currency != company_currency]
+		to_currency = company_currency
+		# manqala end
+
+		if expected_to_exist:
+			# manqala 20/09/2016: modify query so that it uses date in the selection from Currency Exchange.
+			# exchange rates defined with date less than the date on which this document is being saved will be selected
+			exists = frappe.db.sql_list("""select CONCAT(from_currency,'-',to_currency) from `tabCurrency Exchange`
+				where from_currency in (%s) and to_currency = "%s" and date <= curdate()""" % (", ".join(["%s"]*len(from_currency)), to_currency), tuple(from_currency))
+			# manqala end
+
+			missing = list(set(expected_to_exist).difference(exists))
+
+			if missing:
+				msgprint(_("Missing Currency Exchange Rates for {0}").format(comma_and(missing)),
+					raise_exception=ShoppingCartSetupError)
+
+	def validate_tax_rule(self):
+		if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart" : 1}, "name"):
+			frappe.throw(frappe._("Set Tax Rule for shopping cart"), ShoppingCartSetupError)
+
+	def get_tax_master(self, billing_territory):
+		tax_master = self.get_name_from_territory(billing_territory, "sales_taxes_and_charges_masters",
+			"sales_taxes_and_charges_master")
+		return tax_master and tax_master[0] or None
+
+	def get_shipping_rules(self, shipping_territory):
+		return self.get_name_from_territory(shipping_territory, "shipping_rules", "shipping_rule")
+
+def validate_cart_settings(doc, method):
+	frappe.get_doc("E Commerce Settings", "E Commerce Settings").run_method("validate")
+
+def get_shopping_cart_settings():
+	if not getattr(frappe.local, "shopping_cart_settings", None):
+		frappe.local.shopping_cart_settings = frappe.get_doc("E Commerce Settings", "E Commerce Settings")
+
+	return frappe.local.shopping_cart_settings
+
+@frappe.whitelist(allow_guest=True)
+def is_cart_enabled():
+	return get_shopping_cart_settings().enabled
+
+def show_quantity_in_website():
+	return get_shopping_cart_settings().show_quantity_in_website
+
+def check_shopping_cart_enabled():
+	if not get_shopping_cart_settings().enabled:
+		frappe.throw(_("You need to enable Shopping Cart"), ShoppingCartSetupError)
+
+def show_attachments():
+	return get_shopping_cart_settings().show_attachments
 
 def home_page_is_products(doc, method):
 	"""Called on saving Website Settings."""
diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py b/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py
index cf23266..798529b 100644
--- a/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py
+++ b/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py
@@ -2,9 +2,40 @@
 # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
 # See license.txt
 from __future__ import unicode_literals
-
-# import frappe
+import frappe
 import unittest
 
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ShoppingCartSetupError
+
 class TestECommerceSettings(unittest.TestCase):
-	pass
+	def setUp(self):
+		frappe.db.sql("""delete from `tabSingles` where doctype="Shipping Cart Settings" """)
+
+	def get_cart_settings(self):
+		return frappe.get_doc({"doctype": "E Commerce Settings",
+			"company": "_Test Company"})
+
+	def test_exchange_rate_exists(self):
+		frappe.db.sql("""delete from `tabCurrency Exchange`""")
+
+		cart_settings = self.get_cart_settings()
+		cart_settings.price_list = "_Test Price List Rest of the World"
+		self.assertRaises(ShoppingCartSetupError, cart_settings.validate_exchange_rates_exist)
+
+		from erpnext.setup.doctype.currency_exchange.test_currency_exchange import test_records as \
+			currency_exchange_records
+		frappe.get_doc(currency_exchange_records[0]).insert()
+		cart_settings.validate_exchange_rates_exist()
+
+	def test_tax_rule_validation(self):
+		frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 0")
+		frappe.db.commit()
+
+		cart_settings = self.get_cart_settings()
+		cart_settings.enabled = 1
+		if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart": 1}, "name"):
+			self.assertRaises(ShoppingCartSetupError, cart_settings.validate_tax_rule)
+
+		frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 1")
+
+test_dependencies = ["Tax Rule"]
diff --git a/erpnext/e_commerce/doctype/website_item/website_item.json b/erpnext/e_commerce/doctype/website_item/website_item.json
index 85a83e6..02717ea 100644
--- a/erpnext/e_commerce/doctype/website_item/website_item.json
+++ b/erpnext/e_commerce/doctype/website_item/website_item.json
@@ -2,12 +2,13 @@
  "actions": [],
  "allow_guest_to_view": 1,
  "allow_import": 1,
- "autoname": "field:item_code",
+ "autoname": "naming_series",
  "creation": "2021-02-09 21:06:14.441698",
  "doctype": "DocType",
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
+  "naming_series",
   "web_item_name",
   "route",
   "has_variants",
@@ -28,6 +29,7 @@
   "thumbnail",
   "section_break_17",
   "website_warehouse",
+  "description",
   "website_specifications",
   "copy_from_item_group",
   "column_break_27",
@@ -60,8 +62,8 @@
    "fieldtype": "Link",
    "label": "Item Code",
    "options": "Item",
-   "reqd": 1,
-   "unique": 1
+   "read_only_depends_on": "eval:!doc.__islocal",
+   "reqd": 1
   },
   {
    "fetch_from": "item_code.item_name",
@@ -246,13 +248,30 @@
   {
    "fieldname": "column_break_22",
    "fieldtype": "Column Break"
+  },
+  {
+   "fetch_from": "item_code.description",
+   "fieldname": "description",
+   "fieldtype": "Text Editor",
+   "label": "Item Description",
+   "read_only": 1
+  },
+  {
+   "default": "WEB-ITM-.####",
+   "fieldname": "naming_series",
+   "fieldtype": "Select",
+   "hidden": 1,
+   "label": "Naming Series",
+   "no_copy": 1,
+   "options": "WEB-ITM-.####",
+   "print_hide": 1
   }
  ],
  "has_web_view": 1,
  "image_field": "image",
  "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2021-02-10 14:22:41.628232",
+ "modified": "2021-02-12 16:49:42.275517",
  "modified_by": "Administrator",
  "module": "E-commerce",
  "name": "Website Item",
diff --git a/erpnext/e_commerce/doctype/website_item/website_item.py b/erpnext/e_commerce/doctype/website_item/website_item.py
index 1e0b12b..55436f2 100644
--- a/erpnext/e_commerce/doctype/website_item/website_item.py
+++ b/erpnext/e_commerce/doctype/website_item/website_item.py
@@ -5,13 +5,17 @@
 from __future__ import unicode_literals
 import frappe
 import json
+import itertools
+from frappe import _
+from six import iteritems
 from frappe.website.doctype.website_slideshow.website_slideshow import \
 	get_slideshow
 
 from frappe.website.render import clear_cache
 from frappe.website.website_generator import WebsiteGenerator
+from frappe.utils import cstr, random_string, cint
 
-from frappe.utils import cstr, random_string
+from erpnext.setup.doctype.item_group.item_group import get_parent_item_groups
 
 class WebsiteItem(WebsiteGenerator):
 	website = frappe._dict(
@@ -30,13 +34,23 @@
 		if not self.item_code:
 			frappe.throw(_("Item Code is required"), title=_("Mandatory"))
 
+		self.validate_duplicate_website_item()
 		self.validate_website_image()
 		self.make_thumbnail()
 		self.publish_unpublish_desk_item(publish=True)
 
+	def on_update(self):
+		self.update_template_item()
+
 	def on_trash(self):
 		self.publish_unpublish_desk_item(publish=False)
 
+	def validate_duplicate_website_item(self):
+		existing_web_item = frappe.db.exists("Website Item", {"item_code": self.item_code})
+		if existing_web_item and existing_web_item != self.name:
+			message = _("Website Item already exists against Item {0}").format(frappe.bold(self.item_code))
+			frappe.throw(message, title=_("Already Published"))
+
 	def publish_unpublish_desk_item(self, publish=True):
 		if frappe.db.get_value("Item", self.item_code, "published_in_website") and publish:
 			return # if already published don't publish again
@@ -48,6 +62,18 @@
 			return cstr(frappe.db.get_value('Item Group', self.item_group,
 					'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5))
 
+	def update_template_item(self):
+		"""Set Show in Website for Template Item if True for its Variant"""
+		if self.variant_of:
+			if self.published:
+				# show template
+				template_item = frappe.get_doc("Item", self.variant_of)
+
+				if not template_item.published:
+					template_item.published = 1
+					template_item.flags.ignore_permissions = True
+					template_item.save()
+
 	def validate_website_image(self):
 		if frappe.flags.in_import:
 			return
@@ -133,6 +159,164 @@
 
 				self.thumbnail = file_doc.thumbnail_url
 
+	def get_context(self, context):
+		print(context)
+		context.show_search = True
+		context.search_link = '/search'
+
+		context.parents = get_parent_item_groups(self.item_group)
+		context.body_class = "product-page"
+		self.attributes = frappe.get_all("Item Variant Attribute",
+					  fields=["attribute", "attribute_value"],
+					  filters={"parent": self.item_code})
+		self.set_variant_context(context)
+		self.set_attribute_context(context)
+		self.set_disabled_attributes(context)
+		self.set_metatags(context)
+		self.set_shopping_cart_data(context)
+		print("IN WEB ITEM")
+
+		return context
+
+	def set_variant_context(self, context):
+		if self.has_variants:
+			context.no_cache = True
+
+			# load variants
+			# also used in set_attribute_context
+			context.variants = frappe.get_all("Item",
+				 filters={"variant_of": self.name, "show_variant_in_website": 1},
+				 order_by="name asc")
+
+			variant = frappe.form_dict.variant
+			if not variant and context.variants:
+				# the case when the item is opened for the first time from its list
+				variant = context.variants[0]
+
+			if variant:
+				context.variant = frappe.get_doc("Item", variant)
+
+				for fieldname in ("website_image", "website_image_alt", "web_long_description", "description",
+										"website_specifications"):
+					if context.variant.get(fieldname):
+						value = context.variant.get(fieldname)
+						if isinstance(value, list):
+							value = [d.as_dict() for d in value]
+
+						context[fieldname] = value
+
+	def set_attribute_context(self, context):
+		if self.has_variants:
+			attribute_values_available = {}
+			context.attribute_values = {}
+			context.selected_attributes = {}
+
+			# load attributes
+			for v in context.variants:
+				v.attributes = frappe.get_all("Item Variant Attribute",
+					  fields=["attribute", "attribute_value"],
+					  filters={"parent": v.name})
+				# make a map for easier access in templates
+				v.attribute_map = frappe._dict({})
+				for attr in v.attributes:
+					v.attribute_map[attr.attribute] = attr.attribute_value
+
+				for attr in v.attributes:
+					values = attribute_values_available.setdefault(attr.attribute, [])
+					if attr.attribute_value not in values:
+						values.append(attr.attribute_value)
+
+					if v.name == context.variant.name:
+						context.selected_attributes[attr.attribute] = attr.attribute_value
+
+			# filter attributes, order based on attribute table
+			for attr in self.attributes:
+				values = context.attribute_values.setdefault(attr.attribute, [])
+
+				if cint(frappe.db.get_value("Item Attribute", attr.attribute, "numeric_values")):
+					for val in sorted(attribute_values_available.get(attr.attribute, []), key=flt):
+						values.append(val)
+
+				else:
+					# get list of values defined (for sequence)
+					for attr_value in frappe.db.get_all("Item Attribute Value",
+						fields=["attribute_value"],
+						filters={"parent": attr.attribute}, order_by="idx asc"):
+
+						if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []):
+							values.append(attr_value.attribute_value)
+
+			context.variant_info = json.dumps(context.variants)
+
+	def set_disabled_attributes(self, context):
+		"""Disable selection options of attribute combinations that do not result in a variant"""
+
+		if not self.attributes or not self.has_variants:
+			return
+
+		context.disabled_attributes = {}
+		attributes = [attr.attribute for attr in self.attributes]
+
+		def find_variant(combination):
+			for variant in context.variants:
+				if len(variant.attributes) < len(attributes):
+					continue
+
+				if "combination" not in variant:
+					ref_combination = []
+
+					for attr in variant.attributes:
+						idx = attributes.index(attr.attribute)
+						ref_combination.insert(idx, attr.attribute_value)
+
+					variant["combination"] = ref_combination
+
+				if not (set(combination) - set(variant["combination"])):
+					# check if the combination is a subset of a variant combination
+					# eg. [Blue, 0.5] is a possible combination if exists [Blue, Large, 0.5]
+					return True
+
+		for i, attr in enumerate(self.attributes):
+			if i == 0:
+				continue
+
+			combination_source = []
+
+			# loop through previous attributes
+			for prev_attr in self.attributes[:i]:
+				combination_source.append([context.selected_attributes.get(prev_attr.attribute)])
+
+			combination_source.append(context.attribute_values[attr.attribute])
+
+			for combination in itertools.product(*combination_source):
+				if not find_variant(combination):
+					context.disabled_attributes.setdefault(attr.attribute, []).append(combination[-1])
+
+	def set_metatags(self, context):
+		context.metatags = frappe._dict({})
+
+		safe_description = frappe.utils.to_markdown(self.description)
+
+		context.metatags.url = frappe.utils.get_url() + '/' + context.route
+
+		if context.website_image:
+			if context.website_image.startswith('http'):
+				url = context.website_image
+			else:
+				url = frappe.utils.get_url() + context.website_image
+			context.metatags.image = url
+
+		context.metatags.description = safe_description[:300]
+
+		context.metatags.title = self.item_name or self.item_code
+
+		context.metatags['og:type'] = 'product'
+		context.metatags['og:site_name'] = 'ERPNext'
+
+	def set_shopping_cart_data(self, context):
+		from erpnext.shopping_cart.product_info import get_product_info_for_website
+		context.shopping_cart = get_product_info_for_website(self.item_code, skip_quotation_creation=True)
+
 @frappe.whitelist()
 def make_website_item(doc):
 	if not doc:
@@ -141,13 +325,13 @@
 
 	if frappe.db.exists("Website Item", {"item_code": doc.get("item_code")}):
 		message = _("Website Item already exists against {0}").format(frappe.bold(doc.get("item_code")))
-		frappe.throw(message, title=_("Already Published"), indicator="blue")
+		frappe.throw(message, title=_("Already Published"))
 
 	website_item = frappe.new_doc("Website Item")
 	website_item.web_item_name = doc.get("item_name")
 
 	fields_to_map = ["item_code", "item_name", "item_group", "stock_uom", "brand", "image",
-		"has_variants", "variant_of"]
+		"has_variants", "variant_of", "description"]
 	for field in fields_to_map:
 		website_item.update({field: doc.get(field)})
 
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 684b13a..d0142d3 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -240,8 +240,8 @@
 			"erpnext.support.doctype.issue.issue.set_first_response_time"
 		]
 	},
-	"Sales Taxes and Charges Template": {
-		"on_update": "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.validate_cart_settings"
+	("Sales Taxes and Charges Template", "Price List"): {
+		"on_update": "erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings.validate_cart_settings"
 	},
 	"Website Settings": {
 		"validate": "erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings.home_page_is_products"
diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py
index b0c5c37..0de9729 100644
--- a/erpnext/portal/product_configurator/utils.py
+++ b/erpnext/portal/product_configurator/utils.py
@@ -5,166 +5,6 @@
 from erpnext.setup.doctype.item_group.item_group import get_child_groups
 from erpnext.shopping_cart.product_info import get_product_info_for_website
 
-
-def get_field_filter_data():
-	e_commerce_settings = get_e_commerce_settings()
-	filter_fields = [row.fieldname for row in e_commerce_settings.filter_fields]
-
-	meta = frappe.get_meta('Item')
-	fields = [df for df in meta.fields if df.fieldname in filter_fields]
-
-	filter_data = []
-	for f in fields:
-		doctype = f.get_link_doctype()
-
-		# apply enable/disable/show_in_website filter
-		meta = frappe.get_meta(doctype)
-		filters = {}
-		if meta.has_field('enabled'):
-			filters['enabled'] = 1
-		if meta.has_field('disabled'):
-			filters['disabled'] = 0
-		if meta.has_field('show_in_website'):
-			filters['show_in_website'] = 1
-
-		values = [d.name for d in frappe.get_all(doctype, filters)]
-		filter_data.append([f, values])
-
-	return filter_data
-
-
-def get_attribute_filter_data():
-	e_commerce_settings = get_e_commerce_settings()
-	attributes = [row.attribute for row in e_commerce_settings.filter_attributes]
-	attribute_docs = [
-		frappe.get_doc('Item Attribute', attribute) for attribute in attributes
-	]
-
-	# mark attribute values as checked if they are present in the request url
-	if frappe.form_dict:
-		for attr in attribute_docs:
-			if attr.name in frappe.form_dict:
-				value = frappe.form_dict[attr.name]
-				if value:
-					enabled_values = value.split(',')
-				else:
-					enabled_values = []
-
-				for v in enabled_values:
-					for item_attribute_row in attr.item_attribute_values:
-						if v == item_attribute_row.attribute_value:
-							item_attribute_row.checked = True
-
-	return attribute_docs
-
-
-def get_products_for_website(field_filters=None, attribute_filters=None, search=None):
-	if attribute_filters:
-		item_codes = get_item_codes_by_attributes(attribute_filters)
-		items_by_attributes = get_items([['name', 'in', item_codes]])
-
-	if field_filters:
-		items_by_fields = get_items_by_fields(field_filters)
-
-	if attribute_filters and not field_filters:
-		return items_by_attributes
-
-	if field_filters and not attribute_filters:
-		return items_by_fields
-
-	if field_filters and attribute_filters:
-		items_intersection = []
-		item_codes_in_attribute = [item.name for item in items_by_attributes]
-
-		for item in items_by_fields:
-			if item.name in item_codes_in_attribute:
-				items_intersection.append(item)
-
-		return items_intersection
-
-	if search:
-		return get_items(search=search)
-
-	return get_items()
-
-
-@frappe.whitelist(allow_guest=True)
-def get_products_html_for_website(field_filters=None, attribute_filters=None):
-	field_filters = frappe.parse_json(field_filters)
-	attribute_filters = frappe.parse_json(attribute_filters)
-	set_item_group_filters(field_filters)
-
-	items = get_products_for_website(field_filters, attribute_filters)
-	html = ''.join(get_html_for_items(items))
-
-	if not items:
-		html = frappe.render_template('erpnext/www/all-products/not_found.html', {})
-
-	return html
-
-def set_item_group_filters(field_filters):
-	if field_filters is not None and 'item_group' in field_filters:
-		field_filters['item_group'] = [ig[0] for ig in get_child_groups(field_filters['item_group'])]
-
-
-def get_item_codes_by_attributes(attribute_filters, template_item_code=None):
-	items = []
-
-	for attribute, values in attribute_filters.items():
-		attribute_values = values
-
-		if not isinstance(attribute_values, list):
-			attribute_values = [attribute_values]
-
-		if not attribute_values: continue
-
-		wheres = []
-		query_values = []
-		for attribute_value in attribute_values:
-			wheres.append('( attribute = %s and attribute_value = %s )')
-			query_values += [attribute, attribute_value]
-
-		attribute_query = ' or '.join(wheres)
-
-		if template_item_code:
-			variant_of_query = 'AND t2.variant_of = %s'
-			query_values.append(template_item_code)
-		else:
-			variant_of_query = ''
-
-		query = '''
-			SELECT
-				t1.parent
-			FROM
-				`tabItem Variant Attribute` t1
-			WHERE
-				1 = 1
-				AND (
-					{attribute_query}
-				)
-				AND EXISTS (
-					SELECT
-						1
-					FROM
-						`tabItem` t2
-					WHERE
-						t2.name = t1.parent
-						{variant_of_query}
-				)
-			GROUP BY
-				t1.parent
-			ORDER BY
-				NULL
-		'''.format(attribute_query=attribute_query, variant_of_query=variant_of_query)
-
-		item_codes = set([r[0] for r in frappe.db.sql(query, query_values)])
-		items.append(item_codes)
-
-	res = list(set.intersection(*items))
-
-	return res
-
-
 @frappe.whitelist(allow_guest=True)
 def get_attributes_and_values(item_code):
 	'''Build a list of attributes and their possible values.
@@ -278,140 +118,6 @@
 
 	return set.intersection(*items)
 
-
-def get_items_by_fields(field_filters):
-	meta = frappe.get_meta('Item')
-	filters = []
-	for fieldname, values in field_filters.items():
-		if not values: continue
-
-		_doctype = 'Item'
-		_fieldname = fieldname
-
-		df = meta.get_field(fieldname)
-		if df.fieldtype == 'Table MultiSelect':
-			child_doctype = df.options
-			child_meta = frappe.get_meta(child_doctype)
-			fields = child_meta.get("fields", { "fieldtype": "Link", "in_list_view": 1 })
-			if fields:
-				_doctype = child_doctype
-				_fieldname = fields[0].fieldname
-
-		if len(values) == 1:
-			filters.append([_doctype, _fieldname, '=', values[0]])
-		else:
-			filters.append([_doctype, _fieldname, 'in', values])
-
-	return get_items(filters)
-
-
-def get_items(filters=None, search=None):
-	start = frappe.form_dict.start or 0
-	e_commerce_settings = get_e_commerce_settings()
-	page_length = e_commerce_settings.products_per_page
-
-	filters = filters or []
-	# convert to list of filters
-	if isinstance(filters, dict):
-		filters = [['Item', fieldname, '=', value] for fieldname, value in filters.items()]
-
-	enabled_items_filter = get_conditions({ 'disabled': 0 }, 'and')
-
-	show_in_website_condition = ''
-	if e_commerce_settings.hide_variants:
-		show_in_website_condition = get_conditions({'show_in_website': 1 }, 'and')
-	else:
-		show_in_website_condition = get_conditions([
-			['show_in_website', '=', 1],
-			['show_variant_in_website', '=', 1]
-		], 'or')
-
-	search_condition = ''
-	if search:
-		# Default fields to search from
-		default_fields = {'name', 'item_name', 'description', 'item_group'}
-
-		# Get meta search fields
-		meta = frappe.get_meta("Item")
-		meta_fields = set(meta.get_search_fields())
-
-		# 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
-
-		# Build or filters for query
-		search = '%{}%'.format(search)
-		or_filters = [[field, 'like', search] for field in search_fields]
-
-		search_condition = get_conditions(or_filters, 'or')
-
-	filter_condition = get_conditions(filters, 'and')
-
-	where_conditions = ' and '.join(
-		[condition for condition in [enabled_items_filter, show_in_website_condition, \
-			search_condition, filter_condition] if condition]
-	)
-
-	left_joins = []
-	for f in filters:
-		if len(f) == 4 and f[0] != 'Item':
-			left_joins.append(f[0])
-
-	left_join = ' '.join(['LEFT JOIN `tab{0}` on (`tab{0}`.parent = `tabItem`.name)'.format(l) for l in left_joins])
-
-	results = frappe.db.sql('''
-		SELECT
-			`tabItem`.`name`, `tabItem`.`item_name`, `tabItem`.`item_code`,
-			`tabItem`.`website_image`, `tabItem`.`image`,
-			`tabItem`.`web_long_description`, `tabItem`.`description`,
-			`tabItem`.`route`, `tabItem`.`item_group`
-		FROM
-			`tabItem`
-		{left_join}
-		WHERE
-			{where_conditions}
-		GROUP BY
-			`tabItem`.`name`
-		ORDER BY
-			`tabItem`.`weightage` DESC
-		LIMIT
-			{page_length}
-		OFFSET
-			{start}
-	'''.format(
-			where_conditions=where_conditions,
-			start=start,
-			page_length=page_length,
-			left_join=left_join
-		)
-	, as_dict=1)
-
-	for r in results:
-		r.description = r.web_long_description or r.description
-		r.image = r.website_image or r.image
-		product_info = get_product_info_for_website(r.item_code, skip_quotation_creation=True).get('product_info')
-		if product_info:
-			r.formatted_price = product_info['price'].get('formatted_price') if product_info['price'] else None
-
-	return results
-
-
-def get_conditions(filter_list, and_or='and'):
-	from frappe.model.db_query import DatabaseQuery
-
-	if not filter_list:
-		return ''
-
-	conditions = []
-	DatabaseQuery('Item').build_filter_conditions(filter_list, conditions, ignore_permissions=True)
-	join_by = ' {0} '.format(and_or)
-
-	return '(' + join_by.join(conditions) + ')'
-
 # utilities
 
 def get_item_attributes(item_code):
diff --git a/erpnext/portal/utils.py b/erpnext/portal/utils.py
index bae8f35..3ee2c88 100644
--- a/erpnext/portal/utils.py
+++ b/erpnext/portal/utils.py
@@ -1,10 +1,8 @@
-from __future__ import unicode_literals
-
 import frappe
 from frappe.utils.nestedset import get_root_of
 
 from erpnext.shopping_cart.cart import get_debtors_account
-from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import (
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
 	get_shopping_cart_settings,
 )
 
diff --git a/erpnext/public/js/shopping_cart.js b/erpnext/public/js/shopping_cart.js
index 6a923ae..227881a 100644
--- a/erpnext/public/js/shopping_cart.js
+++ b/erpnext/public/js/shopping_cart.js
@@ -180,7 +180,7 @@
 
 	show_cart_navbar: function () {
 		frappe.call({
-			method: "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.is_cart_enabled",
+			method: "erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings.is_cart_enabled",
 			callback: function(r) {
 				$(".shopping-cart").toggleClass('hidden', r.message ? false : true);
 			}
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index 99c43bf..e9644cc 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -274,7 +274,7 @@
 				customer = frappe.get_doc(customer_doclist)
 				customer.flags.ignore_permissions = ignore_permissions
 				if quotation.get("party_name") == "Shopping Cart":
-					customer.customer_group = frappe.db.get_value("Shopping Cart Settings", None,
+					customer.customer_group = frappe.db.get_value("E Commerce Settings", None,
 						"default_customer_group")
 
 				try:
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index 2f03ee7..3965d82 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -120,9 +120,8 @@
 				values[f"slide_{index + 1}_image"] = slide.image
 				values[f"slide_{index + 1}_title"] = slide.heading
 				values[f"slide_{index + 1}_subtitle"] = slide.description
-				values[f"slide_{index + 1}_theme"] = slide.theme or "Light"
-				values[f"slide_{index + 1}_content_align"] = slide.content_align or "Centre"
-				values[f"slide_{index + 1}_primary_action_label"] = slide.label
+				values[f"slide_{index + 1}_theme"] = slide.get("theme") or "Light"
+				values[f"slide_{index + 1}_content_align"] = slide.get("content_align") or "Centre"
 				values[f"slide_{index + 1}_primary_action"] = slide.url
 
 			context.slideshow = values
@@ -175,7 +174,7 @@
 	data = frappe.db.sql(query, {"product_group": product_group,"search": search, "today": nowdate()}, as_dict=1)
 	data = adjust_qty_for_expired_items(data)
 
-	if cint(frappe.db.get_single_value("Shopping Cart Settings", "enabled")):
+	if cint(frappe.db.get_single_value("E Commerce Settings", "enabled")):
 		for item in data:
 			set_product_info_for_website(item)
 
diff --git a/erpnext/setup/setup_wizard/operations/company_setup.py b/erpnext/setup/setup_wizard/operations/company_setup.py
index bea3906..be94994 100644
--- a/erpnext/setup/setup_wizard/operations/company_setup.py
+++ b/erpnext/setup/setup_wizard/operations/company_setup.py
@@ -33,7 +33,7 @@
 def enable_shopping_cart(args):
 	# Needs price_lists
 	frappe.get_doc({
-		"doctype": "Shopping Cart Settings",
+		"doctype": "E Commerce Settings",
 		"enabled": 1,
 		'company': args.get('company_name')	,
 		'price_list': frappe.db.get_value("Price List", {"selling": 1}),
diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py
index c473395..fbfcb10 100644
--- a/erpnext/setup/setup_wizard/operations/install_fixtures.py
+++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py
@@ -534,7 +534,7 @@
 			pass
 
 def update_shopping_cart_settings(args):
-	shopping_cart = frappe.get_doc("Shopping Cart Settings")
+	shopping_cart = frappe.get_doc("E Commerce Settings")
 	shopping_cart.update({
 		"enabled": 1,
 		'company': args.company_name,
diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py
index 3f1dfde..cd9f1e8 100644
--- a/erpnext/shopping_cart/cart.py
+++ b/erpnext/shopping_cart/cart.py
@@ -1,8 +1,6 @@
 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 
-from __future__ import unicode_literals
-
 import frappe
 import frappe.defaults
 from frappe import _, throw
@@ -11,10 +9,8 @@
 from frappe.utils import cint, cstr, flt, get_fullname
 from frappe.utils.nestedset import get_root_of
 
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import get_shopping_cart_settings
 from erpnext.accounts.utils import get_account_name
-from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import (
-	get_shopping_cart_settings,
-)
 from erpnext.utilities.product import get_qty_in_stock
 
 
@@ -22,7 +18,7 @@
 	pass
 
 def set_cart_count(quotation=None):
-	if cint(frappe.db.get_singles_value("Shopping Cart Settings", "enabled")):
+	if cint(frappe.db.get_singles_value("E Commerce Settings", "enabled")):
 		if not quotation:
 			quotation = _get_cart_quotation()
 		cart_count = cstr(len(quotation.get("items")))
@@ -49,7 +45,7 @@
 		"shipping_addresses": get_shipping_addresses(party),
 		"billing_addresses": get_billing_addresses(party),
 		"shipping_rules": get_applicable_shipping_rules(party),
-		"cart_settings": frappe.get_cached_doc("Shopping Cart Settings")
+		"cart_settings": frappe.get_cached_doc("E Commerce Settings")
 	}
 
 @frappe.whitelist()
@@ -73,7 +69,7 @@
 @frappe.whitelist()
 def place_order():
 	quotation = _get_cart_quotation()
-	cart_settings = frappe.db.get_value("Shopping Cart Settings", None,
+	cart_settings = frappe.db.get_value("E Commerce Settings", None,
 		["company", "allow_items_not_in_stock"], as_dict=1)
 	quotation.company = cart_settings.company
 
@@ -263,7 +259,7 @@
 		territory = frappe.db.get_value("Territory", geoip_country)
 
 	return territory or \
-		frappe.db.get_value("Shopping Cart Settings", None, "territory") or \
+		frappe.db.get_value("E Commerce Settings", None, "territory") or \
 			get_root_of("Territory")
 
 def decorate_quotation_doc(doc):
@@ -286,7 +282,7 @@
 	if quotation:
 		qdoc = frappe.get_doc("Quotation", quotation[0].name)
 	else:
-		company = frappe.db.get_value("Shopping Cart Settings", None, ["company"])
+		company = frappe.db.get_value("E Commerce Settings", None, ["company"])
 		qdoc = frappe.get_doc({
 			"doctype": "Quotation",
 			"naming_series": get_shopping_cart_settings().quotation_series or "QTN-CART-",
@@ -341,7 +337,7 @@
 	if not quotation:
 		quotation = _get_cart_quotation(party)
 
-	cart_settings = frappe.get_doc("Shopping Cart Settings")
+	cart_settings = frappe.get_doc("E Commerce Settings")
 
 	set_price_list_and_rate(quotation, cart_settings)
 
@@ -418,7 +414,7 @@
 			party_doctype = contact.links[0].link_doctype
 			party = contact.links[0].link_name
 
-	cart_settings = frappe.get_doc("Shopping Cart Settings")
+	cart_settings = frappe.get_doc("E Commerce Settings")
 
 	debtors_account = ''
 
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/__init__.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/__init__.py
+++ /dev/null
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js
deleted file mode 100644
index b38828e..0000000
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-// License: GNU General Public License v3. See license.txt
-
-frappe.ui.form.on("Shopping Cart Settings", {
-	onload: function(frm) {
-		if(frm.doc.__onload && frm.doc.__onload.quotation_series) {
-			frm.fields_dict.quotation_series.df.options = frm.doc.__onload.quotation_series;
-			frm.refresh_field("quotation_series");
-		}
-
-		frm.set_query('payment_gateway_account', function() {
-			return { 'filters': { 'payment_channel': "Email" } };
-		});
-	},
-	refresh: function(frm) {
-		if (frm.doc.enabled) {
-			frm.get_field('store_page_docs').$wrapper.removeClass('hide-control').html(
-				`<div>${__("Follow these steps to create a landing page for your store")}:
-					<a href="https://docs.erpnext.com/docs/user/manual/en/website/store-landing-page"
-						style="color: var(--gray-600)">
-						docs/store-landing-page
-					</a>
-				</div>`
-			);
-		}
-	},
-	enabled: function(frm) {
-		if (frm.doc.enabled === 1) {
-			frm.set_value('enable_variants', 1);
-		}
-		else {
-			frm.set_value('company', '');
-			frm.set_value('price_list', '');
-			frm.set_value('default_customer_group', '');
-			frm.set_value('quotation_series', '');
-		}
-	}
-});
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json
deleted file mode 100644
index 7a4bb20..0000000
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json
+++ /dev/null
@@ -1,212 +0,0 @@
-{
- "actions": [],
- "creation": "2013-06-19 15:57:32",
- "description": "Default settings for Shopping Cart",
- "doctype": "DocType",
- "document_type": "System",
- "engine": "InnoDB",
- "field_order": [
-  "enabled",
-  "store_page_docs",
-  "display_settings",
-  "show_attachments",
-  "show_price",
-  "show_stock_availability",
-  "enable_variants",
-  "column_break_7",
-  "show_contact_us_button",
-  "show_quantity_in_website",
-  "show_apply_coupon_code_in_website",
-  "allow_items_not_in_stock",
-  "section_break_2",
-  "company",
-  "price_list",
-  "column_break_4",
-  "default_customer_group",
-  "quotation_series",
-  "section_break_8",
-  "enable_checkout",
-  "save_quotations_as_draft",
-  "column_break_11",
-  "payment_gateway_account",
-  "payment_success_url"
- ],
- "fields": [
-  {
-   "default": "0",
-   "fieldname": "enabled",
-   "fieldtype": "Check",
-   "in_list_view": 1,
-   "label": "Enable Shopping Cart"
-  },
-  {
-   "fieldname": "display_settings",
-   "fieldtype": "Section Break",
-   "label": "Display Settings"
-  },
-  {
-   "default": "0",
-   "fieldname": "show_attachments",
-   "fieldtype": "Check",
-   "label": "Show Public Attachments"
-  },
-  {
-   "default": "0",
-   "fieldname": "show_price",
-   "fieldtype": "Check",
-   "label": "Show Price"
-  },
-  {
-   "default": "0",
-   "fieldname": "show_stock_availability",
-   "fieldtype": "Check",
-   "label": "Show Stock Availability"
-  },
-  {
-   "default": "0",
-   "fieldname": "show_contact_us_button",
-   "fieldtype": "Check",
-   "label": "Show Contact Us Button"
-  },
-  {
-   "default": "0",
-   "depends_on": "show_stock_availability",
-   "fieldname": "show_quantity_in_website",
-   "fieldtype": "Check",
-   "label": "Show Stock Quantity"
-  },
-  {
-   "default": "0",
-   "fieldname": "show_apply_coupon_code_in_website",
-   "fieldtype": "Check",
-   "label": "Show Apply Coupon Code"
-  },
-  {
-   "default": "0",
-   "fieldname": "allow_items_not_in_stock",
-   "fieldtype": "Check",
-   "label": "Allow items not in stock to be added to cart"
-  },
-  {
-   "depends_on": "enabled",
-   "fieldname": "section_break_2",
-   "fieldtype": "Section Break"
-  },
-  {
-   "fieldname": "company",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "label": "Company",
-   "mandatory_depends_on": "eval: doc.enabled === 1",
-   "options": "Company",
-   "remember_last_selected_value": 1
-  },
-  {
-   "description": "Prices will not be shown if Price List is not set",
-   "fieldname": "price_list",
-   "fieldtype": "Link",
-   "label": "Price List",
-   "mandatory_depends_on": "eval: doc.enabled === 1",
-   "options": "Price List"
-  },
-  {
-   "fieldname": "column_break_4",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "default_customer_group",
-   "fieldtype": "Link",
-   "ignore_user_permissions": 1,
-   "label": "Default Customer Group",
-   "mandatory_depends_on": "eval: doc.enabled === 1",
-   "options": "Customer Group"
-  },
-  {
-   "fieldname": "quotation_series",
-   "fieldtype": "Select",
-   "label": "Quotation Series",
-   "mandatory_depends_on": "eval: doc.enabled === 1"
-  },
-  {
-   "collapsible": 1,
-   "collapsible_depends_on": "eval:doc.enable_checkout",
-   "depends_on": "enabled",
-   "fieldname": "section_break_8",
-   "fieldtype": "Section Break",
-   "label": "Checkout Settings"
-  },
-  {
-   "default": "0",
-   "fieldname": "enable_checkout",
-   "fieldtype": "Check",
-   "label": "Enable Checkout"
-  },
-  {
-   "default": "Orders",
-   "depends_on": "enable_checkout",
-   "description": "After payment completion redirect user to selected page.",
-   "fieldname": "payment_success_url",
-   "fieldtype": "Select",
-   "label": "Payment Success Url",
-   "mandatory_depends_on": "enable_checkout",
-   "options": "\nOrders\nInvoices\nMy Account"
-  },
-  {
-   "fieldname": "column_break_11",
-   "fieldtype": "Column Break"
-  },
-  {
-   "depends_on": "enable_checkout",
-   "fieldname": "payment_gateway_account",
-   "fieldtype": "Link",
-   "label": "Payment Gateway Account",
-   "mandatory_depends_on": "enable_checkout",
-   "options": "Payment Gateway Account"
-  },
-  {
-   "fieldname": "column_break_7",
-   "fieldtype": "Column Break"
-  },
-  {
-   "default": "0",
-   "fieldname": "enable_variants",
-   "fieldtype": "Check",
-   "label": "Enable Variants"
-  },
-  {
-   "default": "0",
-   "depends_on": "eval: doc.enable_checkout == 0",
-   "fieldname": "save_quotations_as_draft",
-   "fieldtype": "Check",
-   "label": "Save Quotations as Draft"
-  },
-  {
-   "depends_on": "doc.enabled",
-   "fieldname": "store_page_docs",
-   "fieldtype": "HTML"
-  }
- ],
- "icon": "fa fa-shopping-cart",
- "idx": 1,
- "issingle": 1,
- "links": [],
- "modified": "2021-03-02 17:34:57.642565",
- "modified_by": "Administrator",
- "module": "Shopping Cart",
- "name": "Shopping Cart Settings",
- "owner": "Administrator",
- "permissions": [
-  {
-   "create": 1,
-   "email": 1,
-   "print": 1,
-   "read": 1,
-   "role": "Website Manager",
-   "share": 1,
-   "write": 1
-  }
- ],
- "sort_field": "modified",
- "sort_order": "ASC",
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py
deleted file mode 100644
index 8f4afda..0000000
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-
-import frappe
-from frappe import _
-from frappe.model.document import Document
-from frappe.utils import flt
-
-
-class ShoppingCartSetupError(frappe.ValidationError): pass
-
-class ShoppingCartSettings(Document):
-	def onload(self):
-		self.get("__onload").quotation_series = frappe.get_meta("Quotation").get_options("naming_series")
-
-	def validate(self):
-		if self.enabled:
-			self.validate_price_list_exchange_rate()
-
-	def validate_price_list_exchange_rate(self):
-		"Check if exchange rate exists for Price List currency (to Company's currency)."
-		from erpnext.setup.utils import get_exchange_rate
-
-		if not self.enabled or not self.company or not self.price_list:
-			return # this function is also called from hooks, check values again
-
-		company_currency = frappe.get_cached_value("Company", self.company, "default_currency")
-		price_list_currency = frappe.db.get_value("Price List", self.price_list, "currency")
-
-		if not company_currency:
-			msg = f"Please specify currency in Company {self.company}"
-			frappe.throw(_(msg), title=_("Missing Currency"), exc=ShoppingCartSetupError)
-
-		if not price_list_currency:
-			msg = f"Please specify currency in Price List {frappe.bold(self.price_list)}"
-			frappe.throw(_(msg), title=_("Missing Currency"), exc=ShoppingCartSetupError)
-
-		if price_list_currency != company_currency:
-			from_currency, to_currency = price_list_currency, company_currency
-
-			# Get exchange rate checks Currency Exchange Records too
-			exchange_rate = get_exchange_rate(from_currency, to_currency, args="for_selling")
-
-			if not flt(exchange_rate):
-				msg = f"Missing Currency Exchange Rates for {from_currency}-{to_currency}"
-				frappe.throw(_(msg), title=_("Missing"), exc=ShoppingCartSetupError)
-
-	def validate_tax_rule(self):
-		if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart" : 1}, "name"):
-			frappe.throw(frappe._("Set Tax Rule for shopping cart"), ShoppingCartSetupError)
-
-	def get_tax_master(self, billing_territory):
-		tax_master = self.get_name_from_territory(billing_territory, "sales_taxes_and_charges_masters",
-			"sales_taxes_and_charges_master")
-		return tax_master and tax_master[0] or None
-
-	def get_shipping_rules(self, shipping_territory):
-		return self.get_name_from_territory(shipping_territory, "shipping_rules", "shipping_rule")
-
-def validate_cart_settings(doc=None, method=None):
-	frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings").run_method("validate")
-
-def get_shopping_cart_settings():
-	if not getattr(frappe.local, "shopping_cart_settings", None):
-		frappe.local.shopping_cart_settings = frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings")
-
-	return frappe.local.shopping_cart_settings
-
-@frappe.whitelist(allow_guest=True)
-def is_cart_enabled():
-	return get_shopping_cart_settings().enabled
-
-def show_quantity_in_website():
-	return get_shopping_cart_settings().show_quantity_in_website
-
-def check_shopping_cart_enabled():
-	if not get_shopping_cart_settings().enabled:
-		frappe.throw(_("You need to enable Shopping Cart"), ShoppingCartSetupError)
-
-def show_attachments():
-	return get_shopping_cart_settings().show_attachments
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py
deleted file mode 100644
index f8a22b0..0000000
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-
-import unittest
-
-import frappe
-
-from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import (
-	ShoppingCartSetupError,
-)
-
-
-class TestShoppingCartSettings(unittest.TestCase):
-	def setUp(self):
-		frappe.db.sql("""delete from `tabSingles` where doctype="Shipping Cart Settings" """)
-
-	def get_cart_settings(self):
-		return frappe.get_doc({"doctype": "Shopping Cart Settings",
-			"company": "_Test Company"})
-
-	# NOTE: Exchangrate API has all enabled currencies that ERPNext supports.
-	# We aren't checking just currency exchange record anymore
-	# while validating price list currency exchange rate to that of company.
-	# The API is being used to fetch the rate which again almost always
-	# gives back a valid value (for valid currencies).
-	# This makes the test obsolete.
-	# Commenting because im not sure if there's a better test we can write
-
-	# def test_exchange_rate_exists(self):
-	# 	frappe.db.sql("""delete from `tabCurrency Exchange`""")
-
-	# 	cart_settings = self.get_cart_settings()
-	# 	cart_settings.price_list = "_Test Price List Rest of the World"
-	# 	self.assertRaises(ShoppingCartSetupError, cart_settings.validate_price_list_exchange_rate)
-
-	# 	from erpnext.setup.doctype.currency_exchange.test_currency_exchange import test_records as \
-	# 		currency_exchange_records
-	# 	frappe.get_doc(currency_exchange_records[0]).insert()
-	# 	cart_settings.validate_price_list_exchange_rate()
-
-	def test_tax_rule_validation(self):
-		frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 0")
-		frappe.db.commit()
-
-		cart_settings = self.get_cart_settings()
-		cart_settings.enabled = 1
-		if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart": 1}, "name"):
-			self.assertRaises(ShoppingCartSetupError, cart_settings.validate_tax_rule)
-
-		frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 1")
-
-test_dependencies = ["Tax Rule"]
diff --git a/erpnext/shopping_cart/product_info.py b/erpnext/shopping_cart/product_info.py
index fa68636..dd77536 100644
--- a/erpnext/shopping_cart/product_info.py
+++ b/erpnext/shopping_cart/product_info.py
@@ -1,17 +1,14 @@
 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 
-from __future__ import unicode_literals
-
 import frappe
 
 from erpnext.shopping_cart.cart import _get_cart_quotation, _set_price_list
-from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import (
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
 	get_shopping_cart_settings,
-	show_quantity_in_website,
+	show_quantity_in_website
 )
-from erpnext.utilities.product import get_non_stock_item_status, get_price, get_qty_in_stock
-
+from erpnext.utilities.product import get_price, get_qty_in_stock, get_non_stock_item_status
 
 @frappe.whitelist(allow_guest=True)
 def get_product_info_for_website(item_code, skip_quotation_creation=False):
diff --git a/erpnext/shopping_cart/product_query.py b/erpnext/shopping_cart/product_query.py
index d105ab8..140e1c6 100644
--- a/erpnext/shopping_cart/product_query.py
+++ b/erpnext/shopping_cart/product_query.py
@@ -10,26 +10,22 @@
 	"""Query engine for product listing
 
 	Attributes:
-	    cart_settings (Document): Settings for Cart
 	    fields (list): Fields to fetch in query
-	    filters (TYPE): Description
-	    or_filters (list): Description
+	    conditions (string): Conditions for query building
+	    or_conditions (string): Search conditions
 	    page_length (Int): Length of page for the query
 	    settings (Document): E Commerce Settings DocType
-	    filters (list)
-	    or_filters (list)
 	"""
 
 	def __init__(self):
 		self.settings = frappe.get_doc("E Commerce Settings")
-		self.cart_settings = frappe.get_doc("Shopping Cart Settings")
 		self.page_length = self.settings.products_per_page or 20
-		self.fields = ['name', 'item_name', 'item_code', 'website_image', 'variant_of', 'has_variants',
-			'item_group', 'image', 'web_long_description', 'description', 'route', 'weightage']
-		self.filters = []
-		self.or_filters = [['show_in_website', '=', 1]]
-		if not self.settings.get('hide_variants'):
-			self.or_filters.append(['show_variant_in_website', '=', 1])
+		self.fields = ['wi.name', 'wi.item_name', 'wi.item_code', 'wi.website_image', 'wi.variant_of',
+			'wi.has_variants', 'wi.item_group', 'wi.image', 'wi.web_long_description', 'wi.description',
+			'wi.route']
+		self.conditions = ""
+		self.or_conditions = ""
+		self.substitutions = []
 
 	def query(self, attributes=None, fields=None, search_term=None, start=0, item_group=None):
 		"""Summary
@@ -57,51 +53,14 @@
 				filters=[["Website Item Group", "item_group", "=", item_group]]
 			)
 
+		self.query_fields = (", ").join(self.fields)
 		if attributes:
-			all_items = []
-			for attribute, values in attributes.items():
-				if not isinstance(values, list):
-					values = [values]
-
-				items = frappe.get_all(
-					"Item",
-					fields=self.fields,
-					filters=[
-						*self.filters,
-						["Item Variant Attribute", "attribute", "=", attribute],
-						["Item Variant Attribute", "attribute_value", "in", values],
-					],
-					or_filters=self.or_filters,
-					start=start,
-					limit=self.page_length,
-					order_by="weightage desc"
-				)
-
-				items_dict = {item.name: item for item in items}
-
-				all_items.append(set(items_dict.keys()))
-
-			result = [items_dict.get(item) for item in list(set.intersection(*all_items))]
+			result = self.query_items_with_attributes(attributes, start)
 		else:
-			result = frappe.get_all(
-				"Item",
-				fields=self.fields,
-				filters=self.filters,
-				or_filters=self.or_filters,
-				start=start,
-				limit=self.page_length,
-				order_by="weightage desc"
-			)
+			result = self.query_items(self.conditions, self.or_conditions,
+				self.substitutions, start=start)
 
-		# Combine results having 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)
-
-		result = sorted(result, key=lambda x: x.get("weightage"), reverse=True)
-
+		# add price info in results
 		for item in result:
 			product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info')
 			if product_info:
@@ -109,6 +68,51 @@
 
 		return result
 
+	def query_items(self, conditions, or_conditions, substitutions, start=0):
+		"""Build a query to fetch Website Items based on field filters."""
+		return frappe.db.sql("""
+			select distinct {query_fields}
+			from
+				`tabWebsite Item` wi, `tabItem Variant Attribute` iva
+			where
+				wi.published = 1
+				{conditions}
+				{or_conditions}
+			limit {limit} offset {start}
+		""".format(
+				query_fields=self.query_fields,
+				conditions=conditions,
+				or_conditions=or_conditions,
+				limit=self.page_length,
+				start=start),
+			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."""
+		all_items = []
+		self.conditions += " and iva.parent = wi.item_code"
+
+		for attribute, values in attributes.items():
+			if not isinstance(values, list): values = [values]
+
+			conditions_copy = self.conditions
+			substitutions_copy = self.substitutions.copy()
+
+			conditions_copy += " and iva.attribute = '{0}' and iva.attribute_value in ({1})" \
+				.format(attribute, (", ").join(['%s'] * len(values)))
+			substitutions_copy.extend(values)
+
+			items = self.query_items(conditions_copy, self.or_conditions, substitutions_copy, start=start)
+
+			items_dict = {item.name: item for item in items}
+			# TODO: Replace Variants by their parent templates
+
+			all_items.append(set(items_dict.keys()))
+
+		result = [items_dict.get(item) for item in list(set.intersection(*all_items))]
+		return result
+
 	def build_fields_filters(self, filters):
 		"""Build filters for field values
 
@@ -130,10 +134,11 @@
 					self.filters.append([child_doctype, fields[0].fieldname, 'IN', values])
 			elif isinstance(values, list):
 				# If value is a list use `IN` query
-				self.filters.append([field, 'IN', values])
+				self.conditions += " and wi.{0} in ({1})".format(field, (', ').join(['%s'] * len(values)))
+				self.substitutions.extend(values)
 			else:
 				# `=` will be faster than `IN` for most cases
-				self.filters.append([field, '=', values])
+				self.conditions += " and wi.{0} = '{1}'".format(field, values)
 
 	def build_search_filters(self, search_term):
 		"""Query search term in specified fields
@@ -158,4 +163,5 @@
 
 		# Build or filters for query
 		search = '%{}%'.format(search_term)
-		self.or_filters += [[field, 'like', search] for field in search_fields]
+		for field in search_fields:
+			self.or_conditions += " or {0} like '{1}'".format(field, search)
diff --git a/erpnext/shopping_cart/test_shopping_cart.py b/erpnext/shopping_cart/test_shopping_cart.py
index d1284cd..63166c6 100644
--- a/erpnext/shopping_cart/test_shopping_cart.py
+++ b/erpnext/shopping_cart/test_shopping_cart.py
@@ -167,7 +167,7 @@
 
 	# helper functions
 	def enable_shopping_cart(self):
-		settings = frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings")
+		settings = frappe.get_doc("E Commerce Settings", "E Commerce Settings")
 
 		settings.update({
 			"enabled": 1,
@@ -197,7 +197,7 @@
 		frappe.local.shopping_cart_settings = None
 
 	def disable_shopping_cart(self):
-		settings = frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings")
+		settings = frappe.get_doc("E Commerce Settings", "E Commerce Settings")
 		settings.enabled = 0
 		settings.save()
 		frappe.local.shopping_cart_settings = None
diff --git a/erpnext/shopping_cart/utils.py b/erpnext/shopping_cart/utils.py
index f412e61..98b5229 100644
--- a/erpnext/shopping_cart/utils.py
+++ b/erpnext/shopping_cart/utils.py
@@ -1,14 +1,9 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 
-from __future__ import unicode_literals
-
 import frappe
 
-from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import (
-	is_cart_enabled,
-)
-
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import is_cart_enabled
 
 def show_cart_count():
 	if (is_cart_enabled() and
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 8cc9f74..2de4689 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -21,7 +21,6 @@
 	strip,
 )
 from frappe.utils.html_utils import clean_html
-from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow
 from frappe.website.utils import clear_cache
 from frappe.website.website_generator import WebsiteGenerator
 
@@ -131,8 +130,6 @@
 		self.validate_attributes()
 		self.validate_variant_attributes()
 		self.validate_variant_based_on_change()
-		self.validate_website_image()
-		self.make_thumbnail()
 		self.validate_fixed_asset()
 		self.validate_retain_sample()
 		self.validate_uom_conversion_factor()
@@ -141,7 +138,6 @@
 		self.validate_item_defaults()
 		self.validate_auto_reorder_enabled_in_stock_settings()
 		self.cant_change()
-		self.update_show_in_website()
 		self.validate_item_tax_net_rate_range()
 		set_item_tax_from_hsn_code(self)
 
@@ -156,7 +152,6 @@
 		self.validate_name_with_item_group()
 		self.update_variants()
 		self.update_item_price()
-		self.update_template_item()
 
 	def validate_description(self):
 		'''Clean HTML description if set'''
@@ -218,95 +213,6 @@
 
 				stock_entry.add_comment("Comment", _("Opening Stock"))
 
-	def make_route(self):
-		if not self.route:
-			return cstr(frappe.db.get_value('Item Group', self.item_group,
-					'route')) + '/' + self.scrub((self.item_name or self.item_code) + '-' + random_string(5))
-
-	def validate_website_image(self):
-		if frappe.flags.in_import:
-			return
-
-		"""Validate if the website image is a public file"""
-		auto_set_website_image = False
-		if not self.website_image and self.image:
-			auto_set_website_image = True
-			self.website_image = self.image
-
-		if not self.website_image:
-			return
-
-		# find if website image url exists as public
-		file_doc = frappe.get_all("File", filters={
-			"file_url": self.website_image
-		}, fields=["name", "is_private"], order_by="is_private asc", limit_page_length=1)
-
-		if file_doc:
-			file_doc = file_doc[0]
-
-		if not file_doc:
-			if not auto_set_website_image:
-				frappe.msgprint(_("Website Image {0} attached to Item {1} cannot be found").format(self.website_image, self.name))
-
-			self.website_image = None
-
-		elif file_doc.is_private:
-			if not auto_set_website_image:
-				frappe.msgprint(_("Website Image should be a public file or website URL"))
-
-			self.website_image = None
-
-	def make_thumbnail(self):
-		if frappe.flags.in_import:
-			return
-
-		"""Make a thumbnail of `website_image`"""
-		import requests.exceptions
-
-		if not self.is_new() and self.website_image != frappe.db.get_value(self.doctype, self.name, "website_image"):
-			self.thumbnail = None
-
-		if self.website_image and not self.thumbnail:
-			file_doc = None
-
-			try:
-				file_doc = frappe.get_doc("File", {
-					"file_url": self.website_image,
-					"attached_to_doctype": "Item",
-					"attached_to_name": self.name
-				})
-			except frappe.DoesNotExistError:
-				# cleanup
-				frappe.local.message_log.pop()
-
-			except requests.exceptions.HTTPError:
-				frappe.msgprint(_("Warning: Invalid attachment {0}").format(self.website_image))
-				self.website_image = None
-
-			except requests.exceptions.SSLError:
-				frappe.msgprint(
-					_("Warning: Invalid SSL certificate on attachment {0}").format(self.website_image))
-				self.website_image = None
-
-			# for CSV import
-			if self.website_image and not file_doc:
-				try:
-					file_doc = frappe.get_doc({
-						"doctype": "File",
-						"file_url": self.website_image,
-						"attached_to_doctype": "Item",
-						"attached_to_name": self.name
-					}).save()
-
-				except IOError:
-					self.website_image = None
-
-			if file_doc:
-				if not file_doc.thumbnail_url:
-					file_doc.make_thumbnail()
-
-				self.thumbnail = file_doc.thumbnail_url
-
 	def validate_fixed_asset(self):
 		if self.is_fixed_asset:
 			if self.is_stock_item:
@@ -330,167 +236,6 @@
 			frappe.throw(_("{0} Retain Sample is based on batch, please check Has Batch No to retain sample of item").format(
 				self.item_code))
 
-	def get_context(self, context):
-		context.show_search = True
-		context.search_link = '/product_search'
-
-		context.parents = get_parent_item_groups(self.item_group)
-		context.body_class = "product-page"
-
-		self.set_variant_context(context)
-		self.set_attribute_context(context)
-		self.set_disabled_attributes(context)
-		self.set_metatags(context)
-		self.set_shopping_cart_data(context)
-
-		return context
-
-	def set_variant_context(self, context):
-		if self.has_variants:
-			context.no_cache = True
-
-			# load variants
-			# also used in set_attribute_context
-			context.variants = frappe.get_all("Item",
-				 filters={"variant_of": self.name, "show_variant_in_website": 1},
-				 order_by="name asc")
-
-			variant = frappe.form_dict.variant
-			if not variant and context.variants:
-				# the case when the item is opened for the first time from its list
-				variant = context.variants[0]
-
-			if variant:
-				context.variant = frappe.get_doc("Item", variant)
-
-				for fieldname in ("website_image", "website_image_alt", "web_long_description", "description",
-										"website_specifications"):
-					if context.variant.get(fieldname):
-						value = context.variant.get(fieldname)
-						if isinstance(value, list):
-							value = [d.as_dict() for d in value]
-
-						context[fieldname] = value
-
-		if self.slideshow:
-			if context.variant and context.variant.slideshow:
-				context.update(get_slideshow(context.variant))
-			else:
-				context.update(get_slideshow(self))
-
-	def set_attribute_context(self, context):
-		if not self.has_variants:
-			return
-
-		attribute_values_available = {}
-		context.attribute_values = {}
-		context.selected_attributes = {}
-
-		# load attributes
-		for v in context.variants:
-			v.attributes = frappe.get_all("Item Variant Attribute",
-				fields=["attribute", "attribute_value"],
-				filters={"parent": v.name})
-			# make a map for easier access in templates
-			v.attribute_map = frappe._dict({})
-			for attr in v.attributes:
-				v.attribute_map[attr.attribute] = attr.attribute_value
-
-			for attr in v.attributes:
-				values = attribute_values_available.setdefault(attr.attribute, [])
-				if attr.attribute_value not in values:
-					values.append(attr.attribute_value)
-
-				if v.name == context.variant.name:
-					context.selected_attributes[attr.attribute] = attr.attribute_value
-
-		# filter attributes, order based on attribute table
-		for attr in self.attributes:
-			values = context.attribute_values.setdefault(attr.attribute, [])
-
-			if cint(frappe.db.get_value("Item Attribute", attr.attribute, "numeric_values")):
-				for val in sorted(attribute_values_available.get(attr.attribute, []), key=flt):
-					values.append(val)
-
-			else:
-				# get list of values defined (for sequence)
-				for attr_value in frappe.db.get_all("Item Attribute Value",
-					fields=["attribute_value"],
-					filters={"parent": attr.attribute}, order_by="idx asc"):
-
-					if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []):
-						values.append(attr_value.attribute_value)
-
-		context.variant_info = json.dumps(context.variants)
-
-	def set_disabled_attributes(self, context):
-		"""Disable selection options of attribute combinations that do not result in a variant"""
-		if not self.attributes or not self.has_variants:
-			return
-
-		context.disabled_attributes = {}
-		attributes = [attr.attribute for attr in self.attributes]
-
-		def find_variant(combination):
-			for variant in context.variants:
-				if len(variant.attributes) < len(attributes):
-					continue
-
-				if "combination" not in variant:
-					ref_combination = []
-
-					for attr in variant.attributes:
-						idx = attributes.index(attr.attribute)
-						ref_combination.insert(idx, attr.attribute_value)
-
-					variant["combination"] = ref_combination
-
-				if not (set(combination) - set(variant["combination"])):
-					# check if the combination is a subset of a variant combination
-					# eg. [Blue, 0.5] is a possible combination if exists [Blue, Large, 0.5]
-					return True
-
-		for i, attr in enumerate(self.attributes):
-			if i == 0:
-				continue
-
-			combination_source = []
-
-			# loop through previous attributes
-			for prev_attr in self.attributes[:i]:
-				combination_source.append([context.selected_attributes.get(prev_attr.attribute)])
-
-			combination_source.append(context.attribute_values[attr.attribute])
-
-			for combination in itertools.product(*combination_source):
-				if not find_variant(combination):
-					context.disabled_attributes.setdefault(attr.attribute, []).append(combination[-1])
-
-	def set_metatags(self, context):
-		context.metatags = frappe._dict({})
-
-		safe_description = frappe.utils.to_markdown(self.description)
-
-		context.metatags.url = frappe.utils.get_url() + '/' + context.route
-
-		if context.website_image:
-			if context.website_image.startswith('http'):
-				url = context.website_image
-			else:
-				url = frappe.utils.get_url() + context.website_image
-			context.metatags.image = url
-
-		context.metatags.description = safe_description[:300]
-
-		context.metatags.title = self.item_name or self.item_code
-
-		context.metatags['og:type'] = 'product'
-		context.metatags['og:site_name'] = 'ERPNext'
-
-	def set_shopping_cart_data(self, context):
-		from erpnext.shopping_cart.product_info import get_product_info_for_website
-		context.shopping_cart = get_product_info_for_website(self.name, skip_quotation_creation=True)
-
 	def add_default_uom_in_conversion_factor_table(self):
 		uom_conv_list = [d.uom for d in self.get("uoms")]
 		if self.stock_uom not in uom_conv_list:
@@ -505,10 +250,6 @@
 
 		[self.remove(d) for d in to_remove]
 
-	def update_show_in_website(self):
-		if self.disabled:
-			self.show_in_website = False
-
 	def validate_item_tax_net_rate_range(self):
 		for tax in self.get('taxes'):
 			if flt(tax.maximum_net_rate) < flt(tax.minimum_net_rate):
@@ -678,7 +419,7 @@
 		if merge:
 			self.validate_duplicate_item_in_stock_reconciliation(old_name, new_name)
 
-		if self.route:
+		if self.published_in_website:
 			invalidate_cache_for_item(self)
 			clear_cache(self.route)
 
@@ -777,25 +518,6 @@
 				where item_code = %s and docstatus < 2
 			""", (self.description, self.name))
 
-	def update_template_item(self):
-		"""Set Show in Website for Template Item if True for its Variant"""
-		if not self.variant_of:
-			return
-
-		if self.show_in_website:
-			self.show_variant_in_website = 1
-			self.show_in_website = 0
-
-		if self.show_variant_in_website:
-			# show template
-			template_item = frappe.get_doc("Item", self.variant_of)
-
-			if not template_item.show_in_website:
-				template_item.show_in_website = 1
-				template_item.flags.dont_update_variants = True
-				template_item.flags.ignore_permissions = True
-				template_item.save()
-
 	def validate_item_defaults(self):
 		companies = {row.company for row in self.item_defaults}
 
@@ -1065,7 +787,6 @@
 						'item_code': item,
 						'item_name': item,
 						'description': item,
-						'show_in_website': 1,
 						'is_sales_item': 1,
 						'is_purchase_item': 1,
 						'is_stock_item': 1,
diff --git a/erpnext/templates/generators/item/item_image.html b/erpnext/templates/generators/item/item_image.html
index 39a30d0..e9b0916 100644
--- a/erpnext/templates/generators/item/item_image.html
+++ b/erpnext/templates/generators/item/item_image.html
@@ -23,7 +23,7 @@
 		})
 	</script>
 	{% else %}
-	{{ product_image(website_image or image or 'no-image.jpg', alt=website_image_alt or item_name) }}
+	{{ product_image(doc.website_image or doc.image or 'no-image.jpg', alt=doc.website_image_alt or doc.item_name) }}
 	{% endif %}
 
 	<!-- Simple image preview -->
diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html
index 4482bc1..979298f 100644
--- a/erpnext/templates/includes/cart/cart_address.html
+++ b/erpnext/templates/includes/cart/cart_address.html
@@ -4,7 +4,7 @@
 	{% set select_address = True %}
 {% endif %}
 
-{% set show_coupon_code = frappe.db.get_single_value('Shopping Cart Settings', 'show_apply_coupon_code_in_website') %}
+{% set show_coupon_code = frappe.db.get_single_value('E Commerce Settings', 'show_apply_coupon_code_in_website') %}
 {% if show_coupon_code == 1%}
 <div class="mb-3">
 	<div class="row no-gutters">
diff --git a/erpnext/templates/includes/products_as_list.html b/erpnext/templates/includes/products_as_list.html
index 9bf9fd9..976d614 100644
--- a/erpnext/templates/includes/products_as_list.html
+++ b/erpnext/templates/includes/products_as_list.html
@@ -1,4 +1,4 @@
-{% from "erpnext/templates/includes/macros.html" import item_card, item_card_body %}
+{% from "erpnext/templates/includes/macros.html" import item_card, item_card_body, product_image_square %}
 
 <a class="product-link product-list-link" href="{{ route|abs_url }}">
 	<div class='row'>
diff --git a/erpnext/templates/pages/order.py b/erpnext/templates/pages/order.py
index d4e81ab..59df433 100644
--- a/erpnext/templates/pages/order.py
+++ b/erpnext/templates/pages/order.py
@@ -6,10 +6,7 @@
 import frappe
 from frappe import _
 
-from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import (
-	show_attachments,
-)
-
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import show_attachments
 
 def get_context(context):
 	context.no_cache = 1
@@ -26,7 +23,7 @@
 	context.payment_ref = frappe.db.get_value("Payment Request",
 		{"reference_name": frappe.form_dict.name}, "name")
 
-	context.enabled_checkout = frappe.get_doc("Shopping Cart Settings").enable_checkout
+	context.enabled_checkout = frappe.get_doc("E Commerce Settings").enable_checkout
 
 	default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=frappe.form_dict.doctype), "value")
 	if default_print_format:
diff --git a/erpnext/www/all-products/index.js b/erpnext/www/all-products/index.js
index 1c641b5..37e07f4 100644
--- a/erpnext/www/all-products/index.js
+++ b/erpnext/www/all-products/index.js
@@ -129,7 +129,7 @@
 				Object.assign(field_filters, { item_group });
 			}
 			return new Promise((resolve, reject) => {
-				frappe.call('erpnext.portal.product_configurator.utils.get_products_html_for_website', args)
+				frappe.call('erpnext.www.all-products.index.get_products_html_for_website', args)
 					.then(r => {
 						if (r.exc) reject(r.exc);
 						else resolve(r.message);
diff --git a/erpnext/www/all-products/index.py b/erpnext/www/all-products/index.py
index 67d51ca..688c029 100644
--- a/erpnext/www/all-products/index.py
+++ b/erpnext/www/all-products/index.py
@@ -1,9 +1,7 @@
 import frappe
-
-from erpnext.portal.product_configurator.utils import (get_products_for_website, get_e_commerce_settings,
-	get_field_filter_data, get_attribute_filter_data)
-from erpnext.shopping_cart.filters import ProductFiltersBuilder
+from frappe.utils import cint
 from erpnext.shopping_cart.product_query import ProductQuery
+from erpnext.shopping_cart.filters import ProductFiltersBuilder
 
 sitemap = 1
 
@@ -13,7 +11,7 @@
 		search = frappe.form_dict.search
 		field_filters = frappe.parse_json(frappe.form_dict.field_filters)
 		attribute_filters = frappe.parse_json(frappe.form_dict.attribute_filters)
-		start = frappe.parse_json(frappe.form_dict.start)
+		start = cint(frappe.parse_json(frappe.form_dict.start))
 	else:
 		search = field_filters = attribute_filters = None
 		start = 0
@@ -24,15 +22,34 @@
 	# Add homepage as parent
 	context.parents = [{"name": frappe._("Home"), "route":"/"}]
 
-	e_commerce_settings = get_e_commerce_settings()
 	filter_engine = ProductFiltersBuilder()
 
 	context.field_filters = filter_engine.get_field_filters()
 	context.attribute_filters = filter_engine.get_attribute_filters()
 
-	context.e_commerce_settings = e_commerce_settings
+	context.e_commerce_settings = engine.settings
 	context.body_class = "product-page"
-	context.page_length = e_commerce_settings.products_per_page or 20
+	context.page_length = engine.settings.products_per_page or 20
 
 	context.no_cache = 1
-	print(context)
+
+@frappe.whitelist(allow_guest=True)
+def get_products_html_for_website(field_filters=None, attribute_filters=None):
+	"""Get Products on filter change."""
+	field_filters = frappe.parse_json(field_filters)
+	attribute_filters = frappe.parse_json(attribute_filters)
+
+	engine = ProductQuery()
+	items = engine.query(attribute_filters, field_filters, search_term=None, start=0)
+
+	item_html = []
+	for item in items:
+		item_html.append(frappe.render_template('erpnext/www/all-products/item_row.html', {
+			'item': item
+		}))
+	html = ''.join(item_html)
+
+	if not items:
+		html = frappe.render_template('erpnext/www/all-products/not_found.html', {})
+
+	return html