155 lines
4.6 KiB
Python
155 lines
4.6 KiB
Python
import re
|
|
import click
|
|
|
|
from werkzeug.routing import Rule
|
|
|
|
import frappe
|
|
from frappe.website.page_renderers.document_page import DocumentPage
|
|
from frappe.website.page_renderers.list_page import ListPage
|
|
from frappe.website.page_renderers.not_found_page import NotFoundPage
|
|
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
|
|
|
|
|
|
class PathResolver():
|
|
def __init__(self, path):
|
|
self.path = path.strip('/ ')
|
|
|
|
def resolve(self):
|
|
'''Returns endpoint and a renderer instance that can render the endpoint'''
|
|
request = frappe._dict()
|
|
if hasattr(frappe.local, 'request'):
|
|
request = frappe.local.request or request
|
|
|
|
# check if the request url is in 404 list
|
|
if request.url and can_cache() and frappe.cache().hget('website_404', request.url):
|
|
return self.path, NotFoundPage(self.path)
|
|
|
|
try:
|
|
resolve_redirect(self.path, request.query_string)
|
|
except frappe.Redirect:
|
|
return frappe.flags.redirect_location, RedirectPage(self.path)
|
|
|
|
endpoint = resolve_path(self.path)
|
|
custom_renderers = self.get_custom_page_renderers()
|
|
renderers = custom_renderers + [StaticPage, WebFormPage, ListPage, DocumentPage, TemplatePage, PrintPage, NotFoundPage]
|
|
|
|
for renderer in renderers:
|
|
renderer_instance = renderer(endpoint, 200)
|
|
if renderer_instance.can_render():
|
|
return endpoint, renderer_instance
|
|
|
|
return endpoint, NotFoundPage(endpoint)
|
|
|
|
def is_valid_path(self):
|
|
_endpoint, renderer_instance = self.resolve()
|
|
return not isinstance(renderer_instance, NotFoundPage)
|
|
|
|
@staticmethod
|
|
def get_custom_page_renderers():
|
|
custom_renderers = []
|
|
for renderer_path in frappe.get_hooks('page_renderer') or []:
|
|
try:
|
|
renderer = frappe.get_attr(renderer_path)
|
|
if not hasattr(renderer, 'can_render'):
|
|
click.echo(f'{renderer.__name__} does not have can_render method')
|
|
continue
|
|
if not hasattr(renderer, 'render'):
|
|
click.echo(f'{renderer.__name__} does not have render method')
|
|
continue
|
|
|
|
custom_renderers.append(renderer)
|
|
|
|
except Exception:
|
|
click.echo(f'Failed to load page renderer. Import path: {renderer_path}')
|
|
|
|
return custom_renderers
|
|
|
|
|
|
|
|
def resolve_redirect(path, query_string=None):
|
|
'''
|
|
Resolve redirects from hooks
|
|
|
|
Example:
|
|
|
|
website_redirect = [
|
|
# absolute location
|
|
{"source": "/from", "target": "https://mysite/from"},
|
|
|
|
# relative location
|
|
{"source": "/from", "target": "/main"},
|
|
|
|
# use regex
|
|
{"source": r"/from/(.*)", "target": r"/main/\1"}
|
|
# use r as a string prefix if you use regex groups or want to escape any string literal
|
|
]
|
|
'''
|
|
redirects = frappe.get_hooks('website_redirects')
|
|
redirects += frappe.db.get_all('Website Route Redirect', ['source', 'target'])
|
|
|
|
if not redirects: return
|
|
|
|
redirect_to = frappe.cache().hget('website_redirects', path)
|
|
|
|
if redirect_to:
|
|
frappe.flags.redirect_location = redirect_to
|
|
raise frappe.Redirect
|
|
|
|
for rule in redirects:
|
|
pattern = rule['source'].strip('/ ') + '$'
|
|
path_to_match = path
|
|
if rule.get('match_with_query_string'):
|
|
path_to_match = path + '?' + frappe.safe_decode(query_string)
|
|
|
|
if re.match(pattern, path_to_match):
|
|
redirect_to = re.sub(pattern, rule['target'], path_to_match)
|
|
frappe.flags.redirect_location = redirect_to
|
|
frappe.cache().hset('website_redirects', path_to_match, redirect_to)
|
|
raise frappe.Redirect
|
|
|
|
|
|
def resolve_path(path):
|
|
if not path:
|
|
path = "index"
|
|
|
|
if path.endswith('.html'):
|
|
path = path[:-5]
|
|
|
|
if path == "index":
|
|
path = get_home_page()
|
|
|
|
frappe.local.path = path
|
|
|
|
if path != "index":
|
|
path = resolve_from_map(path)
|
|
|
|
return path
|
|
|
|
def resolve_from_map(path):
|
|
'''transform dynamic route to a static one from hooks and route defined in doctype'''
|
|
rules = [Rule(r["from_route"], endpoint=r["to_route"], defaults=r.get("defaults"))
|
|
for r in get_website_rules()]
|
|
|
|
return evaluate_dynamic_routes(rules, path) or path
|
|
|
|
def get_website_rules():
|
|
'''Get website route rules from hooks and DocType route'''
|
|
def _get():
|
|
rules = frappe.get_hooks("website_route_rules")
|
|
for d in frappe.get_all('DocType', 'name, route', dict(has_web_view=1)):
|
|
if d.route:
|
|
rules.append(dict(from_route = '/' + d.route.strip('/'), to_route=d.name))
|
|
|
|
return rules
|
|
|
|
if frappe.local.dev_server:
|
|
# dont cache in development
|
|
return _get()
|
|
|
|
return frappe.cache().get_value('website_route_rules', _get)
|