feat: introduce /portal

This commit is contained in:
sokumon 2025-12-15 01:21:33 +05:30
parent 1dece29b80
commit 46f6c7481f
5 changed files with 169 additions and 1 deletions

View file

@ -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()

View file

@ -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

View file

@ -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("/")}
)

27
frappe/www/portal.html Normal file
View file

@ -0,0 +1,27 @@
{% extends "templates/web.html" %}
{% block footer %}{% endblock %}
{% block header %}
{% if doctype %}
<h3 class="my-account-header">
{{ title or (_("{0} List").format(_(doctype))) }}
</h3>
{% endif %}
{% endblock %}
{% block page_content %}
{% if not doctype %}
<h2>Welcome to the Portal</h2>
{% else %}
{% if introduction %}
<p>{{ introduction }}</p>
{% endif %}
{% include list_template %}
{% if list_footer %}
{{ list_footer }}
{% endif %}
{% endif %}
{% endblock %}

84
frappe/www/portal.py Normal file
View file

@ -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,
}