Item, Item Group and Partner Web Page generators moved from Shopping Cart to ERPNext
diff --git a/erpnext/selling/doctype/lead/test_records.json b/erpnext/selling/doctype/lead/test_records.json
index c286a12..ce94604 100644
--- a/erpnext/selling/doctype/lead/test_records.json
+++ b/erpnext/selling/doctype/lead/test_records.json
@@ -4,7 +4,7 @@
   "email_id": "test_lead@example.com",
   "lead_name": "_Test Lead",
   "status": "Open",
-  "territory": "_Test Territory Rest Of The World"
+  "territory": "_Test Territory"
  },
  {
   "doctype": "Lead",
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index 91a5efc..fcdd4b2 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -6,6 +6,7 @@
 
 from frappe.utils.nestedset import NestedSet
 from frappe.website.website_generator import WebsiteGenerator
+from frappe.website.render import clear_cache
 
 class ItemGroup(NestedSet, WebsiteGenerator):
 	nsm_parent_field = 'parent_item_group'
@@ -19,7 +20,9 @@
 				self.parent_item_group)
 
 	def on_update(self):
-		super(ItemGroup, self).on_update()
+		NestedSet.on_update(self)
+		WebsiteGenerator.on_update(self)
+		invalidate_cache_for(self)
 		self.validate_name_with_item()
 		self.validate_one_root()
 
@@ -32,3 +35,18 @@
 	def validate_name_with_item(self):
 		if frappe.db.exists("Item", self.name):
 			frappe.throw(frappe._("An item exists with same name ({0}), please change the item group name or rename the item").format(self.name))
+
+def get_parent_item_groups(item_group_name):
+	item_group = frappe.get_doc("Item Group", item_group_name)
+	return frappe.db.sql("""select name, page_name from `tabItem Group`
+		where lft <= %s and rgt >= %s
+		and ifnull(show_in_website,0)=1
+		order by lft asc""", (item_group.lft, item_group.rgt), as_dict=True)
+
+def invalidate_cache_for(doc, item_group=None):
+	if not item_group:
+		item_group = doc.name
+
+	for i in get_parent_item_groups(item_group):
+		if i.page_name:
+			clear_cache(i.page_name)
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index b945309..26d827f 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -6,6 +6,8 @@
 from frappe import msgprint, _
 from frappe.utils import cstr, flt, getdate, now_datetime, formatdate
 from frappe.website.website_generator import WebsiteGenerator
+from erpnext.setup.doctype.item_group.item_group import invalidate_cache_for
+from frappe.website.render import clear_cache
 
 class WarehouseNotSet(frappe.ValidationError): pass
 
@@ -46,6 +48,7 @@
 
 	def on_update(self):
 		super(Item, self).on_update()
+		invalidate_cache_for_item(self)
 		self.validate_name_with_item_group()
 		self.update_item_price()
 
@@ -230,6 +233,9 @@
 
 	def after_rename(self, olddn, newdn, merge):
 		super(Item, self).after_rename(olddn, newdn, merge)
+		if self.page_name:
+			invalidate_cache_for_item(self)
+			clear_cache(self.page_name)
 
 		frappe.db.set_value("Item", newdn, "item_code", newdn)
 		if merge:
@@ -344,3 +350,8 @@
 	})
 
 	return out
+
+def invalidate_cache_for_item(doc):
+	invalidate_cache_for(doc, doc.item_group)
+	for d in doc.get({"doctype":"Website Item Group"}):
+		invalidate_cache_for(doc, d.item_group)
diff --git a/erpnext/templates/generators/__init__.py b/erpnext/templates/generators/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/templates/generators/__init__.py
diff --git a/erpnext/templates/generators/item.html b/erpnext/templates/generators/item.html
new file mode 100644
index 0000000..4d4f5d4
--- /dev/null
+++ b/erpnext/templates/generators/item.html
@@ -0,0 +1,84 @@
+{% block title %} {{ title }} {% endblock %}
+
+{% block header %}<h2>{{ title }}</h2>{% endblock %}
+
+{% block content %}
+<div class="item-content">
+	{% include 'templates/includes/product_search_box.html' %}
+	{% include 'templates/includes/product_breadcrumbs.html' %}
+	<div class="product-page-content" itemscope itemtype="http://schema.org/Product">
+		<div class="row">
+			<div class="col-md-6">
+				{% if slideshow %}
+					{% include "templates/includes/slideshow.html" %}
+				{% else %}
+					{% if website_image %}
+					<image itemprop="image" class="item-main-image"
+						src="{{ website_image }}" />
+					{% else %}
+					<div class="img-area">
+		{% include 'templates/includes/product_missing_image.html' %}
+					</div>
+					{% endif %}
+				{% endif %}
+			</div>
+			<div class="col-md-6">
+				<h3 itemprop="name" style="margin-top: 0px;">{{ item_name }}</h3>
+				<p class="help">Item Code: <span itemprop="productID">{{ name }}</span></p>
+				<h4>Product Description</h4>
+				<div itemprop="description">
+				{{ web_long_description or description or "[No description given]" }}
+				</div>
+				<div style="min-height: 100px; margin: 10px 0;">
+					<div class="item-price-info" style="display: none;">
+						<h4 class="item-price" itemprop="price"></h4>
+						<div class="item-stock" itemprop="availablity"></div>
+						<div id="item-add-to-cart">
+							<button class="btn btn-primary">
+								<i class="icon-shopping-cart"></i> Add to Cart</button>
+						</div>
+						<div id="item-update-cart" class="input-group col-md-4" style="display: none;
+							padding-left: 0px; padding-right: 0px;">
+							<input class="form-control" type="text">
+							<div class="input-group-btn">
+								<button class="btn btn-primary">
+									<i class="icon-ok"></i></button>
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+		{% if doc.get({"doctype":"Item Website Specification"}) -%}
+		<div class="row" style="margin-top: 20px">
+			<div class="col-md-12">
+				<h4>Specifications</h4>
+				<table class="table table-bordered" style="width: 100%">
+				{% for d in doc.get(
+					{"doctype":"Item Website Specification"}) -%}
+					<tr>
+						<td style="width: 30%;">{{ d.label }}</td>
+						<td>{{ d.description }}</td>
+					</tr>
+				{%- endfor %}
+				</table>
+			</div>
+		</div>
+		{%- endif %}
+	</div>
+</div>
+<script>
+	{% include "templates/includes/product_page.js" %}
+
+	$(function() {
+		if(window.logged_in && getCookie("system_user")==="yes") {
+			frappe.has_permission("Item", "{{ name }}", "write", function(r) {
+				frappe.require("/assets/frappe/js/frappe/website/editable.js");
+				frappe.make_editable($('[itemprop="description"]'), "Item", "{{ name }}", "web_long_description");
+			});
+		}
+	});
+</script>
+{% endblock %}
+
+{% block sidebar %}{% include "templates/includes/sidebar.html" %}{% endblock %}
diff --git a/erpnext/templates/generators/item.py b/erpnext/templates/generators/item.py
new file mode 100644
index 0000000..1ad070e
--- /dev/null
+++ b/erpnext/templates/generators/item.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+from erpnext.setup.doctype.item_group.item_group import get_parent_item_groups
+from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow
+
+doctype = "Item"
+condition_field = "show_in_website"
+
+def get_context(context):
+	item_context = context.doc.as_dict()
+	item_context["parent_groups"] = get_parent_item_groups(context.doc.item_group) + \
+		[{"name":context.doc.name}]
+	if context.doc.slideshow:
+		item_context.update(get_slideshow(context.doc))
+
+	return item_context
diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html
new file mode 100644
index 0000000..ae0cd22
--- /dev/null
+++ b/erpnext/templates/generators/item_group.html
@@ -0,0 +1,58 @@
+{% block title %} {{ title }} {% endblock %}
+
+{% block header %}<h2>{{ title }}</h2>{% endblock %}
+
+{% block content %}
+<div class="item-group-content">
+	{% include 'templates/includes/product_search_box.html' %}
+	{% include 'templates/includes/product_breadcrumbs.html' %}
+	<div>
+		{% if slideshow %}<!-- slideshow -->
+		{% include "templates/includes/slideshow.html" %}
+		{% endif %}
+		{% if description %}<!-- description -->
+		<div itemprop="description">{{ description or ""}}</div>
+		{% else %}
+		<h3>{{ name }}</h3>
+		{% endif %}
+	</div>
+	<div>
+		{% if sub_groups %}
+		<hr />
+		<div class="row">
+		{% for d in sub_groups %}
+			<div class="col-sm-4">
+				<a href="{{ d.page_name }}">{{ d.name }} ({{ d.count }})</a>
+			</div>
+		{% endfor %}
+		</div>
+		<hr />
+		{% endif %}
+		{% if items %}
+		<div id="search-list" class="row">
+			{% for item in items %}
+				{{ item }}
+			{% endfor %}
+		</div>
+			{% if (items|length)==100 %}
+				<div class="alert alert-info info">Showing top 100 items.</div>
+			{% endif %}
+		{% else %}
+			<div class="alert alert-warning">No items listed.</div>
+		{% endif %}
+	</div>	
+</div>
+<script>
+$(function() {
+	if(window.logged_in && getCookie("system_user")==="yes") {
+		frappe.has_permission("Item Group", "{{ name }}", "write", function(r) {
+			frappe.require("/assets/frappe/js/frappe/website/editable.js");
+			frappe.make_editable($('[itemprop="description"]'), "Item Group", "{{ name }}", "description");
+		});
+	}
+});
+</script>
+
+{% endblock %}
+
+{% block sidebar %}{% include "templates/includes/sidebar.html" %}{% endblock %}
\ No newline at end of file
diff --git a/erpnext/templates/generators/item_group.py b/erpnext/templates/generators/item_group.py
new file mode 100644
index 0000000..7743018
--- /dev/null
+++ b/erpnext/templates/generators/item_group.py
@@ -0,0 +1,64 @@
+# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow
+from erpnext.setup.doctype.item_group.item_group import get_parent_item_groups
+
+doctype = "Item Group"
+condition_field = "show_in_website"
+
+def get_context(context):
+	item_group_context = context.doc.as_dict()
+	item_group_context.update({
+		"sub_groups": frappe.db.sql("""select name, page_name
+			from `tabItem Group` where parent_item_group=%s
+			and ifnull(show_in_website,0)=1""", context.docname, as_dict=1),
+		"items": get_product_list_for_group(product_group = context.docname, limit=100),
+		"parent_groups": get_parent_item_groups(context.docname),
+		"title": context.docname
+	})
+
+	if context.doc.slideshow:
+		item_group_context.update(get_slideshow(context.doc))
+
+	for d in item_group_context.sub_groups:
+		d.count = get_group_item_count(d.name)
+
+	return item_group_context
+
+def get_product_list_for_group(product_group=None, start=0, limit=10):
+	child_groups = ", ".join(['"' + i[0] + '"' for i in get_child_groups(product_group)])
+
+	# base query
+	query = """select name, item_name, page_name, website_image, item_group,
+			web_long_description as website_description
+		from `tabItem` where docstatus = 0 and show_in_website = 1
+		and (item_group in (%s)
+			or name in (select parent from `tabWebsite Item Group` where item_group in (%s))) """ % (child_groups, child_groups)
+
+	query += """order by weightage desc, modified desc limit %s, %s""" % (start, limit)
+
+	data = frappe.db.sql(query, {"product_group": product_group}, as_dict=1)
+
+	return [get_item_for_list_in_html(r) for r in data]
+
+def get_child_groups(item_group_name):
+	item_group = frappe.get_doc("Item Group", item_group_name)
+	return frappe.db.sql("""select name
+		from `tabItem Group` where lft>=%(lft)s and rgt<=%(rgt)s
+			and show_in_website = 1""", item_group.as_dict())
+
+def get_item_for_list_in_html(context):
+	return frappe.get_template("templates/includes/product_in_grid.html").render(context)
+
+def get_group_item_count(item_group):
+	child_groups = ", ".join(['"' + i[0] + '"' for i in get_child_groups(item_group)])
+	return frappe.db.sql("""select count(*) from `tabItem`
+		where docstatus = 0 and show_in_website = 1
+		and (item_group in (%s)
+			or name in (select parent from `tabWebsite Item Group`
+				where item_group in (%s))) """ % (child_groups, child_groups))[0][0]
+
diff --git a/erpnext/templates/generators/partner.html b/erpnext/templates/generators/partner.html
new file mode 100644
index 0000000..6a869a1
--- /dev/null
+++ b/erpnext/templates/generators/partner.html
@@ -0,0 +1,29 @@
+{% block title %} {{ title }} {% endblock %}
+
+{% block header %}<h2>{{ title }}</h2>{% endblock %}
+
+{% block content %}
+<div class="partner-content" itemscope itemtype="http://schema.org/Organization">
+	<div class="row">
+		<div class="col-md-4">
+			{% if logo -%}
+			<img itemprop="brand" src="{{ logo }}" class="partner-logo" 
+				alt="{{ partner_name }}" title="{{ partner_name }}" />
+			<br><br>
+			{%- endif %}
+			<address>
+				{% if partner_website -%}<p><a href="{{ partner_website }}" 
+					target="_blank">{{ partner_website }}</a></p>{%- endif %}
+				{% if partner_address -%}<p itemprop="address">{{ partner_address }}</p>{%- endif %}
+				{% if phone -%}<p itemprop="telephone">{{ phone }}</p>{%- endif %}
+				{% if email -%}<p itemprop="email"><span class="icon-envelope"></span> {{ email }}</p>{%- endif %}
+			</address>
+		</div>
+		<div class="col-md-8">
+			<p>{{ description }}</p>
+		</div>
+	</div>
+</div>
+{% endblock %}
+
+{% block sidebar %}{% include "templates/includes/sidebar.html" %}{% endblock %}
\ No newline at end of file
diff --git a/erpnext/templates/generators/partner.py b/erpnext/templates/generators/partner.py
new file mode 100644
index 0000000..2079309
--- /dev/null
+++ b/erpnext/templates/generators/partner.py
@@ -0,0 +1,28 @@
+# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.utils import filter_strip_join
+
+doctype = "Sales Partner"
+condition_field = "show_in_website"
+
+def get_context(context):
+	partner_context = context.doc.as_dict()
+	
+	address = frappe.db.get_value("Address", 
+		{"sales_partner": context.doc.name, "is_primary_address": 1}, 
+		"*", as_dict=True)
+	if address:
+		city_state = ", ".join(filter(None, [address.city, address.state]))
+		address_rows = [address.address_line1, address.address_line2,
+			city_state, address.pincode, address.country]
+			
+		partner_context.update({
+			"email": address.email_id,
+			"partner_address": filter_strip_join(address_rows, "\n<br>"),
+			"phone": filter_strip_join(cstr(address.phone).split(","), "\n<br>")
+		})
+	
+	return partner_context
diff --git a/erpnext/templates/includes/__init__.py b/erpnext/templates/includes/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/templates/includes/__init__.py
diff --git a/erpnext/templates/includes/product_breadcrumbs.html b/erpnext/templates/includes/product_breadcrumbs.html
new file mode 100644
index 0000000..b126847
--- /dev/null
+++ b/erpnext/templates/includes/product_breadcrumbs.html
@@ -0,0 +1,10 @@
+{% if parent_groups and (parent_groups|length) > 1 %}
+<div class="container">
+	<ul class="breadcrumb">
+		{% for ig in parent_groups[:-1] %}
+		<li><a href="{{ ig.page_name }}.html">{{ ig.name }}</a></li>
+		{% endfor %}
+		<li class="active">{{ parent_groups[-1].name }}</li>
+	</ul>
+</div>
+{% endif %}
\ No newline at end of file
diff --git a/erpnext/templates/includes/product_in_grid.html b/erpnext/templates/includes/product_in_grid.html
new file mode 100644
index 0000000..9ef4bbd
--- /dev/null
+++ b/erpnext/templates/includes/product_in_grid.html
@@ -0,0 +1,14 @@
+<div class="col-sm-3">
+	<div style="height: 120px; overflow: hidden;">
+		<a href="{{ page_name }}">
+		{%- if website_image -%}
+		<img class="product-image" style="width: 80%; margin: auto;" src="{{ website_image }}">
+		{%- else -%}
+		{% include 'templates/includes/product_missing_image.html' %}
+		{%- endif -%}
+		</a>
+	</div>
+	<div style="height: 100px; overflow: hidden; font-size: 80%;">
+		<h4 style="margin-bottom: 2px;"><a href="{{ page_name }}">{{ item_name }}</a></h4>
+	</div>
+</div>
\ No newline at end of file
diff --git a/erpnext/templates/includes/product_in_list.html b/erpnext/templates/includes/product_in_list.html
new file mode 100644
index 0000000..116124a
--- /dev/null
+++ b/erpnext/templates/includes/product_in_list.html
@@ -0,0 +1,15 @@
+<!-- TODO product listing -->
+<div class="container content">
+	<div style="height: 120px; overflow: hidden;">
+		<a href="{{ page_name }}">
+		{%- if website_image -%}
+		<img class="product-image" style="width: 80%; margin: auto;" src="{{ website_image }}">
+		{%- else -%}
+		{% include 'templates/includes/product_missing_image.html' %}
+		{%- endif -%}
+		</a>
+	</div>
+	<div style="height: 100px; overflow: hidden; font-size: 80%;">
+		<h4 style="margin-bottom: 2px;"><a href="{{ page_name }}">{{ item_name }}</a></h4>
+	</div>
+</div>
\ No newline at end of file
diff --git a/erpnext/templates/includes/product_list.js b/erpnext/templates/includes/product_list.js
new file mode 100644
index 0000000..3d19d5a
--- /dev/null
+++ b/erpnext/templates/includes/product_list.js
@@ -0,0 +1,52 @@
+// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
+// License: GNU General Public License v3. See license.txt
+
+window.get_product_list = function() {
+	$(".more-btn .btn").click(function() {
+		window.get_product_list()
+	});
+
+	if(window.start==undefined) {
+		throw "product list not initialized (no start)"
+	}
+
+	$.ajax({
+		method: "GET",
+		url: "/",
+		dataType: "json",
+		data: {
+			cmd: "erpnext.templates.pages.product_search.get_product_list",
+			start: window.start,
+			search: window.search,
+			product_group: window.product_group
+		},
+		dataType: "json",
+		success: function(data) {
+			window.render_product_list(data.message);
+		}
+	})
+}
+
+window.render_product_list = function(data) {
+	if(data.length) {
+		var table = $("#search-list .table");
+		if(!table.length)
+			var table = $("<table class='table'>").appendTo("#search-list");
+
+		$.each(data, function(i, d) {
+			$(d).appendTo(table);
+		});
+	}
+	if(data.length < 10) {
+		if(!table) {
+			$(".more-btn")
+				.replaceWith("<div class='alert alert-warning'>No products found.</div>");
+		} else {
+			$(".more-btn")
+				.replaceWith("<div class='text-muted'>Nothing more to show.</div>");
+		}
+	} else {
+		$(".more-btn").toggle(true)
+	}
+	window.start += (data.length || 0);
+}
diff --git a/erpnext/templates/includes/product_missing_image.html b/erpnext/templates/includes/product_missing_image.html
new file mode 100644
index 0000000..81b8935
--- /dev/null
+++ b/erpnext/templates/includes/product_missing_image.html
@@ -0,0 +1 @@
+<div class="missing-image"><i class="icon-camera"></i></div>
\ No newline at end of file
diff --git a/erpnext/templates/includes/product_page.js b/erpnext/templates/includes/product_page.js
new file mode 100644
index 0000000..42d4ae7
--- /dev/null
+++ b/erpnext/templates/includes/product_page.js
@@ -0,0 +1,78 @@
+// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
+// License: GNU General Public License v3. See license.txt
+
+$(document).ready(function() {
+	var item_code = $('[itemscope] [itemprop="productID"]').text().trim();
+	var qty = 0;
+	
+	frappe.call({
+		type: "POST",
+		method: "shopping_cart.shopping_cart.product.get_product_info",
+		args: {
+			item_code: "{{ name }}"
+		},
+		callback: function(r) {
+			if(r.message && r.message.price) {
+				$(".item-price")
+					.html(r.message.price.formatted_price + " per " + r.message.uom);
+				
+				if(r.message.stock==0) {
+					$(".item-stock").html("<div class='help'>Not in stock</div>");
+				}
+				else if(r.message.stock==1) {
+					$(".item-stock").html("<div style='color: green'>\
+						<i class='icon-check'></i> Available (in stock)</div>");
+				}
+				
+				$(".item-price-info").toggle(true);
+				
+				if(r.message.qty) {
+					qty = r.message.qty;
+					toggle_update_cart(qty);
+					$("#item-update-cart input").val(qty);
+				}
+			}
+		}
+	})
+	
+	$("#item-add-to-cart button").on("click", function() {
+		shopping_cart.update_cart({
+			item_code: item_code,
+			qty: 1,
+			callback: function(r) {
+				if(!r.exc) {
+					toggle_update_cart(1);
+					qty = 1;
+				}
+			},
+			btn: this, 
+		});
+	});
+	
+	$("#item-update-cart button").on("click", function() {
+		shopping_cart.update_cart({
+			item_code: item_code,
+			qty: $("#item-update-cart input").val(),
+			btn: this,
+			callback: function(r) {
+				if(r.exc) {
+					$("#item-update-cart input").val(qty);
+				} else {
+					qty = $("#item-update-cart input").val();
+				}
+			},
+		});
+	});
+	
+	if(localStorage && localStorage.getItem("pending_add_to_cart") && full_name) {
+		localStorage.removeItem("pending_add_to_cart");
+		$("#item-add-to-cart button").trigger("click");
+	}
+});
+
+var toggle_update_cart = function(qty) {
+	$("#item-add-to-cart").toggle(qty ? false : true);
+	$("#item-update-cart")
+		.toggle(qty ? true : false)
+		.find("input").val(qty);
+}
\ No newline at end of file
diff --git a/erpnext/templates/includes/product_search_box.html b/erpnext/templates/includes/product_search_box.html
new file mode 100644
index 0000000..0f44eea
--- /dev/null
+++ b/erpnext/templates/includes/product_search_box.html
@@ -0,0 +1,28 @@
+<div class="container" style="margin-bottom: 7px;">
+	<form class="form-inline form-search row">
+		<div class="input-group col-md-4 col-md-offset-8">
+			<input class="form-control" type="text" id="product-search" placeholder="Product Search...">
+			<span class="input-group-btn">
+				<button class="btn btn-default" type="button" id="btn-product-search">
+					<i class="icon-search"></i></button>
+			</span>
+		</div>
+	</form>
+	<script>
+		// redirect to product search page
+		$(document).ready(function() {
+			$('.dropdown-toggle').dropdown();
+			$("#btn-product-search").click(function() {
+				var txt = $("#product-search").val();
+				if(txt) {
+					window.location.href="product_search?q=" + txt;
+				}
+				return false;
+			});
+			$("#product-search").keypress(function(e) {
+				if(e.which==13) $("#btn-product-search").click();
+			});
+			$(".form-search").on("submit", function() { return false; });
+		});
+	</script>
+</div>
\ No newline at end of file
diff --git a/erpnext/templates/pages/__init__.py b/erpnext/templates/pages/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/templates/pages/__init__.py
diff --git a/erpnext/templates/pages/partners.html b/erpnext/templates/pages/partners.html
new file mode 100644
index 0000000..089c15b
--- /dev/null
+++ b/erpnext/templates/pages/partners.html
@@ -0,0 +1,30 @@
+{% block title %} {{ title }} {% endblock %}
+
+{% block header %}<h2>{{ title }}</h2>{% endblock %}
+
+{% block content %}
+<div class="partners-content">
+	{% for partner_info in partners %}
+	<div class="row">
+		<div class="col-md-3">
+			{% if partner_info.logo -%}
+			<a href="{{ partner_info.page_name }}">
+				<img itemprop="brand" src="{{ partner_info.logo }}" class="partner-logo" 
+					alt="{{ partner_info.partner_name }}" title="{{ partner_info.partner_name }}" />
+			</a>
+			{%- endif %}
+		</div>
+		<div class="col-md-9">
+			<a href="{{ partner_info.page_name }}">
+				<h4>{{ partner_info.partner_name }}</h4>
+			</a>
+			<p style="color: #999">{{ partner_info.territory }} - {{ partner_info.partner_type }}</p>
+			<p>{{ partner_info.introduction }}</p>
+		</div>
+	</div>
+	<hr>
+	{% endfor %}
+</div>
+{% endblock %}
+
+{% block sidebar %}{% include "templates/includes/sidebar.html" %}{% endblock %}
\ No newline at end of file
diff --git a/erpnext/templates/pages/partners.py b/erpnext/templates/pages/partners.py
new file mode 100644
index 0000000..94b553c
--- /dev/null
+++ b/erpnext/templates/pages/partners.py
@@ -0,0 +1,13 @@
+# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+import frappe.website.render
+
+def get_context(context):
+	return {
+		"partners": frappe.db.sql("""select * from `tabSales Partner`
+			where show_in_website=1 order by name asc""", as_dict=True),
+		"title": "Partners"
+	}
diff --git a/erpnext/templates/pages/product_search.html b/erpnext/templates/pages/product_search.html
new file mode 100644
index 0000000..8e57cb5
--- /dev/null
+++ b/erpnext/templates/pages/product_search.html
@@ -0,0 +1,33 @@
+{% block title %} {{ "Product Search" }} {% endblock %}
+
+{% block header %}<h2>Product Search</h2>{% endblock %}
+
+{% block content %}
+<script>{% include "templates/includes/product_list.js" %}</script>
+
+<script>
+$(document).ready(function() {
+	var txt = get_url_arg("q");
+	$(".search-results").html("Search results for: " + txt);
+	window.search = txt;
+	window.start = 0;
+	window.get_product_list();
+});
+</script>
+
+{% include "templates/includes/product_search_box.html" %}
+<div class="product-search-content">
+	<h3 class="search-results">Search Results</h3>
+	<div id="search-list" class="row">
+		
+	</div>
+	<div style="text-align: center;">
+		<div class="more-btn" 
+			style="display: none; text-align: center;">
+			<button class="btn">More...</button>
+		</div>
+	</div>
+</div>
+{% endblock %}
+
+{% block sidebar %}{% include "templates/includes/sidebar.html" %}{% endblock %}
\ No newline at end of file
diff --git a/erpnext/templates/pages/product_search.py b/erpnext/templates/pages/product_search.py
new file mode 100644
index 0000000..8d4a8f6
--- /dev/null
+++ b/erpnext/templates/pages/product_search.py
@@ -0,0 +1,33 @@
+# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.utils import cstr
+from erpnext.templates.generators.item_group import get_item_for_list_in_html
+
+no_cache = 1
+no_sitemap = 1
+
+@frappe.whitelist(allow_guest=True)
+def get_product_list(search=None, start=0, limit=10):
+	# base query
+	query = """select name, item_name, page_name, website_image, item_group,
+			web_long_description as website_description
+		from `tabItem` where docstatus = 0 and show_in_website = 1 """
+
+	# search term condition
+	if search:
+		query += """and (web_long_description like %(search)s or
+				item_name like %(search)s or name like %(search)s)"""
+		search = "%" + cstr(search) + "%"
+
+	# order by
+	query += """order by weightage desc, modified desc limit %s, %s""" % (start, limit)
+
+	data = frappe.db.sql(query, {
+		"search": search,
+	}, as_dict=1)
+
+	return [get_item_for_list_in_html(r) for r in data]
+
diff --git a/erpnext/templates/utils.py b/erpnext/templates/utils.py
index e98a5c0..7ba4a9d 100644
--- a/erpnext/templates/utils.py
+++ b/erpnext/templates/utils.py
@@ -7,10 +7,10 @@
 @frappe.whitelist(allow_guest=True)
 def send_message(subject="Website Query", message="", sender="", status="Open"):
 	from frappe.templates.pages.contact import send_message as website_send_message
-	
+
 	if not website_send_message(subject, message, sender):
 		return
-		
+
 	if subject=="Support":
 		# create support ticket
 		from erpnext.support.doctype.support_ticket.get_support_mails import add_support_communication
@@ -18,6 +18,6 @@
 	else:
 		# make lead / communication
 		from erpnext.selling.doctype.lead.get_leads import add_sales_communication
-		add_sales_communication(subject or "Website Query", message, sender, sender, 
+		add_sales_communication(subject or "Website Query", message, sender, sender,
 			mail=None, status=status)
-	
\ No newline at end of file
+