diff --git a/community/hooks.py b/community/hooks.py
index fba4d694..0727f9ca 100644
--- a/community/hooks.py
+++ b/community/hooks.py
@@ -169,3 +169,5 @@ profile_rules = [
]
website_route_rules = primary_rules + whitelist_rules + profile_rules
+
+update_website_context = 'community.widgets.update_website_context'
diff --git a/community/lms/widgets/HelloWorld.html b/community/lms/widgets/HelloWorld.html
new file mode 100644
index 00000000..a8e09c01
--- /dev/null
+++ b/community/lms/widgets/HelloWorld.html
@@ -0,0 +1,14 @@
+{#
+ Widget to demonostrate how to write a widget.
+
+ A wiget is a reusable template, that can be used in
+ other templates.
+
+ To this widget can be called as:
+
+ {{ widgets.HelloWorld(name="World") }}
+#}
+
+
+ Hello, {{ name }}!
+
diff --git a/community/test_widgets.py b/community/test_widgets.py
new file mode 100644
index 00000000..7d810472
--- /dev/null
+++ b/community/test_widgets.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, FOSS United and Contributors
+# See license.txt
+import frappe
+import unittest
+from .widgets import Widget, Widgets
+
+class TestWidgets(unittest.TestCase):
+ def test_Widgets(self):
+ widgets = Widgets()
+ assert widgets.Foo.name == "Foo"
+ assert widgets.Bar.name == "Bar"
+
+ def _test_Widget(self):
+ hello = Widget("HelloWorld")
+ assert hello(name="Test") == "Hello, Test"
diff --git a/community/widgets.py b/community/widgets.py
new file mode 100644
index 00000000..b5475ecc
--- /dev/null
+++ b/community/widgets.py
@@ -0,0 +1,60 @@
+"""The widgets provides access to HTML widgets
+provided in each frappe module.
+
+Widgets are simple moduler templates that can reused
+in multiple places. These are like macros, but accessing
+them will be a lot easier.
+
+The widgets will be provided
+"""
+import frappe
+from frappe.utils.jinja import get_jenv
+
+# search path for widgets.
+# When {{widgets.SomeWidget()}} is called, it looks for
+# widgets/SomeWidgets.html in each of these modules.
+MODULES = [
+ "lms"
+]
+
+def update_website_context(context):
+ """Adds widgets to the context.
+
+ Called from hooks.
+ """
+ context.widgets = Widgets()
+
+class Widgets:
+ """The widget collection.
+
+ This is just a placeholder object and returns the appropriate
+ widget when accessed using attribute.
+
+ >>> widgets = Widgets()
+ >>> widgets.HelloWorld(name="World!")
+ 'Hello, World!
'
+ """
+ def __getattr__(self, name):
+ if not name.startswith("__"):
+ return Widget(name)
+ else:
+ raise AttributeError(name)
+
+class Widget:
+ """The Widget class renders a widget.
+
+ Widget is a reusable template defined in widgets/ directory in
+ each frappe module.
+
+ >>> w = Widget("HelloWorld")
+ >>> w(name="World!")
+ 'Hello, World!
'
+ """
+ def __init__(self, name):
+ self.name = name
+
+ def __call__(self, **kwargs):
+ # the widget could be in any of the modules
+ paths = [f"{module}/widgets/{self.name}.html" for module in MODULES]
+ env = get_jenv()
+ return env.get_or_select_template(paths).render(kwargs)