diff --git a/frappe/website/page_renderers/portal_renderer.py b/frappe/website/page_renderers/portal_renderer.py new file mode 100644 index 0000000000..c2369e341c --- /dev/null +++ b/frappe/website/page_renderers/portal_renderer.py @@ -0,0 +1,31 @@ +import frappe +from frappe.website.page_renderers.template_page import TemplatePage +from frappe.website.utils import check_if_webform_exists + + +class PortalPage(TemplatePage): + def can_render(self): + parts = self.path.split("/", 1) + if len(parts) >= 2: + menu_item = frappe.db.exists( + "Portal Menu Item", + {"route": f"/{parts[1]}"}, + ) + + if check_if_webform_exists(parts[1]): + return False + + if parts[0] == "portal" and menu_item: + self.ref_doctype = frappe.db.get_value( + "Portal Menu Item", + menu_item, + "reference_doctype", + ) + return self.ref_doctype + + return False + + def render(self): + frappe.form_dict.doctype = self.ref_doctype + self.set_standard_path("portal") + return super().render() diff --git a/frappe/website/path_resolver.py b/frappe/website/path_resolver.py index 2f71cc55be..bd5daec4da 100644 --- a/frappe/website/path_resolver.py +++ b/frappe/website/path_resolver.py @@ -7,13 +7,14 @@ from werkzeug.routing import Rule import frappe from frappe.website.page_renderers.document_page import DocumentPage from frappe.website.page_renderers.not_found_page import NotFoundPage +from frappe.website.page_renderers.portal_renderer import PortalPage from frappe.website.page_renderers.print_page import PrintPage from frappe.website.page_renderers.redirect_page import RedirectPage from frappe.website.page_renderers.static_page import StaticPage from frappe.website.page_renderers.template_page import TemplatePage from frappe.website.page_renderers.web_form import WebFormPage from frappe.website.router import evaluate_dynamic_routes -from frappe.website.utils import can_cache, get_home_page +from frappe.website.utils import can_cache, check_if_webform_exists, get_home_page class PathResolver: @@ -60,6 +61,7 @@ class PathResolver: DocumentPage, TemplatePage, PrintPage, + PortalPage, ] for renderer in renderers: @@ -135,6 +137,7 @@ def resolve_redirect(path, query_string=None): return redirects = frappe.get_hooks("website_redirects") + redirects += add_portal_redirect() redirects += [ { "source": r.source, @@ -226,3 +229,20 @@ def get_website_rules(): def validate_path(path: str): if not PathResolver(path).is_valid_path(): frappe.throw(frappe._("Path {0} it not a valid path").format(frappe.bold(path))) + + +def add_portal_redirect(): + redirects = [] + menu_items = frappe.get_single("Portal Settings").menu + + for item in menu_items: + if check_if_webform_exists(item.route): + continue + redirects.append( + { + "source": item.route, + "target": f"/portal{item.route}", + "formward_query_parameters": True, + } + ) + return redirects diff --git a/frappe/website/utils.py b/frappe/website/utils.py index 631d57117b..1118bec946 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -607,3 +607,9 @@ def is_binary_file(path): with open(path, "rb") as f: content = f.read(1024) return bool(content.translate(None, textchars)) + + +def check_if_webform_exists(route): + return frappe.db.exists("Web Form", {"name": route.strip("/")}) or frappe.db.exists( + "Web Form", {"route": route.strip("/")} + ) diff --git a/frappe/www/portal.html b/frappe/www/portal.html new file mode 100644 index 0000000000..015f4f01cb --- /dev/null +++ b/frappe/www/portal.html @@ -0,0 +1,27 @@ +{% extends "templates/web.html" %} +{% block footer %}{% endblock %} + + +{% block header %} + {% if doctype %} +

+ {{ title or (_("{0} List").format(_(doctype))) }} +

+ {% endif %} +{% endblock %} + +{% block page_content %} +{% if not doctype %} +

Welcome to the Portal

+{% else %} + {% if introduction %} +

{{ introduction }}

+ {% endif %} + + {% include list_template %} + + {% if list_footer %} + {{ list_footer }} + {% endif %} +{% endif %} +{% endblock %} \ No newline at end of file diff --git a/frappe/www/portal.py b/frappe/www/portal.py new file mode 100644 index 0000000000..f544369c37 --- /dev/null +++ b/frappe/www/portal.py @@ -0,0 +1,84 @@ +import json + +import frappe +from frappe import _, cint +from frappe.model.document import Document +from frappe.utils.data import quoted +from frappe.www.list import get_list_context, get_list_data + + +def get_context(context, **dict_params): + frappe.local.form_dict.update(dict_params) + context.show_sidebar = True + doctype = frappe.local.form_dict.doctype + if doctype: + context.meta = frappe.get_meta(doctype) + context.update(get_list_context(context, doctype) or {}) + context.update(get(**frappe.local.form_dict)) + context.home_page = "/portal" + context.doctype = frappe.local.form_dict.doctype + return context + + +def set_route(context): + """Set link for the list item""" + if context.web_form_name: + context.route = f"{context.pathname}?name={quoted(context.doc.name)}" + elif context.doc and getattr(context.doc, "route", None): + context.route = context.doc.route + else: + context.route = f"{context.pathname or quoted(context.doc.doctype)}/{quoted(context.doc.name)}" + + +def get( + doctype: str, + txt: str | None = None, + limit_start: int = 0, + limit: int = 20, + pathname: str | None = None, + **kwargs, +): + """Return processed HTML page for a standard listing.""" + limit_start = cint(limit_start) + raw_result = get_list_data(doctype, txt, limit_start, limit=limit + 1, **kwargs) + show_more = len(raw_result) > limit + if show_more: + raw_result = raw_result[:-1] + + meta = frappe.get_meta(doctype) + list_context = frappe.flags.list_context + + if not raw_result: + return {"result": []} + + if txt: + list_context.default_subtitle = _('Filtered by "{0}"').format(txt) + + result = [] + row_template = list_context.row_template or "templates/includes/list/row_template.html" + list_view_fields = [df for df in meta.fields if df.in_list_view][:4] + + for doc in raw_result: + doc.doctype = doctype + new_context = frappe._dict(doc=doc, meta=meta, list_view_fields=list_view_fields) + + if not list_context.get_list and not isinstance(new_context.doc, Document): + new_context.doc = frappe.get_doc(doc.doctype, doc.name) + new_context.update(new_context.doc.as_dict()) + + if not frappe.in_test: + pathname = pathname or frappe.local.request.path + new_context["pathname"] = pathname.strip("/ ") + new_context.update(list_context) + set_route(new_context) + rendered_row = frappe.render_template(row_template, new_context, is_path=True) + result.append(rendered_row) + + from frappe.utils.response import json_handler + + return { + "raw_result": json.dumps(raw_result, default=json_handler), + "result": result, + "show_more": show_more, + "next_start": limit_start + limit, + }