From 435bbe2665a447048058660e86deb6ed2005c635 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 5 Feb 2021 14:31:46 +0530 Subject: [PATCH 001/164] wip: refactor website routing and rendering --- frappe/tests/test_website_new.py | 20 ++ frappe/utils/boilerplate.py | 1 - frappe/website/serve.py | 276 ++++++++++++++++++++++++++ frappe/www/_test/static-file-test.png | Bin 0 -> 440 bytes 4 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 frappe/tests/test_website_new.py create mode 100644 frappe/website/serve.py create mode 100644 frappe/www/_test/static-file-test.png diff --git a/frappe/tests/test_website_new.py b/frappe/tests/test_website_new.py new file mode 100644 index 0000000000..aa9e77d5ba --- /dev/null +++ b/frappe/tests/test_website_new.py @@ -0,0 +1,20 @@ +from __future__ import unicode_literals + +import unittest + +import frappe +from frappe.website import serve +from frappe.website.utils import get_home_page +from frappe.utils import set_request + +class TestWebsite(unittest.TestCase): + def test_static_page(self): + set_request(method='GET', path='/_test/static-file-test.png') + response = serve.StaticPage().get() + self.assertEquals(response.status_code, 200) + + def test_error_page(self): + set_request(method='GET', path='/error') + response = serve.TemplatePage().get() + self.assertEquals(response.status_code, 200) + print(response.get_data()) diff --git a/frappe/utils/boilerplate.py b/frappe/utils/boilerplate.py index 69d5726c34..e613c4d810 100755 --- a/frappe/utils/boilerplate.py +++ b/frappe/utils/boilerplate.py @@ -330,7 +330,6 @@ Configuration for docs """ # source_link = "https://github.com/[org_name]/{app_name}" -# docs_base_url = "https://[org_name].github.io/{app_name}" # headline = "App that does everything" # sub_heading = "Yes, you got that right the first time, everything" diff --git a/frappe/website/serve.py b/frappe/website/serve.py new file mode 100644 index 0000000000..3f3e1d4571 --- /dev/null +++ b/frappe/website/serve.py @@ -0,0 +1,276 @@ +import frappe +import os, mimetypes + +from werkzeug.wrappers import Response +from werkzeug.wsgi import wrap_file + +from frappe.website.render import (resolve_path, build_response) +from frappe.website.redirect import resolve_redirect +from frappe.website.router import get_start_folders +from frappe.website.utils import (get_home_page, can_cache, delete_page_cache, + get_toc, get_next_link) +from frappe.website.doctype.website_settings.website_settings import get_website_settings + +def render(path=None, http_status_code=None): + """render html page""" + if not path: + path = frappe.local.request.path + + try: + path = path.strip('/ ') + resolve_redirect(path) + path = resolve_path(path) + data = None + + response = StaticPage(path).get() + if not response: + response = TemplatePage(path).get() + if not response: + response = DocTypePage(path).get() + if not response: + response = TemplatePage('404').get() + except frappe.PermissionError as e: + response = TemplatePage('403').get() + except: + response = TemplatePage('error').get() + + return response + +class WebPage(object): + def __init__(self, path=None): + self.headers = None + self.status_code = 200 + if not path: + path = frappe.local.request.path + self.path = path.strip('/ ') + + def get(self): + if self.validate(): + return self.render() + + def validate(self): + pass + + def render(self): + pass + +class StaticPage(WebPage): + def validate(self): + if ('.' not in self.path): + return False + extn = self.path.rsplit('.', 1)[-1] + if extn in ('html', 'md', 'js', 'xml', 'css', 'txt', 'py', 'json'): + return False + + if self.find_path_in_apps(): + return True + + return False + + def find_path_in_apps(self): + for app in frappe.get_installed_apps(): + file_path = frappe.get_app_path(app, 'www') + '/' + self.path + if os.path.exists(file_path): + self.file_path = file_path + return True + return False + + def render(self): + try: + f = open(self.file_path, 'rb') + except IOError: + raise NotFound + + response = Response(wrap_file(frappe.local.request.environ, f), direct_passthrough=True) + response.mimetype = mimetypes.guess_type(self.file_path)[0] or 'application/octet-stream' + return response + +class TemplatePage(WebPage): + def validate(self): + for app in frappe.get_installed_apps(frappe_last=True): + if self.find_page_in_app(app): + return True + + def find_page_in_app(self, app): + ''' + Searches for file matching the path in the /www and /templates/pages folders + ''' + app_path = frappe.get_app_path(app) + folders = get_start_folders() + + for dirname in folders: + search_path = os.path.join(app_path, dirname, self.path) + for p in self.get_index_path_options(search_path): + file_path = frappe.as_unicode(p) + if os.path.exists(file_path) and not os.path.isdir(file_path): + self.app = app + self.app_path = app_path + self.dirname = dirname + self.file_path = file_path + self.template_path = os.path.relpath(file_path, self.app_path) + return True + + def get_index_path_options(self, search_path): + return ( + search_path, + search_path + '.html', + search_path + '.md', + search_path + '/index.html', + search_path + '/index.md') + + def render(self): + return build_response(self.path, self.get_html(), self.status_code, self.headers) + + def get_html(self): + # context object should be separate from self for security + # because it will be accessed via the user defined template + self.context = frappe._dict() + + self.set_pymodule() + self.setup_template() + + if self.pymodule_name: + self.update_context() + + if self.source: + html = frappe.render_template(self.source, self.context) + + elif self.template_path: + html = self.render_template() + + html = self.update_toc(html) + + return html + + def set_pymodule(self): + ''' + A template may have a python module with a `get_context` method along with it in the + same folder. Also the hyphens will be coverted to underscore for python module names. + This method sets the pymodule_name if it exists. + ''' + self.basepath = self.template_path.rsplit('.', 1)[0] + self.pymodule_name = None + + # replace - with _ in the internal modules names + self.pymodule_path = os.path.join(self.basepath.replace("-", "_") + ".py") + + if os.path.exists(os.path.join(self.app_path, self.pymodule_path)): + self.pymodule_name = self.app + "." + self.pymodule_path.replace(os.path.sep, ".")[:-3] + + def setup_template(self): + '''Setup template source, frontmatter and markdown conversion''' + self.source = self.get_raw_template() + + if self.template_path.endswith(('.md', '.html')): + self.extract_frontmatter() + + self.convert_from_markdown() + + if self.extends_template(): + self.context.base_template_path = self.context.base_template_path or 'templates/base.html' + else: + self.source = None # clear the source + + # TODO: setup index.txt ? + + def update_context(self): + self.set_page_properties() + self.context.update(get_website_settings(self.context)) + self.context.update(frappe.local.conf.get("website_context") or {}) + + self.pymodule = frappe.get_module(self.pymodule_name) + + if self.pymodule: + self.set_pymodule_properties() + + data = self.run_pymodule_method('get_context') + # some methods may return a "context" object + if data: self.context.update(data) + + # TODO: self.context.children = self.run_pymodule_method('get_children') + + self.context.developer_mode = frappe.conf.developer_mode + + def set_pymodule_properties(self): + for prop in ("base_template_path", "template", "no_cache", "sitemap", + "condition_field"): + if hasattr(self.pymodule, prop): + self.context[prop] = getattr(self.pymodule, prop) + + def set_page_properties(self): + self.context.template = self.template_path + + def run_pymodule_method(self, method): + if hasattr(self.pymodule, method): + try: + return getattr(self.pymodule, method)(self) + except (frappe.PermissionError, frappe.DoesNotExistError, frappe.Redirect): + raise + except: + if not frappe.flags.in_migrate: + frappe.errprint(frappe.utils.get_traceback()) + + def render_template(self): + if self.path.endswith('min.js'): + # directly serve min.js pages using the jloader to find it in various apps + # (can be used as static?) + html = self.get_raw_template() + else: + html = frappe.get_template(self.template_path).render(self.context) + + def extends_template(self): + return (self.template_path.endswith(('.html', '.md', )) + and ('{%- extends' in self.source + or '{% extends' in self.source)) + + def get_raw_template(self): + return frappe.get_jloader().get_source(frappe.get_jenv(), self.template_path)[0] + + def load_colocated_files(self): + '''load co-located css/js files with the same name''' + js_path = self.basepath + '.js' + if os.path.exists(js_path) and '{% block script %}' not in self.source: + self.colocated_js = self.get_colocated_file(js_path) + + css_path = self.basepath + '.css' + if os.path.exists(css_path) and '{% block style %}' not in self.source: + self.colocated_css = self.get_colocated_file(css_path) + + def get_colocated_file(self, path): + with io.open(path, 'r', encoding = 'utf-8') as f: + return f.read() + + def extract_frontmatter(self): + try: + # values will be used to update page_info + res = get_frontmatter(self.source) + if res['attributes']: + self.context.update(res['attributes']) + self.source = res['body'] + except Exception: + pass + + def convert_from_markdown(self): + if self.template_path.endswith('.md'): + self.source = frappe.utils.md_to_html(self.source) + self.page_toc_html = self.toc_html + + if not self.show_sidebar: + self.source = '
' + self.source + '
' + + def update_toc(self, html): + if '{index}' in html: + html = html.replace('{index}', get_toc(self.path)) + + if '{next}' in html: + html = html.replace('{next}', get_next_link(self.path)) + + return html + + +class DocTypePage(WebPage): + pass + +class WebFormPage(WebPage): + pass + diff --git a/frappe/www/_test/static-file-test.png b/frappe/www/_test/static-file-test.png new file mode 100644 index 0000000000000000000000000000000000000000..b51db82f82b906b30609f807f0f663cac40d20b4 GIT binary patch literal 440 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q4M;wBd$a>caTa()7Beu2se&-0XOPMVpde#$ zkh>GZx^prwfgF}}M_)$E)e-c@Na=xA}jv*C{Z|`n2ZE+NFc(}bULexYtxXVk! zRa1H)JCmwK`xQRZ9Zdhl)Xnb@xa7e_h#Kc{rb11QMP%$ zyna9bX2;pHxqo~9%j-KE^?Uy3M6(kqS{SBbSa7?V|4TdH>w?cef9;X4JIhPRIT!|D tb@a_URnxz=-&b0D_xf+-FaWXL-%6{zP`s|39McHmdb;|#taD0e0su)Qf|39L literal 0 HcmV?d00001 From 3f38c3300462dfcda96638c0597216613bf21f19 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 5 Feb 2021 15:54:23 +0530 Subject: [PATCH 002/164] fix: status_code, login page works --- frappe/app.py | 8 ++++++-- frappe/tests/test_website_new.py | 17 ++++++++++++++++- frappe/website/serve.py | 18 ++++++++++-------- frappe/www/error.py | 2 ++ 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/frappe/app.py b/frappe/app.py index adf2bfa8c9..9495268f00 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -18,7 +18,8 @@ import frappe.handler import frappe.auth import frappe.api import frappe.utils.response -import frappe.website.render +import frappe.website.serve +import frappe.website.page from frappe.utils import get_site_name, sanitize_html from frappe.middlewares import StaticDataMiddleware from frappe.utils.error import make_error_snapshot @@ -73,7 +74,10 @@ def application(request): response = frappe.utils.response.download_private_file(request.path) elif request.method in ('GET', 'HEAD', 'POST'): - response = frappe.website.render.render() + if frappe.conf.flag_new_website: + response = frappe.website.serve.render() + else: + response = frappe.website.render.render() else: raise NotFound diff --git a/frappe/tests/test_website_new.py b/frappe/tests/test_website_new.py index aa9e77d5ba..34786611d6 100644 --- a/frappe/tests/test_website_new.py +++ b/frappe/tests/test_website_new.py @@ -8,6 +8,12 @@ from frappe.website.utils import get_home_page from frappe.utils import set_request class TestWebsite(unittest.TestCase): + def setUp(self): + frappe.set_user('Guest') + + def tearDown(self): + frappe.set_user('Administrator') + def test_static_page(self): set_request(method='GET', path='/_test/static-file-test.png') response = serve.StaticPage().get() @@ -16,5 +22,14 @@ class TestWebsite(unittest.TestCase): def test_error_page(self): set_request(method='GET', path='/error') response = serve.TemplatePage().get() + self.assertEquals(response.status_code, 500) + + def test_login(self): + set_request(method='GET', path='/login') + response = serve.TemplatePage().get() self.assertEquals(response.status_code, 200) - print(response.get_data()) + + html = frappe.safe_decode(response.get_data()) + + self.assertTrue('// login.js' in html) + self.assertTrue('' in html) diff --git a/frappe/website/serve.py b/frappe/website/serve.py index 3f3e1d4571..a0710b9988 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -22,6 +22,8 @@ def render(path=None, http_status_code=None): path = resolve_path(path) data = None + # there is no way to determine the type of the page based on the route + # so evaluate each type of page sequentially response = StaticPage(path).get() if not response: response = TemplatePage(path).get() @@ -93,7 +95,8 @@ class TemplatePage(WebPage): def find_page_in_app(self, app): ''' - Searches for file matching the path in the /www and /templates/pages folders + Searches for file matching the path in the /www + and /templates/pages folders ''' app_path = frappe.get_app_path(app) folders = get_start_folders() @@ -128,9 +131,7 @@ class TemplatePage(WebPage): self.set_pymodule() self.setup_template() - - if self.pymodule_name: - self.update_context() + self.update_context() if self.source: html = frappe.render_template(self.source, self.context) @@ -178,18 +179,19 @@ class TemplatePage(WebPage): self.context.update(get_website_settings(self.context)) self.context.update(frappe.local.conf.get("website_context") or {}) - self.pymodule = frappe.get_module(self.pymodule_name) - - if self.pymodule: + if self.pymodule_name: + self.pymodule = frappe.get_module(self.pymodule_name) self.set_pymodule_properties() data = self.run_pymodule_method('get_context') + # some methods may return a "context" object if data: self.context.update(data) # TODO: self.context.children = self.run_pymodule_method('get_children') self.context.developer_mode = frappe.conf.developer_mode + self.status_code = self.context.http_status_code or 200 def set_pymodule_properties(self): for prop in ("base_template_path", "template", "no_cache", "sitemap", @@ -203,7 +205,7 @@ class TemplatePage(WebPage): def run_pymodule_method(self, method): if hasattr(self.pymodule, method): try: - return getattr(self.pymodule, method)(self) + return getattr(self.pymodule, method)(self.context) except (frappe.PermissionError, frappe.DoesNotExistError, frappe.Redirect): raise except: diff --git a/frappe/www/error.py b/frappe/www/error.py index 161038373d..9274fe7c26 100644 --- a/frappe/www/error.py +++ b/frappe/www/error.py @@ -8,5 +8,7 @@ no_cache = 1 def get_context(context): if frappe.flags.in_migrate: return + context.http_status_code = 500 + print(frappe.get_traceback().encode("utf-8")) return {"error": frappe.get_traceback().replace("<", "<").replace(">", ">") } From 4369b8c0dc3d88c3d37af7ead06a4b35e4812778 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 12 Feb 2021 10:02:10 +0530 Subject: [PATCH 003/164] fix(rendering): add doctype generate pages --- frappe/app.py | 7 +- frappe/sessions.py | 3 +- frappe/tests/test_website_new.py | 20 ++- frappe/website/context.py | 2 +- frappe/website/render.py | 7 + frappe/website/serve.py | 231 ++++++++++++++++++++++++++++--- frappe/www/404.py | 2 + 7 files changed, 241 insertions(+), 31 deletions(-) diff --git a/frappe/app.py b/frappe/app.py index 9495268f00..52a8d2fe1c 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -18,8 +18,6 @@ import frappe.handler import frappe.auth import frappe.api import frappe.utils.response -import frappe.website.serve -import frappe.website.page from frappe.utils import get_site_name, sanitize_html from frappe.middlewares import StaticDataMiddleware from frappe.utils.error import make_error_snapshot @@ -74,10 +72,7 @@ def application(request): response = frappe.utils.response.download_private_file(request.path) elif request.method in ('GET', 'HEAD', 'POST'): - if frappe.conf.flag_new_website: - response = frappe.website.serve.render() - else: - response = frappe.website.render.render() + response = frappe.website.render.render() else: raise NotFound diff --git a/frappe/sessions.py b/frappe/sessions.py index 1ca1d4ee6f..dddeb6f37f 100644 --- a/frappe/sessions.py +++ b/frappe/sessions.py @@ -169,7 +169,8 @@ def get_csrf_token(): def generate_csrf_token(): frappe.local.session.data.csrf_token = frappe.generate_hash() - frappe.local.session_obj.update(force=True) + if not frappe.flags.in_test: + frappe.local.session_obj.update(force=True) class Session: def __init__(self, user, resume=False, full_name=None, user_type=None): diff --git a/frappe/tests/test_website_new.py b/frappe/tests/test_website_new.py index 34786611d6..3d4c2a69ef 100644 --- a/frappe/tests/test_website_new.py +++ b/frappe/tests/test_website_new.py @@ -6,7 +6,6 @@ import frappe from frappe.website import serve from frappe.website.utils import get_home_page from frappe.utils import set_request - class TestWebsite(unittest.TestCase): def setUp(self): frappe.set_user('Guest') @@ -21,15 +20,30 @@ class TestWebsite(unittest.TestCase): def test_error_page(self): set_request(method='GET', path='/error') - response = serve.TemplatePage().get() + response = serve.get_response() self.assertEquals(response.status_code, 500) def test_login(self): set_request(method='GET', path='/login') - response = serve.TemplatePage().get() + response = serve.get_response() self.assertEquals(response.status_code, 200) html = frappe.safe_decode(response.get_data()) self.assertTrue('// login.js' in html) self.assertTrue('' in html) + + def test_app(self): + frappe.set_user('Administrator') + set_request(method='GET', path='/app') + response = serve.get_response() + self.assertEquals(response.status_code, 200) + + html = frappe.safe_decode(response.get_data()) + self.assertTrue('window.app = true;' in html) + frappe.local.session_obj = None + + def test_not_found(self): + set_request(method='GET', path='/_test/missing') + response = serve.get_response() + self.assertEquals(response.status_code, 404) diff --git a/frappe/website/context.py b/frappe/website/context.py index 4236971aec..abda2e3f4f 100644 --- a/frappe/website/context.py +++ b/frappe/website/context.py @@ -243,7 +243,7 @@ def add_metatags(context): tags["og:title"] = tags["twitter:title"] = title tags["twitter:card"] = "summary" - if "description" not in tags and context.description: + if not tags.get('description') and context.description: tags["description"] = context.description description = tags.get("description") diff --git a/frappe/website/render.py b/frappe/website/render.py index 2f8bc59d6d..ce9647c62a 100644 --- a/frappe/website/render.py +++ b/frappe/website/render.py @@ -26,6 +26,13 @@ from frappe.translate import guess_language class PageNotFoundError(Exception): pass def render(path=None, http_status_code=None): + if frappe.conf.flag_new_website: + from frappe.website.serve import get_response + return get_response() + else: + return render(path, http_status_code) + +def _render(path=None, http_status_code=None): """render html page""" if not path: path = frappe.local.request.path diff --git a/frappe/website/serve.py b/frappe/website/serve.py index a0710b9988..ec54e8c149 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -6,12 +6,13 @@ from werkzeug.wsgi import wrap_file from frappe.website.render import (resolve_path, build_response) from frappe.website.redirect import resolve_redirect -from frappe.website.router import get_start_folders +from frappe.website.router import (get_start_folders, get_doctypes_with_web_view, + get_page_info_from_web_page_with_dynamic_routes) from frappe.website.utils import (get_home_page, can_cache, delete_page_cache, get_toc, get_next_link) from frappe.website.doctype.website_settings.website_settings import get_website_settings -def render(path=None, http_status_code=None): +def get_response(path=None, http_status_code=None): """render html page""" if not path: path = frappe.local.request.path @@ -87,7 +88,120 @@ class StaticPage(WebPage): response.mimetype = mimetypes.guess_type(self.file_path)[0] or 'application/octet-stream' return response -class TemplatePage(WebPage): +class BaseTemplatePage(WebPage): + def init_context(self): + self.context = frappe._dict() + self.context.update(get_website_settings(self.context)) + self.context.update(frappe.local.conf.get("website_context") or {}) + + def add_csrf_token(self, html): + if frappe.local.session: + return html.replace("", ''.format( + frappe.local.session.data.csrf_token)) + else: + return html + + def post_process_context(self): + self.add_metatags() + # add_sidebar_and_breadcrumbs(context) + + self.set_base_template_if_missing() + self.set_title_with_prefix() + self.update_website_context() + + # set using frappe.respond_as_web_page + if hasattr(frappe.local, 'response') and frappe.local.response.get('context'): + context.update(frappe.local.response.context) + + # to be able to inspect the context dict + # Use the macro "inspect" from macros.html + self.context._context_dict = self.context + + def set_base_template_if_missing(self): + if not self.context.base_template_path: + app_base = frappe.get_hooks("base_template") + self.context.base_template_path = app_base[-1] if app_base else "templates/base.html" + + def set_title_with_prefix(self): + if (self.context.title_prefix and self.context.title + and not self.context.title.startswith(self.context.title_prefix)): + self.context.title = '{0} - {1}'.format(self.context.title_prefix, self.context.title) + + def update_website_context(self): + # apply context from hooks + update_website_context = frappe.get_hooks('update_website_context') + for method in update_website_context: + values = frappe.get_attr(method)(self.context) + if values: + self.context.update(values) + + def add_metatags(self): + self.tags = frappe._dict(self.context.get("metatags") or {}) + self.init_metatags_from_context() + self.set_opengraph_tags() + self.set_twitter_tags() + self.set_meta_published_on() + self.set_metatags_from_website_route_meta() + + self.context.metatags = self.tags + + def init_metatags_from_context(self): + for key in ('title', 'description', 'image', 'author', 'url', 'published_on'): + if not key in self.tags and self.context.get(key): + self.tags[key] = self.context[key] + + if not self.tags.get('title'): self.tags['title'] = self.context.get('name') + + if self.tags.get('image'): + self.tags['image'] = frappe.utils.get_url(self.tags['image']) + + self.tags["language"] = frappe.local.lang or "en" + + def set_opengraph_tags(self): + if "og:type" not in self.tags: + self.tags["og:type"] = "article" + + for key in ('title', 'description', 'image', 'author', 'url'): + if self.tags.get(key): + self.tags['og:' + key] = self.tags.get(key) + + def set_twitter_tags(self): + for key in ('title', 'description', 'image', 'author', 'url'): + if self.tags.get(key): + self.tags['twitter:' + key] = self.tags.get(key) + + if self.tags.get('image'): + self.tags['twitter:card'] = "summary_large_image" + else: + self.tags["twitter:card"] = "summary" + + def set_meta_published_on(self): + if "published_on" in self.tags: + self.tags["datePublished"] = self.tags["published_on"] + del self.tags["published_on"] + + def set_metatags_from_website_route_meta(self): + ''' + Get meta tags from Website Route meta + they can override the defaults set above + ''' + route = self.context.path + if route == '': + # homepage + route = frappe.db.get_single_value('Website Settings', 'home_page') + + route_exists = (route + and not route.endswith(('.js', '.css')) + and frappe.db.exists('Website Route Meta', route)) + + if route_exists: + website_route_meta = frappe.get_doc('Website Route Meta', route) + for meta_tag in website_route_meta.meta_tags: + d = meta_tag.get_meta_dict() + self.tags.update(d) + + +class TemplatePage(BaseTemplatePage): def validate(self): for app in frappe.get_installed_apps(frappe_last=True): if self.find_page_in_app(app): @@ -127,19 +241,16 @@ class TemplatePage(WebPage): def get_html(self): # context object should be separate from self for security # because it will be accessed via the user defined template - self.context = frappe._dict() + self.init_context() self.set_pymodule() self.setup_template() self.update_context() - - if self.source: - html = frappe.render_template(self.source, self.context) - - elif self.template_path: - html = self.render_template() + self.post_process_context() + html = self.render_template() html = self.update_toc(html) + html = self.add_csrf_token(html) return html @@ -176,8 +287,6 @@ class TemplatePage(WebPage): def update_context(self): self.set_page_properties() - self.context.update(get_website_settings(self.context)) - self.context.update(frappe.local.conf.get("website_context") or {}) if self.pymodule_name: self.pymodule = frappe.get_module(self.pymodule_name) @@ -213,12 +322,15 @@ class TemplatePage(WebPage): frappe.errprint(frappe.utils.get_traceback()) def render_template(self): - if self.path.endswith('min.js'): - # directly serve min.js pages using the jloader to find it in various apps - # (can be used as static?) - html = self.get_raw_template() - else: - html = frappe.get_template(self.template_path).render(self.context) + if self.source: + html = frappe.render_template(self.source, self.context) + elif self.template_path: + if self.path.endswith('min.js'): + html = self.get_raw_template() # static + else: + html = frappe.get_template(self.template_path).render(self.context) + + return html def extends_template(self): return (self.template_path.endswith(('.html', '.md', )) @@ -270,8 +382,87 @@ class TemplatePage(WebPage): return html -class DocTypePage(WebPage): - pass +class DocTypePage(BaseTemplatePage): + def validate(self): + ''' + Find a document with matching `route` from all doctypes with `has_web_view`=1 + ''' + if self.search_in_doctypes_with_web_view(): + return True + + if self.search_web_page_dynamic_routes(): + return True + + return False + + def search_in_doctypes_with_web_view(self): + for doctype in get_doctypes_with_web_view(): + filters = dict(route=self.path) + meta = frappe.get_meta(doctype) + condition_field = self.get_condition_field(meta) + + if condition_field: + filters[condition_field] = 1 + + try: + self.docname = frappe.db.get_value(doctype, filters, 'name') + if self.docname: + self.doctype = doctype + self.meta = meta + return True + except Exception as e: + if not frappe.db.is_missing_column(e): raise e + + def search_web_page_dynamic_routes(self): + d = get_page_info_from_web_page_with_dynamic_routes(self.path) + if d: + self.doctype = 'Web Page' + self.docname = d.name + self.meta = frappe.get_meta(self.doctype) + return True + else: + return False + + def render(self): + self.doc = frappe.get_doc(self.doctype, self.docname) + self.init_context() + self.update_context() + self.post_process_context() + + html = frappe.get_template(self.context.template_path).render(self.context) + html = self.add_csrf_token(html) + + return build_response(self.path, html, self.status_code, self.headers) + + def update_context(self): + self.context.doc = self.doc + self.context.update(self.context.doc.as_dict()) + self.context.update(self.context.doc.get_website_properties()) + + if not self.context.template_path: + self.context.template_path = self.context.doc.meta.get_web_template() + + if hasattr(self.doc, "get_context"): + ret = self.doc.get_context(self.context) + + if ret: + self.context.update(ret) + + for prop in ("no_cache", "sitemap"): + if not prop in self.context: + self.context[prop] = getattr(self.doc, prop, False) + + def get_condition_field(self, meta): + condition_field = None + if meta.is_published_field: + condition_field = meta.is_published_field + elif not meta.custom: + controller = get_controller(doctype) + condition_field = controller.website.condition_field + + return condition_field + + class WebFormPage(WebPage): pass diff --git a/frappe/www/404.py b/frappe/www/404.py index c9de234743..e02e1c3929 100644 --- a/frappe/www/404.py +++ b/frappe/www/404.py @@ -2,3 +2,5 @@ # MIT License. See license.txt from __future__ import unicode_literals +def get_context(context): + context.http_status_code = 404 From 46dc5af29a81bbe00b022e30e096add46ecb88dd Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 15 Feb 2021 12:55:47 +0530 Subject: [PATCH 004/164] fix(list): make list pages work --- frappe/website/doctype/blog_post/blog_post.py | 5 --- frappe/website/render.py | 5 +-- frappe/website/router.py | 2 +- frappe/website/serve.py | 34 +++++++++++++------ 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py index 28549225be..e7c2772c69 100644 --- a/frappe/website/doctype/blog_post/blog_post.py +++ b/frappe/website/doctype/blog_post/blog_post.py @@ -13,11 +13,6 @@ from frappe.website.utils import (find_first_image, get_html_content_based_on_ty get_comment_list) class BlogPost(WebsiteGenerator): - website = frappe._dict( - route = 'blog', - order_by = "published_on desc" - ) - def make_route(self): if not self.route: return frappe.db.get_value('Blog Category', self.blog_category, diff --git a/frappe/website/render.py b/frappe/website/render.py index ce9647c62a..21d5dda8d5 100644 --- a/frappe/website/render.py +++ b/frappe/website/render.py @@ -26,11 +26,12 @@ from frappe.translate import guess_language class PageNotFoundError(Exception): pass def render(path=None, http_status_code=None): - if frappe.conf.flag_new_website: + # temp feature flag + if True or frappe.conf.flag_new_website: from frappe.website.serve import get_response return get_response() else: - return render(path, http_status_code) + return _render(path, http_status_code) def _render(path=None, http_status_code=None): """render html page""" diff --git a/frappe/website/router.py b/frappe/website/router.py index 946c83811a..5244c57ba8 100644 --- a/frappe/website/router.py +++ b/frappe/website/router.py @@ -441,4 +441,4 @@ def get_doctypes_with_web_view(): return frappe.cache().get_value('doctypes_with_web_view', _get) def get_start_folders(): - return frappe.local.flags.web_pages_folders or ('www', 'templates/pages') \ No newline at end of file + return frappe.local.flags.web_pages_folders or ('www', 'templates/pages') diff --git a/frappe/website/serve.py b/frappe/website/serve.py index ec54e8c149..6784dbefd9 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -29,7 +29,9 @@ def get_response(path=None, http_status_code=None): if not response: response = TemplatePage(path).get() if not response: - response = DocTypePage(path).get() + response = ListPage(path).get() + if not response: + response = DocumentPage(path).get() if not response: response = TemplatePage('404').get() except frappe.PermissionError as e: @@ -117,6 +119,11 @@ class BaseTemplatePage(WebPage): # Use the macro "inspect" from macros.html self.context._context_dict = self.context + # context sends us a new template path + if self.context.template: + self.template_path = self.context.template + + def set_base_template_if_missing(self): if not self.context.base_template_path: app_base = frappe.get_hooks("base_template") @@ -200,7 +207,6 @@ class BaseTemplatePage(WebPage): d = meta_tag.get_meta_dict() self.tags.update(d) - class TemplatePage(BaseTemplatePage): def validate(self): for app in frappe.get_installed_apps(frappe_last=True): @@ -222,8 +228,6 @@ class TemplatePage(BaseTemplatePage): if os.path.exists(file_path) and not os.path.isdir(file_path): self.app = app self.app_path = app_path - self.dirname = dirname - self.file_path = file_path self.template_path = os.path.relpath(file_path, self.app_path) return True @@ -244,9 +248,9 @@ class TemplatePage(BaseTemplatePage): self.init_context() self.set_pymodule() - self.setup_template() self.update_context() self.post_process_context() + self.setup_template() html = self.render_template() html = self.update_toc(html) @@ -380,9 +384,18 @@ class TemplatePage(BaseTemplatePage): html = html.replace('{next}', get_next_link(self.path)) return html - - -class DocTypePage(BaseTemplatePage): +class ListPage(TemplatePage): + def validate(self): + if frappe.db.get_value('DocType', self.path): + self.app = 'frappe' + self.app_path = frappe.get_app_path('frappe') + self.doctype = self.path + self.path = 'list' + self.template_path = 'www/list.html' + frappe.local.form_dict.doctype = self.doctype + return True + return False +class DocumentPage(BaseTemplatePage): def validate(self): ''' Find a document with matching `route` from all doctypes with `has_web_view`=1 @@ -462,8 +475,7 @@ class DocTypePage(BaseTemplatePage): return condition_field - - +class PrintPage(TemplatePage): + pass class WebFormPage(WebPage): pass - From 75ba0ca6a9be2f8a1d43529ff8a28a43efc42457 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 15 Feb 2021 13:49:38 +0530 Subject: [PATCH 005/164] fix(minor): website/serve.py --- frappe/website/serve.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/website/serve.py b/frappe/website/serve.py index 6784dbefd9..8f686144f1 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -113,7 +113,7 @@ class BaseTemplatePage(WebPage): # set using frappe.respond_as_web_page if hasattr(frappe.local, 'response') and frappe.local.response.get('context'): - context.update(frappe.local.response.context) + self.context.update(frappe.local.response.context) # to be able to inspect the context dict # Use the macro "inspect" from macros.html @@ -384,6 +384,7 @@ class TemplatePage(BaseTemplatePage): html = html.replace('{next}', get_next_link(self.path)) return html + class ListPage(TemplatePage): def validate(self): if frappe.db.get_value('DocType', self.path): @@ -395,6 +396,7 @@ class ListPage(TemplatePage): frappe.local.form_dict.doctype = self.doctype return True return False + class DocumentPage(BaseTemplatePage): def validate(self): ''' @@ -477,5 +479,6 @@ class DocumentPage(BaseTemplatePage): class PrintPage(TemplatePage): pass + class WebFormPage(WebPage): pass From e79e1420612b814f1e65ce9b4021d0c31f00d00a Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 15 Feb 2021 17:43:17 +0530 Subject: [PATCH 006/164] fix(minor): fix some tests and merge old and new website tests --- .../print_settings/print_settings.json | 4 +- frappe/tests/test_website.py | 46 ++++++++++- frappe/tests/test_website_new.py | 49 ------------ frappe/website/render.py | 2 +- frappe/website/serve.py | 80 ++++++++++++++----- frappe/www/message.py | 8 +- 6 files changed, 113 insertions(+), 76 deletions(-) delete mode 100644 frappe/tests/test_website_new.py diff --git a/frappe/printing/doctype/print_settings/print_settings.json b/frappe/printing/doctype/print_settings/print_settings.json index d64cb4c6d3..31962be050 100644 --- a/frappe/printing/doctype/print_settings/print_settings.json +++ b/frappe/printing/doctype/print_settings/print_settings.json @@ -148,7 +148,7 @@ "label": "Print Style" }, { - "default": "Modern", + "default": "Redesign", "fieldname": "print_style", "fieldtype": "Link", "in_list_view": 1, @@ -183,7 +183,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-10-22 23:42:09.471022", + "modified": "2021-02-15 14:16:18.474254", "modified_by": "Administrator", "module": "Printing", "name": "Print Settings", diff --git a/frappe/tests/test_website.py b/frappe/tests/test_website.py index c5da2bdfb7..91e08db457 100644 --- a/frappe/tests/test_website.py +++ b/frappe/tests/test_website.py @@ -4,13 +4,19 @@ import unittest import frappe from frappe.website import render +from frappe.website import serve from frappe.website.utils import get_home_page from frappe.utils import set_request - class TestWebsite(unittest.TestCase): + def setUp(self): + frappe.set_user('Guest') + + def tearDown(self): + frappe.set_user('Administrator') def test_home_page_for_role(self): + frappe.set_user('Administrator') frappe.delete_doc_if_exists('User', 'test-user-for-home-page@example.com') frappe.delete_doc_if_exists('Role', 'home-page-test') frappe.delete_doc_if_exists('Web Page', 'home-page-test') @@ -44,7 +50,6 @@ class TestWebsite(unittest.TestCase): self.assertEqual(get_home_page(), 'test-portal-home') def test_page_load(self): - frappe.set_user('Guest') set_request(method='POST', path='login') response = render.render() @@ -54,10 +59,47 @@ class TestWebsite(unittest.TestCase): self.assertTrue('// login.js' in html) self.assertTrue('' in html) + + def test_static_page(self): + set_request(method='GET', path='/_test/static-file-test.png') + response = serve.get_response() + self.assertEquals(response.status_code, 200) + + def test_error_page(self): + set_request(method='GET', path='/error') + response = serve.get_response() + self.assertEquals(response.status_code, 500) + + def test_login(self): + set_request(method='GET', path='/login') + response = serve.get_response() + self.assertEquals(response.status_code, 200) + + html = frappe.safe_decode(response.get_data()) + + self.assertTrue('// login.js' in html) + self.assertTrue('' in html) + + def test_app(self): frappe.set_user('Administrator') + set_request(method='GET', path='/app') + response = serve.get_response() + self.assertEquals(response.status_code, 200) + + html = frappe.safe_decode(response.get_data()) + self.assertTrue('window.app = true;' in html) + frappe.local.session_obj = None + + def test_not_found(self): + set_request(method='GET', path='/_test/missing') + response = serve.get_response() + self.assertEquals(response.status_code, 404) + def test_redirect(self): import frappe.hooks + frappe.set_user('Administrator') + frappe.hooks.website_redirects = [ dict(source=r'/testfrom', target=r'://testto1'), dict(source=r'/testfromregex.*', target=r'://testto2'), diff --git a/frappe/tests/test_website_new.py b/frappe/tests/test_website_new.py deleted file mode 100644 index 3d4c2a69ef..0000000000 --- a/frappe/tests/test_website_new.py +++ /dev/null @@ -1,49 +0,0 @@ -from __future__ import unicode_literals - -import unittest - -import frappe -from frappe.website import serve -from frappe.website.utils import get_home_page -from frappe.utils import set_request -class TestWebsite(unittest.TestCase): - def setUp(self): - frappe.set_user('Guest') - - def tearDown(self): - frappe.set_user('Administrator') - - def test_static_page(self): - set_request(method='GET', path='/_test/static-file-test.png') - response = serve.StaticPage().get() - self.assertEquals(response.status_code, 200) - - def test_error_page(self): - set_request(method='GET', path='/error') - response = serve.get_response() - self.assertEquals(response.status_code, 500) - - def test_login(self): - set_request(method='GET', path='/login') - response = serve.get_response() - self.assertEquals(response.status_code, 200) - - html = frappe.safe_decode(response.get_data()) - - self.assertTrue('// login.js' in html) - self.assertTrue('' in html) - - def test_app(self): - frappe.set_user('Administrator') - set_request(method='GET', path='/app') - response = serve.get_response() - self.assertEquals(response.status_code, 200) - - html = frappe.safe_decode(response.get_data()) - self.assertTrue('window.app = true;' in html) - frappe.local.session_obj = None - - def test_not_found(self): - set_request(method='GET', path='/_test/missing') - response = serve.get_response() - self.assertEquals(response.status_code, 404) diff --git a/frappe/website/render.py b/frappe/website/render.py index 21d5dda8d5..65f871b8a0 100644 --- a/frappe/website/render.py +++ b/frappe/website/render.py @@ -29,7 +29,7 @@ def render(path=None, http_status_code=None): # temp feature flag if True or frappe.conf.flag_new_website: from frappe.website.serve import get_response - return get_response() + return get_response(path, http_status_code) else: return _render(path, http_status_code) diff --git a/frappe/website/serve.py b/frappe/website/serve.py index 8f686144f1..927909e75f 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -25,26 +25,34 @@ def get_response(path=None, http_status_code=None): # there is no way to determine the type of the page based on the route # so evaluate each type of page sequentially - response = StaticPage(path).get() + response = StaticPage(path, http_status_code).get() if not response: - response = TemplatePage(path).get() + response = TemplatePage(path, http_status_code).get() if not response: - response = ListPage(path).get() + response = ListPage(path, http_status_code).get() if not response: - response = DocumentPage(path).get() + response = DocumentPage(path, http_status_code).get() if not response: - response = TemplatePage('404').get() + response = PrintPage(path, http_status_code).get() + if not response: + response = TemplatePage('404', 404).get() + except frappe.Redirect: + return build_response(path, "", 301, { + "Location": frappe.flags.redirect_location or (frappe.local.response or {}).get('location'), + "Cache-Control": "no-store, no-cache, must-revalidate" + }) except frappe.PermissionError as e: - response = TemplatePage('403').get() - except: - response = TemplatePage('error').get() + frappe.local.message = cstr(e) + response = NotPermittedPage(path, http_status_code).get() + except Exception as e: + response = TemplatePage('error', getattr(e, 'http_status_code', 500) or http_status_code).get() return response class WebPage(object): - def __init__(self, path=None): + def __init__(self, path=None, http_status_code=200): self.headers = None - self.status_code = 200 + self.http_status_code = http_status_code if not path: path = frappe.local.request.path self.path = path.strip('/ ') @@ -240,7 +248,7 @@ class TemplatePage(BaseTemplatePage): search_path + '/index.md') def render(self): - return build_response(self.path, self.get_html(), self.status_code, self.headers) + return build_response(self.path, self.get_html(), self.http_status_code, self.headers) def get_html(self): # context object should be separate from self for security @@ -304,7 +312,8 @@ class TemplatePage(BaseTemplatePage): # TODO: self.context.children = self.run_pymodule_method('get_children') self.context.developer_mode = frappe.conf.developer_mode - self.status_code = self.context.http_status_code or 200 + if self.context.http_status_code: + self.http_status_code = self.context.http_status_code def set_pymodule_properties(self): for prop in ("base_template_path", "template", "no_cache", "sitemap", @@ -385,18 +394,50 @@ class TemplatePage(BaseTemplatePage): return html + def set_standard_path(self, path): + self.app = 'frappe' + self.app_path = frappe.get_app_path('frappe') + self.path = path + self.template_path = 'www/{path}.html'.format(path=path) + + class ListPage(TemplatePage): def validate(self): if frappe.db.get_value('DocType', self.path): - self.app = 'frappe' - self.app_path = frappe.get_app_path('frappe') - self.doctype = self.path - self.path = 'list' - self.template_path = 'www/list.html' - frappe.local.form_dict.doctype = self.doctype + frappe.local.form_dict.doctype = self.path + self.set_standard_path('list') return True return False +class PrintPage(TemplatePage): + ''' + default path returns a printable object (based on permission) + /Quotation/Q-0001 + ''' + def validate(self): + parts = self.path.split('/', 1) + if len(parts)==2: + if (frappe.db.get_value('DocType', parts[0]) + and frappe.db.get_value(parts[0], parts[1])): + frappe.form_dict.doctype = parts[0] + frappe.form_dict.name = parts[1] + self.set_standard_path('printview') + return True + + return False + +class NotPermittedPage(TemplatePage): + def validate(self): + frappe.local.message_title = _("Not Permitted") + frappe.local.response['context'] = dict( + indicator_color = 'red', + primary_action = '/login', + primary_label = _('Login'), + fullpage=True + ) + self.set_standard_path('message') + return True + class DocumentPage(BaseTemplatePage): def validate(self): ''' @@ -477,8 +518,5 @@ class DocumentPage(BaseTemplatePage): return condition_field -class PrintPage(TemplatePage): - pass - class WebFormPage(WebPage): pass diff --git a/frappe/www/message.py b/frappe/www/message.py index ea45b9c4b5..d650170073 100644 --- a/frappe/www/message.py +++ b/frappe/www/message.py @@ -9,7 +9,7 @@ from frappe.utils import strip_html_tags no_cache = 1 def get_context(context): - message_context = {} + message_context = frappe._dict() if hasattr(frappe.local, "message"): message_context["header"] = frappe.local.message_title message_context["title"] = strip_html_tags(frappe.local.message_title) @@ -26,4 +26,10 @@ def get_context(context): if message.get('http_status_code'): frappe.local.response['http_status_code'] = message['http_status_code'] + if not message_context.title: + message_context.title = frappe.form_dict.title + + if not message_context.message: + message_context.message = frappe.form_dict.message + return message_context From b699d92f5ee57d54918221fb9d74c54ca4cfe24a Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 15 Feb 2021 18:05:44 +0530 Subject: [PATCH 007/164] fix(minor): http_status_code --- frappe/website/serve.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/website/serve.py b/frappe/website/serve.py index 927909e75f..b635d9615b 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -12,7 +12,7 @@ from frappe.website.utils import (get_home_page, can_cache, delete_page_cache, get_toc, get_next_link) from frappe.website.doctype.website_settings.website_settings import get_website_settings -def get_response(path=None, http_status_code=None): +def get_response(path=None, http_status_code=200): """render html page""" if not path: path = frappe.local.request.path @@ -45,14 +45,14 @@ def get_response(path=None, http_status_code=None): frappe.local.message = cstr(e) response = NotPermittedPage(path, http_status_code).get() except Exception as e: - response = TemplatePage('error', getattr(e, 'http_status_code', 500) or http_status_code).get() + response = TemplatePage('error', getattr(e, 'http_status_code', None) or http_status_code).get() return response class WebPage(object): - def __init__(self, path=None, http_status_code=200): + def __init__(self, path=None, http_status_code=None): self.headers = None - self.http_status_code = http_status_code + self.http_status_code = http_status_code or 200 if not path: path = frappe.local.request.path self.path = path.strip('/ ') @@ -488,7 +488,7 @@ class DocumentPage(BaseTemplatePage): html = frappe.get_template(self.context.template_path).render(self.context) html = self.add_csrf_token(html) - return build_response(self.path, html, self.status_code, self.headers) + return build_response(self.path, html, self.http_status_code or 200, self.headers) def update_context(self): self.context.doc = self.doc From 30659b9ee28e034c6eef7ba682eea5b302b6fb6b Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 11 Mar 2021 10:29:12 +0530 Subject: [PATCH 008/164] feat: Option to consider query string while writing website redirect - if match_with_query_string is true in a website redirect rule, system will try to match the regex with the result of route + query_string --- frappe/tests/test_website.py | 20 +++++++++++++------- frappe/website/redirect.py | 12 ++++++++---- frappe/website/serve.py | 28 ++++++++++++++++++---------- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/frappe/tests/test_website.py b/frappe/tests/test_website.py index 91e08db457..ce165e36d9 100644 --- a/frappe/tests/test_website.py +++ b/frappe/tests/test_website.py @@ -51,7 +51,7 @@ class TestWebsite(unittest.TestCase): def test_page_load(self): set_request(method='POST', path='login') - response = render.render() + response = serve.get_response() self.assertEquals(response.status_code, 200) @@ -103,7 +103,8 @@ class TestWebsite(unittest.TestCase): frappe.hooks.website_redirects = [ dict(source=r'/testfrom', target=r'://testto1'), dict(source=r'/testfromregex.*', target=r'://testto2'), - dict(source=r'/testsub/(.*)', target=r'://testto3/\1') + dict(source=r'/testsub/(.*)', target=r'://testto3/\1'), + dict(source=r'/courses/course\?course=(.*)', target=r'/courses/\1', match_with_query_string=True), ] website_settings = frappe.get_doc('Website Settings') @@ -117,28 +118,33 @@ class TestWebsite(unittest.TestCase): frappe.cache().delete_key('website_redirects') set_request(method='GET', path='/testfrom') - response = render.render() + response = serve.get_response() self.assertEquals(response.status_code, 301) self.assertEquals(response.headers.get('Location'), r'://testto1') set_request(method='GET', path='/testfromregex/test') - response = render.render() + response = serve.get_response() self.assertEquals(response.status_code, 301) self.assertEquals(response.headers.get('Location'), r'://testto2') set_request(method='GET', path='/testsub/me') - response = render.render() + response = serve.get_response() self.assertEquals(response.status_code, 301) self.assertEquals(response.headers.get('Location'), r'://testto3/me') set_request(method='GET', path='/test404') - response = render.render() + response = serve.get_response() self.assertEquals(response.status_code, 404) set_request(method='GET', path='/testsource') - response = render.render() + response = serve.get_response() self.assertEquals(response.status_code, 301) self.assertEquals(response.headers.get('Location'), '/testtarget') + set_request(method='GET', path='/courses/course?course=data') + response = serve.get_response() + self.assertEquals(response.status_code, 301) + self.assertEquals(response.headers.get('Location'), '/courses/data') + delattr(frappe.hooks, 'website_redirects') frappe.cache().delete_key('app_hooks') diff --git a/frappe/website/redirect.py b/frappe/website/redirect.py index 73e3c21727..e66c0a3b7b 100644 --- a/frappe/website/redirect.py +++ b/frappe/website/redirect.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import re, frappe -def resolve_redirect(path): +def resolve_redirect(path, query_string=None): ''' Resolve redirects from hooks @@ -33,9 +33,13 @@ def resolve_redirect(path): for rule in redirects: pattern = rule['source'].strip('/ ') + '$' - if re.match(pattern, path): - redirect_to = re.sub(pattern, rule['target'], path) + 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, redirect_to) + frappe.cache().hset('website_redirects', path_to_match, redirect_to) raise frappe.Redirect diff --git a/frappe/website/serve.py b/frappe/website/serve.py index b635d9615b..11e950573e 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -1,25 +1,33 @@ -import frappe -import os, mimetypes +import mimetypes +import os from werkzeug.wrappers import Response from werkzeug.wsgi import wrap_file -from frappe.website.render import (resolve_path, build_response) +import frappe +from frappe import _ +from frappe.utils import cint, cstr +from frappe.model.document import get_controller +from frappe.website.doctype.website_settings.website_settings import \ + get_website_settings from frappe.website.redirect import resolve_redirect -from frappe.website.router import (get_start_folders, get_doctypes_with_web_view, - get_page_info_from_web_page_with_dynamic_routes) -from frappe.website.utils import (get_home_page, can_cache, delete_page_cache, - get_toc, get_next_link) -from frappe.website.doctype.website_settings.website_settings import get_website_settings +from frappe.website.render import build_response, resolve_path +from frappe.website.router import (get_doctypes_with_web_view, + get_page_info_from_web_page_with_dynamic_routes, get_start_folders) +from frappe.website.utils import (can_cache, delete_page_cache, get_home_page, + get_next_link, get_toc) + def get_response(path=None, http_status_code=200): """render html page""" + query_string = None if not path: path = frappe.local.request.path + query_string = frappe.local.request.query_string try: path = path.strip('/ ') - resolve_redirect(path) + resolve_redirect(path, query_string) path = resolve_path(path) data = None @@ -513,7 +521,7 @@ class DocumentPage(BaseTemplatePage): if meta.is_published_field: condition_field = meta.is_published_field elif not meta.custom: - controller = get_controller(doctype) + controller = get_controller(meta.doctype) condition_field = controller.website.condition_field return condition_field From 39da825592429ab933e65368ef30d39de7a9208e Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 12 Mar 2021 11:42:24 +0530 Subject: [PATCH 009/164] refactor: Move page controllers to separate files - Make template page work --- .../page_controllers/base_template_page.py | 128 +++++ .../website/page_controllers/document_page.py | 88 ++++ frappe/website/page_controllers/list_page.py | 10 + .../page_controllers/not_permitted_page.py | 15 + frappe/website/page_controllers/print_page.py | 19 + .../website/page_controllers/static_page.py | 40 ++ .../website/page_controllers/template_page.py | 276 ++++++++++ frappe/website/page_controllers/web_form.py | 4 + frappe/website/page_controllers/web_page.py | 21 + frappe/website/serve.py | 496 +----------------- 10 files changed, 610 insertions(+), 487 deletions(-) create mode 100644 frappe/website/page_controllers/base_template_page.py create mode 100644 frappe/website/page_controllers/document_page.py create mode 100644 frappe/website/page_controllers/list_page.py create mode 100644 frappe/website/page_controllers/not_permitted_page.py create mode 100644 frappe/website/page_controllers/print_page.py create mode 100644 frappe/website/page_controllers/static_page.py create mode 100644 frappe/website/page_controllers/template_page.py create mode 100644 frappe/website/page_controllers/web_form.py create mode 100644 frappe/website/page_controllers/web_page.py diff --git a/frappe/website/page_controllers/base_template_page.py b/frappe/website/page_controllers/base_template_page.py new file mode 100644 index 0000000000..10e2375356 --- /dev/null +++ b/frappe/website/page_controllers/base_template_page.py @@ -0,0 +1,128 @@ +import frappe +from frappe import _ +from frappe.website.context import add_sidebar_and_breadcrumbs +from frappe.website.doctype.website_settings.website_settings import \ + get_website_settings +from frappe.website.page_controllers.web_page import WebPage + + +class BaseTemplatePage(WebPage): + def init_context(self): + self.context = frappe._dict() + self.context.update(get_website_settings(self.context)) + self.context.update(frappe.local.conf.get("website_context") or {}) + + def add_csrf_token(self, html): + if frappe.local.session: + return html.replace("", ''.format( + frappe.local.session.data.csrf_token)) + else: + return html + + def post_process_context(self): + self.set_missing_values() + self.add_metatags() + self.add_sidebar_and_breadcrumbs() + + self.set_base_template_if_missing() + self.set_title_with_prefix() + self.update_website_context() + + # set using frappe.respond_as_web_page + if hasattr(frappe.local, 'response') and frappe.local.response.get('context'): + self.context.update(frappe.local.response.context) + + # to be able to inspect the context dict + # Use the macro "inspect" from macros.html + self.context._context_dict = self.context + + # context sends us a new template path + if self.context.template: + self.template_path = self.context.template + + + def set_base_template_if_missing(self): + if not self.context.base_template_path: + app_base = frappe.get_hooks("base_template") + self.context.base_template_path = app_base[-1] if app_base else "templates/base.html" + + def set_title_with_prefix(self): + if (self.context.title_prefix and self.context.title + and not self.context.title.startswith(self.context.title_prefix)): + self.context.title = '{0} - {1}'.format(self.context.title_prefix, self.context.title) + + def update_website_context(self): + # apply context from hooks + update_website_context = frappe.get_hooks('update_website_context') + for method in update_website_context: + values = frappe.get_attr(method)(self.context) + if values: + self.context.update(values) + + def add_metatags(self): + self.tags = frappe._dict(self.context.get("metatags") or {}) + self.init_metatags_from_context() + self.set_opengraph_tags() + self.set_twitter_tags() + self.set_meta_published_on() + self.set_metatags_from_website_route_meta() + + self.context.metatags = self.tags + + def add_sidebar_and_breadcrumbs(self): + add_sidebar_and_breadcrumbs(self.context) + + def init_metatags_from_context(self): + for key in ('title', 'description', 'image', 'author', 'url', 'published_on'): + if not key in self.tags and self.context.get(key): + self.tags[key] = self.context[key] + + if not self.tags.get('title'): self.tags['title'] = self.context.get('name') + + if self.tags.get('image'): + self.tags['image'] = frappe.utils.get_url(self.tags['image']) + + self.tags["language"] = frappe.local.lang or "en" + + def set_opengraph_tags(self): + if "og:type" not in self.tags: + self.tags["og:type"] = "article" + + for key in ('title', 'description', 'image', 'author', 'url'): + if self.tags.get(key): + self.tags['og:' + key] = self.tags.get(key) + + def set_twitter_tags(self): + for key in ('title', 'description', 'image', 'author', 'url'): + if self.tags.get(key): + self.tags['twitter:' + key] = self.tags.get(key) + + if self.tags.get('image'): + self.tags['twitter:card'] = "summary_large_image" + else: + self.tags["twitter:card"] = "summary" + + def set_meta_published_on(self): + if "published_on" in self.tags: + self.tags["datePublished"] = self.tags["published_on"] + del self.tags["published_on"] + + def set_metatags_from_website_route_meta(self): + ''' + Get meta tags from Website Route meta + they can override the defaults set above + ''' + route = self.context.path + if route == '': + # homepage + route = frappe.db.get_single_value('Website Settings', 'home_page') + + route_exists = (route + and not route.endswith(('.js', '.css')) + and frappe.db.exists('Website Route Meta', route)) + + if route_exists: + website_route_meta = frappe.get_doc('Website Route Meta', route) + for meta_tag in website_route_meta.meta_tags: + d = meta_tag.get_meta_dict() + self.tags.update(d) diff --git a/frappe/website/page_controllers/document_page.py b/frappe/website/page_controllers/document_page.py new file mode 100644 index 0000000000..bb00d78c0c --- /dev/null +++ b/frappe/website/page_controllers/document_page.py @@ -0,0 +1,88 @@ +import frappe +from frappe import _ +from frappe.model.document import get_controller +from frappe.website.page_controllers.base_template_page import BaseTemplatePage +from frappe.website.render import build_response +from frappe.website.router import (get_doctypes_with_web_view, + get_page_info_from_web_page_with_dynamic_routes) + + +class DocumentPage(BaseTemplatePage): + def validate(self): + ''' + Find a document with matching `route` from all doctypes with `has_web_view`=1 + ''' + if self.search_in_doctypes_with_web_view(): + return True + + if self.search_web_page_dynamic_routes(): + return True + + return False + + def search_in_doctypes_with_web_view(self): + for doctype in get_doctypes_with_web_view(): + filters = dict(route=self.path) + meta = frappe.get_meta(doctype) + condition_field = self.get_condition_field(meta) + + if condition_field: + filters[condition_field] = 1 + + try: + self.docname = frappe.db.get_value(doctype, filters, 'name') + if self.docname: + self.doctype = doctype + self.meta = meta + return True + except Exception as e: + if not frappe.db.is_missing_column(e): raise e + + def search_web_page_dynamic_routes(self): + d = get_page_info_from_web_page_with_dynamic_routes(self.path) + if d: + self.doctype = 'Web Page' + self.docname = d.name + self.meta = frappe.get_meta(self.doctype) + return True + else: + return False + + def render(self): + self.doc = frappe.get_doc(self.doctype, self.docname) + self.init_context() + self.update_context() + self.post_process_context() + + html = frappe.get_template(self.context.template_path).render(self.context) + html = self.add_csrf_token(html) + + return build_response(self.path, html, self.http_status_code or 200, self.headers) + + def update_context(self): + self.context.doc = self.doc + self.context.update(self.context.doc.as_dict()) + self.context.update(self.context.doc.get_website_properties()) + + if not self.context.template_path: + self.context.template_path = self.context.doc.meta.get_web_template() + + if hasattr(self.doc, "get_context"): + ret = self.doc.get_context(self.context) + + if ret: + self.context.update(ret) + + for prop in ("no_cache", "sitemap"): + if not prop in self.context: + self.context[prop] = getattr(self.doc, prop, False) + + def get_condition_field(self, meta): + condition_field = None + if meta.is_published_field: + condition_field = meta.is_published_field + elif not meta.custom: + controller = get_controller(meta.doctype) + condition_field = controller.website.condition_field + + return condition_field diff --git a/frappe/website/page_controllers/list_page.py b/frappe/website/page_controllers/list_page.py new file mode 100644 index 0000000000..2cb6ed5f26 --- /dev/null +++ b/frappe/website/page_controllers/list_page.py @@ -0,0 +1,10 @@ +import frappe +from frappe.website.page_controllers.template_page import TemplatePage + +class ListPage(TemplatePage): + def validate(self): + if frappe.db.get_value('DocType', self.path): + frappe.local.form_dict.doctype = self.path + self.set_standard_path('list') + return True + return False diff --git a/frappe/website/page_controllers/not_permitted_page.py b/frappe/website/page_controllers/not_permitted_page.py new file mode 100644 index 0000000000..e0cd6bf970 --- /dev/null +++ b/frappe/website/page_controllers/not_permitted_page.py @@ -0,0 +1,15 @@ +import frappe +from frappe import _ +from frappe.website.page_controllers.template_page import TemplatePage + +class NotPermittedPage(TemplatePage): + def validate(self): + frappe.local.message_title = _("Not Permitted") + frappe.local.response['context'] = dict( + indicator_color = 'red', + primary_action = '/login', + primary_label = _('Login'), + fullpage=True + ) + self.set_standard_path('message') + return True diff --git a/frappe/website/page_controllers/print_page.py b/frappe/website/page_controllers/print_page.py new file mode 100644 index 0000000000..6daea7fcbc --- /dev/null +++ b/frappe/website/page_controllers/print_page.py @@ -0,0 +1,19 @@ +import frappe +from frappe.website.page_controllers.template_page import TemplatePage + +class PrintPage(TemplatePage): + ''' + default path returns a printable object (based on permission) + /Quotation/Q-0001 + ''' + def validate(self): + parts = self.path.split('/', 1) + if len(parts)==2: + if (frappe.db.get_value('DocType', parts[0]) + and frappe.db.get_value(parts[0], parts[1])): + frappe.form_dict.doctype = parts[0] + frappe.form_dict.name = parts[1] + self.set_standard_path('printview') + return True + + return False diff --git a/frappe/website/page_controllers/static_page.py b/frappe/website/page_controllers/static_page.py new file mode 100644 index 0000000000..4b57c3ec42 --- /dev/null +++ b/frappe/website/page_controllers/static_page.py @@ -0,0 +1,40 @@ +import mimetypes +import os + +from werkzeug.wrappers import Response +from werkzeug.wsgi import wrap_file + +import frappe +from frappe.website.page_controllers.web_page import WebPage + + +class StaticPage(WebPage): + def validate(self): + if ('.' not in self.path): + return False + extn = self.path.rsplit('.', 1)[-1] + if extn in ('html', 'md', 'js', 'xml', 'css', 'txt', 'py', 'json'): + return False + + if self.find_path_in_apps(): + return True + + return False + + def find_path_in_apps(self): + for app in frappe.get_installed_apps(): + file_path = frappe.get_app_path(app, 'www') + '/' + self.path + if os.path.exists(file_path): + self.file_path = file_path + return True + return False + + def render(self): + try: + f = open(self.file_path, 'rb') + except IOError: + raise frappe.exceptions.NotFound + + response = Response(wrap_file(frappe.local.request.environ, f), direct_passthrough=True) + response.mimetype = mimetypes.guess_type(self.file_path)[0] or 'application/octet-stream' + return response diff --git a/frappe/website/page_controllers/template_page.py b/frappe/website/page_controllers/template_page.py new file mode 100644 index 0000000000..46e6e6a57e --- /dev/null +++ b/frappe/website/page_controllers/template_page.py @@ -0,0 +1,276 @@ +import io +import os + +import frappe +from frappe import _ +from frappe.website.page_controllers.base_template_page import BaseTemplatePage +from frappe.website.render import build_response +from frappe.website.utils import (extract_comment_tag, extract_title, + get_next_link, get_toc) + + +class TemplatePage(BaseTemplatePage): + def validate(self): + for app in frappe.get_installed_apps(frappe_last=True): + if self.find_page_in_app(app): + return True + + def find_page_in_app(self, app): + ''' + Searches for file matching the path in the /www + and /templates/pages folders + ''' + app_path = frappe.get_app_path(app) + folders = get_start_folders() + + for dirname in folders: + search_path = os.path.join(app_path, dirname, self.path) + for p in self.get_index_path_options(search_path): + file_path = frappe.as_unicode(p) + if os.path.exists(file_path) and not os.path.isdir(file_path): + self.app = app + self.app_path = app_path + self.template_path = os.path.relpath(file_path, self.app_path) + return True + + def get_index_path_options(self, search_path): + return ( + search_path, + search_path + '.html', + search_path + '.md', + search_path + '/index.html', + search_path + '/index.md') + + def render(self): + return build_response(self.path, self.get_html(), self.http_status_code, self.headers) + + def get_html(self): + # context object should be separate from self for security + # because it will be accessed via the user defined template + self.init_context() + + self.set_pymodule() + self.setup_template() + self.update_context() + self.post_process_context() + html = self.render_template() + + html = self.update_toc(html) + html = self.add_csrf_token(html) + + return html + + def set_pymodule(self): + ''' + A template may have a python module with a `get_context` method along with it in the + same folder. Also the hyphens will be coverted to underscore for python module names. + This method sets the pymodule_name if it exists. + ''' + self.basepath = self.template_path.rsplit('.', 1)[0] + self.pymodule_name = None + + # replace - with _ in the internal modules names + self.pymodule_path = os.path.join(self.basepath.replace("-", "_") + ".py") + + if os.path.exists(os.path.join(self.app_path, self.pymodule_path)): + self.pymodule_name = self.app + "." + self.pymodule_path.replace(os.path.sep, ".")[:-3] + + def setup_template(self): + '''Setup template source, frontmatter and markdown conversion''' + self.source = self.get_raw_template() + + if self.template_path.endswith(('.md', '.html')): + self.extract_frontmatter() + + self.convert_from_markdown() + + if self.extends_template(): + self.context.base_template_path = self.context.base_template_path or 'templates/base.html' + + + # TODO: setup index.txt ? + + def update_context(self): + self.set_page_properties() + self.set_properties_from_source() + self.load_colocated_files() + self.context.build_version = frappe.utils.get_build_version() + + if self.pymodule_name: + self.pymodule = frappe.get_module(self.pymodule_name) + self.set_pymodule_properties() + + data = self.run_pymodule_method('get_context') + + # some methods may return a "context" object + if data: self.context.update(data) + + # TODO: self.context.children = self.run_pymodule_method('get_children') + + self.context.developer_mode = frappe.conf.developer_mode + if self.context.http_status_code: + self.http_status_code = self.context.http_status_code + + def set_pymodule_properties(self): + for prop in ("base_template_path", "template", "no_cache", "sitemap", + "condition_field"): + if hasattr(self.pymodule, prop): + self.context[prop] = getattr(self.pymodule, prop) + + def set_page_properties(self): + self.context.template = self.template_path + self.context.base_template = self.context.base_template or 'templates/web.html' + + def set_properties_from_source(self): + if not self.source: + return + context = self.context + if not context.title: + context.title = extract_title(self.source, self.path) + + base_template = extract_comment_tag(self.source, 'base_template') + if base_template: + context.base_template = base_template + + if (context.base_template + and "{%- extends" not in self.source + and "{% extends" not in self.source + and "" not in self.source): + self.source = '''{{% extends "{0}" %}} + {{% block page_content %}}{1}{{% endblock %}}'''.format(context.base_template, self.source) + + if "" in self.source: + context.no_breadcrumbs = 1 + + if "" in self.source: + context.show_sidebar = 1 + + if "" in self.source: + context.add_breadcrumbs = 1 + + if "" in self.source: + context.no_header = 1 + + if "" in self.source: + context.add_next_prev_links = 1 + + if "" in self.source: + context.no_cache = 1 + + if "" in self.source: + context.sitemap = 0 + + if "" in self.source: + context.sitemap = 1 + + def run_pymodule_method(self, method): + if hasattr(self.pymodule, method): + try: + return getattr(self.pymodule, method)(self.context) + except (frappe.PermissionError, frappe.DoesNotExistError, frappe.Redirect): + raise + except: + if not frappe.flags.in_migrate: + frappe.errprint(frappe.utils.get_traceback()) + + def render_template(self): + if self.source: + html = frappe.render_template(self.source, self.context) + elif self.template_path: + if self.path.endswith('min.js'): + html = self.get_raw_template() # static + else: + html = frappe.get_template(self.template_path).render(self.context) + + return html + + def extends_template(self): + return (self.template_path.endswith(('.html', '.md', )) + and ('{%- extends' in self.source + or '{% extends' in self.source)) + + def get_raw_template(self): + return frappe.get_jloader().get_source(frappe.get_jenv(), self.template_path)[0] + + def load_colocated_files(self): + '''load co-located css/js files with the same name''' + js_path = self.basepath + '.js' + if os.path.exists(js_path) and '{% block script %}' not in self.source: + self.context.colocated_js = self.get_colocated_file(js_path) + + css_path = self.basepath + '.css' + if os.path.exists(css_path) and '{% block style %}' not in self.source: + self.context.colocated_css = self.get_colocated_file(css_path) + + def get_colocated_file(self, path): + with io.open(path, 'r', encoding = 'utf-8') as f: + return f.read() + + def extract_frontmatter(self): + try: + # values will be used to update self + res = get_frontmatter(self.source) + if res['attributes']: + self.context.update(res['attributes']) + self.source = res['body'] + except Exception: + pass + + def convert_from_markdown(self): + if self.template_path.endswith('.md'): + self.source = frappe.utils.md_to_html(self.source) + self.page_toc_html = self.source.toc_html + + if not self.context.show_sidebar: + self.source = '
' + self.source + '
' + + def update_toc(self, html): + if '{index}' in html: + html = html.replace('{index}', get_toc(self.path)) + + if '{next}' in html: + html = html.replace('{next}', get_next_link(self.path)) + + return html + + def set_standard_path(self, path): + self.app = 'frappe' + self.app_path = frappe.get_app_path('frappe') + self.path = path + self.template_path = 'www/{path}.html'.format(path=path) + + def set_missing_values(self): + if not "url_prefix" in self.context: + self.context.url_prefix = "" + + if self.context.url_prefix and self.context.url_prefix[-1]!='/': + self.context.url_prefix += '/' + + # for backward compatibility + self.context.docs_base_url = '/docs' + self.context.path = self.path + + +def get_start_folders(): + return frappe.local.flags.web_pages_folders or ('www', 'templates/pages') + +def get_frontmatter(string): + """ + Reference: https://github.com/jonbeebe/frontmatter + """ + import re + + import yaml + + fmatter = "" + body = "" + result = re.compile(r'^\s*(?:---|\+\+\+)(.*?)(?:---|\+\+\+)\s*(.+)$', re.S | re.M).search(string) + + if result: + fmatter = result.group(1) + body = result.group(2) + + return { + "attributes": yaml.safe_load(fmatter), + "body": body, + } diff --git a/frappe/website/page_controllers/web_form.py b/frappe/website/page_controllers/web_form.py new file mode 100644 index 0000000000..030e8bb3bc --- /dev/null +++ b/frappe/website/page_controllers/web_form.py @@ -0,0 +1,4 @@ +from frappe.website.page_controllers.web_page import WebPage + +class WebFormPage(WebPage): + pass diff --git a/frappe/website/page_controllers/web_page.py b/frappe/website/page_controllers/web_page.py new file mode 100644 index 0000000000..d4af2860f8 --- /dev/null +++ b/frappe/website/page_controllers/web_page.py @@ -0,0 +1,21 @@ +import frappe +from frappe import _ + +class WebPage(object): + def __init__(self, path=None, http_status_code=None): + self.headers = None + self.http_status_code = http_status_code or 200 + if not path: + path = frappe.local.request.path + self.path = path.strip('/ ') + + def get(self): + if self.validate(): + return self.render() + + def validate(self): + pass + + def render(self): + pass + diff --git a/frappe/website/serve.py b/frappe/website/serve.py index 11e950573e..10eddfc641 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -1,22 +1,16 @@ -import mimetypes -import os - -from werkzeug.wrappers import Response -from werkzeug.wsgi import wrap_file - import frappe from frappe import _ -from frappe.utils import cint, cstr -from frappe.model.document import get_controller -from frappe.website.doctype.website_settings.website_settings import \ - get_website_settings +from frappe.utils import cstr + +from frappe.website.page_controllers.document_page import DocumentPage +from frappe.website.page_controllers.list_page import ListPage +from frappe.website.page_controllers.not_permitted_page import NotPermittedPage +from frappe.website.page_controllers.print_page import PrintPage +from frappe.website.page_controllers.template_page import TemplatePage +from frappe.website.page_controllers.static_page import StaticPage + from frappe.website.redirect import resolve_redirect from frappe.website.render import build_response, resolve_path -from frappe.website.router import (get_doctypes_with_web_view, - get_page_info_from_web_page_with_dynamic_routes, get_start_folders) -from frappe.website.utils import (can_cache, delete_page_cache, get_home_page, - get_next_link, get_toc) - def get_response(path=None, http_status_code=200): """render html page""" @@ -56,475 +50,3 @@ def get_response(path=None, http_status_code=200): response = TemplatePage('error', getattr(e, 'http_status_code', None) or http_status_code).get() return response - -class WebPage(object): - def __init__(self, path=None, http_status_code=None): - self.headers = None - self.http_status_code = http_status_code or 200 - if not path: - path = frappe.local.request.path - self.path = path.strip('/ ') - - def get(self): - if self.validate(): - return self.render() - - def validate(self): - pass - - def render(self): - pass - -class StaticPage(WebPage): - def validate(self): - if ('.' not in self.path): - return False - extn = self.path.rsplit('.', 1)[-1] - if extn in ('html', 'md', 'js', 'xml', 'css', 'txt', 'py', 'json'): - return False - - if self.find_path_in_apps(): - return True - - return False - - def find_path_in_apps(self): - for app in frappe.get_installed_apps(): - file_path = frappe.get_app_path(app, 'www') + '/' + self.path - if os.path.exists(file_path): - self.file_path = file_path - return True - return False - - def render(self): - try: - f = open(self.file_path, 'rb') - except IOError: - raise NotFound - - response = Response(wrap_file(frappe.local.request.environ, f), direct_passthrough=True) - response.mimetype = mimetypes.guess_type(self.file_path)[0] or 'application/octet-stream' - return response - -class BaseTemplatePage(WebPage): - def init_context(self): - self.context = frappe._dict() - self.context.update(get_website_settings(self.context)) - self.context.update(frappe.local.conf.get("website_context") or {}) - - def add_csrf_token(self, html): - if frappe.local.session: - return html.replace("", ''.format( - frappe.local.session.data.csrf_token)) - else: - return html - - def post_process_context(self): - self.add_metatags() - # add_sidebar_and_breadcrumbs(context) - - self.set_base_template_if_missing() - self.set_title_with_prefix() - self.update_website_context() - - # set using frappe.respond_as_web_page - if hasattr(frappe.local, 'response') and frappe.local.response.get('context'): - self.context.update(frappe.local.response.context) - - # to be able to inspect the context dict - # Use the macro "inspect" from macros.html - self.context._context_dict = self.context - - # context sends us a new template path - if self.context.template: - self.template_path = self.context.template - - - def set_base_template_if_missing(self): - if not self.context.base_template_path: - app_base = frappe.get_hooks("base_template") - self.context.base_template_path = app_base[-1] if app_base else "templates/base.html" - - def set_title_with_prefix(self): - if (self.context.title_prefix and self.context.title - and not self.context.title.startswith(self.context.title_prefix)): - self.context.title = '{0} - {1}'.format(self.context.title_prefix, self.context.title) - - def update_website_context(self): - # apply context from hooks - update_website_context = frappe.get_hooks('update_website_context') - for method in update_website_context: - values = frappe.get_attr(method)(self.context) - if values: - self.context.update(values) - - def add_metatags(self): - self.tags = frappe._dict(self.context.get("metatags") or {}) - self.init_metatags_from_context() - self.set_opengraph_tags() - self.set_twitter_tags() - self.set_meta_published_on() - self.set_metatags_from_website_route_meta() - - self.context.metatags = self.tags - - def init_metatags_from_context(self): - for key in ('title', 'description', 'image', 'author', 'url', 'published_on'): - if not key in self.tags and self.context.get(key): - self.tags[key] = self.context[key] - - if not self.tags.get('title'): self.tags['title'] = self.context.get('name') - - if self.tags.get('image'): - self.tags['image'] = frappe.utils.get_url(self.tags['image']) - - self.tags["language"] = frappe.local.lang or "en" - - def set_opengraph_tags(self): - if "og:type" not in self.tags: - self.tags["og:type"] = "article" - - for key in ('title', 'description', 'image', 'author', 'url'): - if self.tags.get(key): - self.tags['og:' + key] = self.tags.get(key) - - def set_twitter_tags(self): - for key in ('title', 'description', 'image', 'author', 'url'): - if self.tags.get(key): - self.tags['twitter:' + key] = self.tags.get(key) - - if self.tags.get('image'): - self.tags['twitter:card'] = "summary_large_image" - else: - self.tags["twitter:card"] = "summary" - - def set_meta_published_on(self): - if "published_on" in self.tags: - self.tags["datePublished"] = self.tags["published_on"] - del self.tags["published_on"] - - def set_metatags_from_website_route_meta(self): - ''' - Get meta tags from Website Route meta - they can override the defaults set above - ''' - route = self.context.path - if route == '': - # homepage - route = frappe.db.get_single_value('Website Settings', 'home_page') - - route_exists = (route - and not route.endswith(('.js', '.css')) - and frappe.db.exists('Website Route Meta', route)) - - if route_exists: - website_route_meta = frappe.get_doc('Website Route Meta', route) - for meta_tag in website_route_meta.meta_tags: - d = meta_tag.get_meta_dict() - self.tags.update(d) - -class TemplatePage(BaseTemplatePage): - def validate(self): - for app in frappe.get_installed_apps(frappe_last=True): - if self.find_page_in_app(app): - return True - - def find_page_in_app(self, app): - ''' - Searches for file matching the path in the /www - and /templates/pages folders - ''' - app_path = frappe.get_app_path(app) - folders = get_start_folders() - - for dirname in folders: - search_path = os.path.join(app_path, dirname, self.path) - for p in self.get_index_path_options(search_path): - file_path = frappe.as_unicode(p) - if os.path.exists(file_path) and not os.path.isdir(file_path): - self.app = app - self.app_path = app_path - self.template_path = os.path.relpath(file_path, self.app_path) - return True - - def get_index_path_options(self, search_path): - return ( - search_path, - search_path + '.html', - search_path + '.md', - search_path + '/index.html', - search_path + '/index.md') - - def render(self): - return build_response(self.path, self.get_html(), self.http_status_code, self.headers) - - def get_html(self): - # context object should be separate from self for security - # because it will be accessed via the user defined template - self.init_context() - - self.set_pymodule() - self.update_context() - self.post_process_context() - self.setup_template() - html = self.render_template() - - html = self.update_toc(html) - html = self.add_csrf_token(html) - - return html - - def set_pymodule(self): - ''' - A template may have a python module with a `get_context` method along with it in the - same folder. Also the hyphens will be coverted to underscore for python module names. - This method sets the pymodule_name if it exists. - ''' - self.basepath = self.template_path.rsplit('.', 1)[0] - self.pymodule_name = None - - # replace - with _ in the internal modules names - self.pymodule_path = os.path.join(self.basepath.replace("-", "_") + ".py") - - if os.path.exists(os.path.join(self.app_path, self.pymodule_path)): - self.pymodule_name = self.app + "." + self.pymodule_path.replace(os.path.sep, ".")[:-3] - - def setup_template(self): - '''Setup template source, frontmatter and markdown conversion''' - self.source = self.get_raw_template() - - if self.template_path.endswith(('.md', '.html')): - self.extract_frontmatter() - - self.convert_from_markdown() - - if self.extends_template(): - self.context.base_template_path = self.context.base_template_path or 'templates/base.html' - else: - self.source = None # clear the source - - # TODO: setup index.txt ? - - def update_context(self): - self.set_page_properties() - - if self.pymodule_name: - self.pymodule = frappe.get_module(self.pymodule_name) - self.set_pymodule_properties() - - data = self.run_pymodule_method('get_context') - - # some methods may return a "context" object - if data: self.context.update(data) - - # TODO: self.context.children = self.run_pymodule_method('get_children') - - self.context.developer_mode = frappe.conf.developer_mode - if self.context.http_status_code: - self.http_status_code = self.context.http_status_code - - def set_pymodule_properties(self): - for prop in ("base_template_path", "template", "no_cache", "sitemap", - "condition_field"): - if hasattr(self.pymodule, prop): - self.context[prop] = getattr(self.pymodule, prop) - - def set_page_properties(self): - self.context.template = self.template_path - - def run_pymodule_method(self, method): - if hasattr(self.pymodule, method): - try: - return getattr(self.pymodule, method)(self.context) - except (frappe.PermissionError, frappe.DoesNotExistError, frappe.Redirect): - raise - except: - if not frappe.flags.in_migrate: - frappe.errprint(frappe.utils.get_traceback()) - - def render_template(self): - if self.source: - html = frappe.render_template(self.source, self.context) - elif self.template_path: - if self.path.endswith('min.js'): - html = self.get_raw_template() # static - else: - html = frappe.get_template(self.template_path).render(self.context) - - return html - - def extends_template(self): - return (self.template_path.endswith(('.html', '.md', )) - and ('{%- extends' in self.source - or '{% extends' in self.source)) - - def get_raw_template(self): - return frappe.get_jloader().get_source(frappe.get_jenv(), self.template_path)[0] - - def load_colocated_files(self): - '''load co-located css/js files with the same name''' - js_path = self.basepath + '.js' - if os.path.exists(js_path) and '{% block script %}' not in self.source: - self.colocated_js = self.get_colocated_file(js_path) - - css_path = self.basepath + '.css' - if os.path.exists(css_path) and '{% block style %}' not in self.source: - self.colocated_css = self.get_colocated_file(css_path) - - def get_colocated_file(self, path): - with io.open(path, 'r', encoding = 'utf-8') as f: - return f.read() - - def extract_frontmatter(self): - try: - # values will be used to update page_info - res = get_frontmatter(self.source) - if res['attributes']: - self.context.update(res['attributes']) - self.source = res['body'] - except Exception: - pass - - def convert_from_markdown(self): - if self.template_path.endswith('.md'): - self.source = frappe.utils.md_to_html(self.source) - self.page_toc_html = self.toc_html - - if not self.show_sidebar: - self.source = '
' + self.source + '
' - - def update_toc(self, html): - if '{index}' in html: - html = html.replace('{index}', get_toc(self.path)) - - if '{next}' in html: - html = html.replace('{next}', get_next_link(self.path)) - - return html - - def set_standard_path(self, path): - self.app = 'frappe' - self.app_path = frappe.get_app_path('frappe') - self.path = path - self.template_path = 'www/{path}.html'.format(path=path) - - -class ListPage(TemplatePage): - def validate(self): - if frappe.db.get_value('DocType', self.path): - frappe.local.form_dict.doctype = self.path - self.set_standard_path('list') - return True - return False - -class PrintPage(TemplatePage): - ''' - default path returns a printable object (based on permission) - /Quotation/Q-0001 - ''' - def validate(self): - parts = self.path.split('/', 1) - if len(parts)==2: - if (frappe.db.get_value('DocType', parts[0]) - and frappe.db.get_value(parts[0], parts[1])): - frappe.form_dict.doctype = parts[0] - frappe.form_dict.name = parts[1] - self.set_standard_path('printview') - return True - - return False - -class NotPermittedPage(TemplatePage): - def validate(self): - frappe.local.message_title = _("Not Permitted") - frappe.local.response['context'] = dict( - indicator_color = 'red', - primary_action = '/login', - primary_label = _('Login'), - fullpage=True - ) - self.set_standard_path('message') - return True - -class DocumentPage(BaseTemplatePage): - def validate(self): - ''' - Find a document with matching `route` from all doctypes with `has_web_view`=1 - ''' - if self.search_in_doctypes_with_web_view(): - return True - - if self.search_web_page_dynamic_routes(): - return True - - return False - - def search_in_doctypes_with_web_view(self): - for doctype in get_doctypes_with_web_view(): - filters = dict(route=self.path) - meta = frappe.get_meta(doctype) - condition_field = self.get_condition_field(meta) - - if condition_field: - filters[condition_field] = 1 - - try: - self.docname = frappe.db.get_value(doctype, filters, 'name') - if self.docname: - self.doctype = doctype - self.meta = meta - return True - except Exception as e: - if not frappe.db.is_missing_column(e): raise e - - def search_web_page_dynamic_routes(self): - d = get_page_info_from_web_page_with_dynamic_routes(self.path) - if d: - self.doctype = 'Web Page' - self.docname = d.name - self.meta = frappe.get_meta(self.doctype) - return True - else: - return False - - def render(self): - self.doc = frappe.get_doc(self.doctype, self.docname) - self.init_context() - self.update_context() - self.post_process_context() - - html = frappe.get_template(self.context.template_path).render(self.context) - html = self.add_csrf_token(html) - - return build_response(self.path, html, self.http_status_code or 200, self.headers) - - def update_context(self): - self.context.doc = self.doc - self.context.update(self.context.doc.as_dict()) - self.context.update(self.context.doc.get_website_properties()) - - if not self.context.template_path: - self.context.template_path = self.context.doc.meta.get_web_template() - - if hasattr(self.doc, "get_context"): - ret = self.doc.get_context(self.context) - - if ret: - self.context.update(ret) - - for prop in ("no_cache", "sitemap"): - if not prop in self.context: - self.context[prop] = getattr(self.doc, prop, False) - - def get_condition_field(self, meta): - condition_field = None - if meta.is_published_field: - condition_field = meta.is_published_field - elif not meta.custom: - controller = get_controller(meta.doctype) - condition_field = controller.website.condition_field - - return condition_field - -class WebFormPage(WebPage): - pass From ce3771ab74ab739d18960c50e1dd3826f5ffb4b1 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 12 Mar 2021 13:26:38 +0530 Subject: [PATCH 010/164] refactor: Move code to appropriate file add_sidebar_and_breadcrumbs belongs to TemplatePage --- .../page_controllers/base_template_page.py | 12 +++--------- frappe/website/page_controllers/template_page.py | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/frappe/website/page_controllers/base_template_page.py b/frappe/website/page_controllers/base_template_page.py index 10e2375356..8a5ec992cc 100644 --- a/frappe/website/page_controllers/base_template_page.py +++ b/frappe/website/page_controllers/base_template_page.py @@ -1,6 +1,7 @@ +import os + import frappe from frappe import _ -from frappe.website.context import add_sidebar_and_breadcrumbs from frappe.website.doctype.website_settings.website_settings import \ get_website_settings from frappe.website.page_controllers.web_page import WebPage @@ -15,15 +16,12 @@ class BaseTemplatePage(WebPage): def add_csrf_token(self, html): if frappe.local.session: return html.replace("", ''.format( - frappe.local.session.data.csrf_token)) + frappe.local.session.data.csrf_token)) else: return html def post_process_context(self): - self.set_missing_values() self.add_metatags() - self.add_sidebar_and_breadcrumbs() - self.set_base_template_if_missing() self.set_title_with_prefix() self.update_website_context() @@ -40,7 +38,6 @@ class BaseTemplatePage(WebPage): if self.context.template: self.template_path = self.context.template - def set_base_template_if_missing(self): if not self.context.base_template_path: app_base = frappe.get_hooks("base_template") @@ -69,9 +66,6 @@ class BaseTemplatePage(WebPage): self.context.metatags = self.tags - def add_sidebar_and_breadcrumbs(self): - add_sidebar_and_breadcrumbs(self.context) - def init_metatags_from_context(self): for key in ('title', 'description', 'image', 'author', 'url', 'published_on'): if not key in self.tags and self.context.get(key): diff --git a/frappe/website/page_controllers/template_page.py b/frappe/website/page_controllers/template_page.py index 46e6e6a57e..cfb2d2f310 100644 --- a/frappe/website/page_controllers/template_page.py +++ b/frappe/website/page_controllers/template_page.py @@ -4,6 +4,7 @@ import os import frappe from frappe import _ from frappe.website.page_controllers.base_template_page import BaseTemplatePage +from frappe.website.context import add_sidebar_and_breadcrumbs from frappe.website.render import build_response from frappe.website.utils import (extract_comment_tag, extract_title, get_next_link, get_toc) @@ -60,6 +61,14 @@ class TemplatePage(BaseTemplatePage): return html + def post_process_context(self): + self.add_sidebar_and_breadcrumbs() + self.set_missing_values() + super(TemplatePage, self).post_process_context() + + def add_sidebar_and_breadcrumbs(self): + add_sidebar_and_breadcrumbs(self.context) + def set_pymodule(self): ''' A template may have a python module with a `get_context` method along with it in the @@ -85,7 +94,7 @@ class TemplatePage(BaseTemplatePage): self.convert_from_markdown() if self.extends_template(): - self.context.base_template_path = self.context.base_template_path or 'templates/base.html' + self.context.base_template_path = self.context.base_template_path # TODO: setup index.txt ? @@ -246,9 +255,11 @@ class TemplatePage(BaseTemplatePage): if self.context.url_prefix and self.context.url_prefix[-1]!='/': self.context.url_prefix += '/' + self.context.path = self.path + self.context.pathname = frappe.local.path + # for backward compatibility self.context.docs_base_url = '/docs' - self.context.path = self.path def get_start_folders(): From c6cc70e7dfa2dddcde55acae1ffc21c3ba169a45 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 15 Mar 2021 10:13:40 +0530 Subject: [PATCH 011/164] fix: Document Page --- frappe/website/page_controllers/document_page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/page_controllers/document_page.py b/frappe/website/page_controllers/document_page.py index bb00d78c0c..f13ea0c724 100644 --- a/frappe/website/page_controllers/document_page.py +++ b/frappe/website/page_controllers/document_page.py @@ -82,7 +82,7 @@ class DocumentPage(BaseTemplatePage): if meta.is_published_field: condition_field = meta.is_published_field elif not meta.custom: - controller = get_controller(meta.doctype) + controller = get_controller(meta.name) condition_field = controller.website.condition_field return condition_field From a5f274b5a3dbb72e109184b7a3c56b2a1dac13b4 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 15 Mar 2021 10:15:27 +0530 Subject: [PATCH 012/164] fix: Enable WebForm page --- frappe/website/serve.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/website/serve.py b/frappe/website/serve.py index 10eddfc641..ea27b07859 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -8,6 +8,7 @@ from frappe.website.page_controllers.not_permitted_page import NotPermittedPage from frappe.website.page_controllers.print_page import PrintPage from frappe.website.page_controllers.template_page import TemplatePage from frappe.website.page_controllers.static_page import StaticPage +from frappe.website.page_controllers.web_form import WebFormPage from frappe.website.redirect import resolve_redirect from frappe.website.render import build_response, resolve_path @@ -23,7 +24,6 @@ def get_response(path=None, http_status_code=200): path = path.strip('/ ') resolve_redirect(path, query_string) path = resolve_path(path) - data = None # there is no way to determine the type of the page based on the route # so evaluate each type of page sequentially @@ -32,6 +32,8 @@ def get_response(path=None, http_status_code=200): response = TemplatePage(path, http_status_code).get() if not response: response = ListPage(path, http_status_code).get() + if not response: + response = WebFormPage(path, http_status_code).get() if not response: response = DocumentPage(path, http_status_code).get() if not response: From 08fe794ad8b984a7914dc7b1e07618db69ec6efc Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 28 Apr 2021 12:19:47 +0530 Subject: [PATCH 013/164] refactor: Remove support for commented attributes - Use frontmatter instead --- .../website/page_controllers/template_page.py | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/frappe/website/page_controllers/template_page.py b/frappe/website/page_controllers/template_page.py index cfb2d2f310..f8bc82de30 100644 --- a/frappe/website/page_controllers/template_page.py +++ b/frappe/website/page_controllers/template_page.py @@ -148,30 +148,6 @@ class TemplatePage(BaseTemplatePage): self.source = '''{{% extends "{0}" %}} {{% block page_content %}}{1}{{% endblock %}}'''.format(context.base_template, self.source) - if "" in self.source: - context.no_breadcrumbs = 1 - - if "" in self.source: - context.show_sidebar = 1 - - if "" in self.source: - context.add_breadcrumbs = 1 - - if "" in self.source: - context.no_header = 1 - - if "" in self.source: - context.add_next_prev_links = 1 - - if "" in self.source: - context.no_cache = 1 - - if "" in self.source: - context.sitemap = 0 - - if "" in self.source: - context.sitemap = 1 - def run_pymodule_method(self, method): if hasattr(self.pymodule, method): try: From 4895a8350d0a0c3c2c9eb3065225fe59ce857d3b Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 28 Apr 2021 12:20:51 +0530 Subject: [PATCH 014/164] refactor: Simplify code - Make code more readable --- frappe/website/page/__init__.py | 1 - frappe/website/page_controllers/base_template_page.py | 9 +++++---- frappe/website/page_controllers/static_page.py | 10 +++------- frappe/website/router.py | 6 ++++-- 4 files changed, 12 insertions(+), 14 deletions(-) delete mode 100644 frappe/website/page/__init__.py diff --git a/frappe/website/page/__init__.py b/frappe/website/page/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/frappe/website/page/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/frappe/website/page_controllers/base_template_page.py b/frappe/website/page_controllers/base_template_page.py index 8a5ec992cc..0c5455bc81 100644 --- a/frappe/website/page_controllers/base_template_page.py +++ b/frappe/website/page_controllers/base_template_page.py @@ -15,10 +15,11 @@ class BaseTemplatePage(WebPage): def add_csrf_token(self, html): if frappe.local.session: - return html.replace("", ''.format( - frappe.local.session.data.csrf_token)) - else: - return html + csrf_token = frappe.local.session.data.csrf_token + return html.replace("", + f'') + + return html def post_process_context(self): self.add_metatags() diff --git a/frappe/website/page_controllers/static_page.py b/frappe/website/page_controllers/static_page.py index 4b57c3ec42..8f474e71be 100644 --- a/frappe/website/page_controllers/static_page.py +++ b/frappe/website/page_controllers/static_page.py @@ -12,8 +12,8 @@ class StaticPage(WebPage): def validate(self): if ('.' not in self.path): return False - extn = self.path.rsplit('.', 1)[-1] - if extn in ('html', 'md', 'js', 'xml', 'css', 'txt', 'py', 'json'): + extension = self.path.rsplit('.', 1)[-1] + if extension in ('html', 'md', 'js', 'xml', 'css', 'txt', 'py', 'json'): return False if self.find_path_in_apps(): @@ -30,11 +30,7 @@ class StaticPage(WebPage): return False def render(self): - try: - f = open(self.file_path, 'rb') - except IOError: - raise frappe.exceptions.NotFound - + f = open(self.file_path, 'rb') response = Response(wrap_file(frappe.local.request.environ, f), direct_passthrough=True) response.mimetype = mimetypes.guess_type(self.file_path)[0] or 'application/octet-stream' return response diff --git a/frappe/website/router.py b/frappe/website/router.py index 4acb0c6b9b..13e2818e20 100644 --- a/frappe/website/router.py +++ b/frappe/website/router.py @@ -433,8 +433,10 @@ def get_doctypes_with_web_view(): def _get(): installed_apps = frappe.get_installed_apps() doctypes = frappe.get_hooks("website_generators") - doctypes += [d.name for d in frappe.get_all('DocType', 'name, module', - dict(has_web_view=1)) if frappe.local.module_app[frappe.scrub(d.module)] in installed_apps] + doctypes_with_web_view = frappe.get_all('DocType', fields=['name', 'module'], + filters=dict(has_web_view=1)) + module_app_map = frappe.local.module_app + doctypes += [d.name for d in doctypes_with_web_view if module_app_map[frappe.scrub(d.module)] in installed_apps] return doctypes return frappe.cache().get_value('doctypes_with_web_view', _get) From 34592aa0696eaf6eb7518e5309a413d4d755ddb9 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 28 Apr 2021 12:50:48 +0530 Subject: [PATCH 015/164] test: Use get_response instead of render in tests --- frappe/website/doctype/blog_post/test_blog_post.py | 10 +++++----- frappe/website/doctype/web_page/test_web_page.py | 4 ++-- .../website/doctype/web_template/test_web_template.py | 7 +++---- .../website_route_meta/test_website_route_meta.py | 4 ++-- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/frappe/website/doctype/blog_post/test_blog_post.py b/frappe/website/doctype/blog_post/test_blog_post.py index f0fc484a31..24083c76cd 100644 --- a/frappe/website/doctype/blog_post/test_blog_post.py +++ b/frappe/website/doctype/blog_post/test_blog_post.py @@ -7,7 +7,7 @@ from bs4 import BeautifulSoup import re from frappe.utils import set_request -from frappe.website.render import render +from frappe.website.serve import get_response from frappe.utils import random_string from frappe.website.doctype.blog_post.blog_post import get_blog_list from frappe.website.website_generator import WebsiteGenerator @@ -22,7 +22,7 @@ class TestBlogPost(unittest.TestCase): filters={'published': 1, 'route': ('!=', '')}, limit =1) set_request(path=pages[0].route) - response = render() + response = get_response() self.assertTrue(response.status_code, 200) @@ -36,7 +36,7 @@ class TestBlogPost(unittest.TestCase): frappe.db.set_value('Blog Post', pages[0].name, 'route', 'test-route-000') set_request(path='test-route-000') - response = render() + response = get_response() self.assertTrue(response.status_code, 404) @@ -46,7 +46,7 @@ class TestBlogPost(unittest.TestCase): # Visit the blog post page set_request(path=blog.route) - blog_page_response = render() + blog_page_response = get_response() blog_page_html = frappe.safe_decode(blog_page_response.get_data()) # On blog post page find link to the category page @@ -56,7 +56,7 @@ class TestBlogPost(unittest.TestCase): # Visit the category page (by following the link found in above stage) set_request(path=category_page_url) - category_page_response = render() + category_page_response = get_response() category_page_html = frappe.safe_decode(category_page_response.get_data()) # Category page should contain the blog post title diff --git a/frappe/website/doctype/web_page/test_web_page.py b/frappe/website/doctype/web_page/test_web_page.py index a481337978..2a175f6f77 100644 --- a/frappe/website/doctype/web_page/test_web_page.py +++ b/frappe/website/doctype/web_page/test_web_page.py @@ -2,14 +2,14 @@ from __future__ import unicode_literals import unittest import frappe from frappe.website.router import resolve_route -import frappe.website.render +from frappe.website.serve import get_response from frappe.utils import set_request test_records = frappe.get_test_records('Web Page') def get_page_content(route): set_request(method='GET', path = route) - response = frappe.website.render.render() + response = get_response() return frappe.as_unicode(response.data) class TestWebPage(unittest.TestCase): diff --git a/frappe/website/doctype/web_template/test_web_template.py b/frappe/website/doctype/web_template/test_web_template.py index b4ea9e2f97..ed2ed70c14 100644 --- a/frappe/website/doctype/web_template/test_web_template.py +++ b/frappe/website/doctype/web_template/test_web_template.py @@ -7,8 +7,7 @@ import frappe import unittest from bs4 import BeautifulSoup from frappe.utils import set_request -from frappe.website.render import render - +from frappe.website.serve import get_response class TestWebTemplate(unittest.TestCase): def test_render_web_template_with_values(self): @@ -36,7 +35,7 @@ class TestWebTemplate(unittest.TestCase): self.create_web_page() set_request(method="GET", path="test-web-template") - response = render() + response = get_response() self.assertEqual(response.status_code, 200) @@ -58,7 +57,7 @@ class TestWebTemplate(unittest.TestCase): frappe.conf.developer_mode = 1 set_request(method="GET", path="test-web-template") - response = render() + response = get_response() self.assertEqual(response.status_code, 200) html = frappe.safe_decode(response.get_data()) diff --git a/frappe/website/doctype/website_route_meta/test_website_route_meta.py b/frappe/website/doctype/website_route_meta/test_website_route_meta.py index 0ccedb0ca4..d385a6e59b 100644 --- a/frappe/website/doctype/website_route_meta/test_website_route_meta.py +++ b/frappe/website/doctype/website_route_meta/test_website_route_meta.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe import unittest from frappe.utils import set_request -from frappe.website.render import render +from frappe.website.serve import get_response test_dependencies = ['Blog Post'] class TestWebsiteRouteMeta(unittest.TestCase): @@ -31,7 +31,7 @@ class TestWebsiteRouteMeta(unittest.TestCase): # set request on this route set_request(path=blog.route) - response = render() + response = get_response() self.assertTrue(response.status_code, 200) From e18a43e11d27b2c4a3b7f80746da026eb2c7ef97 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 28 Apr 2021 12:51:36 +0530 Subject: [PATCH 016/164] fix: Replace render() with get_response --- frappe/app.py | 6 +++--- frappe/utils/response.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/app.py b/frappe/app.py index 1695c1f735..8e74595c15 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -20,6 +20,7 @@ import frappe.api import frappe.utils.response from frappe.utils import get_site_name, sanitize_html from frappe.middlewares import StaticDataMiddleware +from frappe.website.serve import get_response from frappe.utils.error import make_error_snapshot from frappe.core.doctype.comment.comment import update_comments_in_parent_after_request from frappe import _ @@ -73,7 +74,7 @@ def application(request): response = frappe.utils.response.download_private_file(request.path) elif request.method in ('GET', 'HEAD', 'POST'): - response = frappe.website.render.render() + response = get_response() else: raise NotFound @@ -255,8 +256,7 @@ def handle_exception(e): make_error_snapshot(e) if return_as_message: - response = frappe.website.render.render("message", - http_status_code=http_status_code) + response = get_response("message", http_status_code=http_status_code) return response diff --git a/frappe/utils/response.py b/frappe/utils/response.py index b152d69d8d..44b037f3cc 100644 --- a/frappe/utils/response.py +++ b/frappe/utils/response.py @@ -149,8 +149,8 @@ def json_handler(obj): def as_page(): """print web page""" - from frappe.website.render import render - return render(frappe.response['route'], http_status_code=frappe.response.get("http_status_code")) + from frappe.website.serve import get_response + return get_response(frappe.response['route'], http_status_code=frappe.response.get("http_status_code")) def redirect(): return werkzeug.utils.redirect(frappe.response.location) From 425b72af591d73de68aaca43da5489c0dc940aac Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 28 Apr 2021 13:06:51 +0530 Subject: [PATCH 017/164] test: Fix Blog Post test case --- frappe/website/doctype/blog_post/test_blog_post.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frappe/website/doctype/blog_post/test_blog_post.py b/frappe/website/doctype/blog_post/test_blog_post.py index 24083c76cd..5d9802cd9e 100644 --- a/frappe/website/doctype/blog_post/test_blog_post.py +++ b/frappe/website/doctype/blog_post/test_blog_post.py @@ -33,16 +33,18 @@ class TestBlogPost(unittest.TestCase): pages = frappe.get_all('Blog Post', fields=['name', 'route'], filters={'published': 0}, limit =1) - frappe.db.set_value('Blog Post', pages[0].name, 'route', 'test-route-000') + route = f'test-route-{frappe.generate_hash(length=5)}' - set_request(path='test-route-000') + frappe.db.set_value('Blog Post', pages[0].name, 'route', route) + + set_request(path=route) response = get_response() self.assertTrue(response.status_code, 404) def test_category_link(self): # Make a temporary Blog Post (and a Blog Category) - blog = make_test_blog() + blog = make_test_blog('Test Category Link') # Visit the blog post page set_request(path=blog.route) @@ -62,7 +64,7 @@ class TestBlogPost(unittest.TestCase): # Category page should contain the blog post title self.assertIn(blog.title, category_page_html) - # Cleanup afterwords + # Cleanup frappe.delete_doc("Blog Post", blog.name) frappe.delete_doc("Blog Category", blog.blog_category) From 3aa990c6d97dbe7a5c4d8dd222059fd4de539f5c Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 28 Apr 2021 13:07:17 +0530 Subject: [PATCH 018/164] fix: Add validator for WebForm --- frappe/website/page_controllers/web_form.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/website/page_controllers/web_form.py b/frappe/website/page_controllers/web_form.py index 030e8bb3bc..d7ab02d880 100644 --- a/frappe/website/page_controllers/web_form.py +++ b/frappe/website/page_controllers/web_form.py @@ -1,4 +1,6 @@ from frappe.website.page_controllers.web_page import WebPage +import frappe class WebFormPage(WebPage): - pass + def validate(self): + return bool(frappe.get_all("Web Form", filters={'route': self.path})) From d7fcc60ac7c50c1ee08f2a8a0bab8635bdceb46a Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 28 Apr 2021 13:21:46 +0530 Subject: [PATCH 019/164] fix: set_metatags_from_website_route_meta --- frappe/website/page_controllers/base_template_page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/page_controllers/base_template_page.py b/frappe/website/page_controllers/base_template_page.py index 0c5455bc81..9af1bcb3e5 100644 --- a/frappe/website/page_controllers/base_template_page.py +++ b/frappe/website/page_controllers/base_template_page.py @@ -107,7 +107,7 @@ class BaseTemplatePage(WebPage): Get meta tags from Website Route meta they can override the defaults set above ''' - route = self.context.path + route = self.path if route == '': # homepage route = frappe.db.get_single_value('Website Settings', 'home_page') From 9f1f3ae3fc7b72dd594df8a68aaea060f881fc7f Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 28 Apr 2021 13:37:48 +0530 Subject: [PATCH 020/164] style: Fix code correctness issues --- frappe/website/doctype/web_page/web_page.py | 1 - .../website/page_controllers/base_template_page.py | 8 +++----- frappe/website/page_controllers/document_page.py | 6 +++--- frappe/website/page_controllers/template_page.py | 14 +++++--------- frappe/website/serve.py | 1 - 5 files changed, 11 insertions(+), 19 deletions(-) diff --git a/frappe/website/doctype/web_page/web_page.py b/frappe/website/doctype/web_page/web_page.py index cce00564ff..d3c7d19b9a 100644 --- a/frappe/website/doctype/web_page/web_page.py +++ b/frappe/website/doctype/web_page/web_page.py @@ -10,7 +10,6 @@ import requests.exceptions from jinja2.exceptions import TemplateSyntaxError import frappe -from frappe import _ from frappe.utils import get_datetime, now, strip_html, quoted from frappe.utils.jinja import render_template from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow diff --git a/frappe/website/page_controllers/base_template_page.py b/frappe/website/page_controllers/base_template_page.py index 9af1bcb3e5..74ca400372 100644 --- a/frappe/website/page_controllers/base_template_page.py +++ b/frappe/website/page_controllers/base_template_page.py @@ -1,7 +1,4 @@ -import os - import frappe -from frappe import _ from frappe.website.doctype.website_settings.website_settings import \ get_website_settings from frappe.website.page_controllers.web_page import WebPage @@ -69,10 +66,11 @@ class BaseTemplatePage(WebPage): def init_metatags_from_context(self): for key in ('title', 'description', 'image', 'author', 'url', 'published_on'): - if not key in self.tags and self.context.get(key): + if key not in self.tags and self.context.get(key): self.tags[key] = self.context[key] - if not self.tags.get('title'): self.tags['title'] = self.context.get('name') + if not self.tags.get('title'): + self.tags['title'] = self.context.get('name') if self.tags.get('image'): self.tags['image'] = frappe.utils.get_url(self.tags['image']) diff --git a/frappe/website/page_controllers/document_page.py b/frappe/website/page_controllers/document_page.py index f13ea0c724..537b8ac658 100644 --- a/frappe/website/page_controllers/document_page.py +++ b/frappe/website/page_controllers/document_page.py @@ -1,5 +1,4 @@ import frappe -from frappe import _ from frappe.model.document import get_controller from frappe.website.page_controllers.base_template_page import BaseTemplatePage from frappe.website.render import build_response @@ -36,7 +35,8 @@ class DocumentPage(BaseTemplatePage): self.meta = meta return True except Exception as e: - if not frappe.db.is_missing_column(e): raise e + if not frappe.db.is_missing_column(e): + raise e def search_web_page_dynamic_routes(self): d = get_page_info_from_web_page_with_dynamic_routes(self.path) @@ -74,7 +74,7 @@ class DocumentPage(BaseTemplatePage): self.context.update(ret) for prop in ("no_cache", "sitemap"): - if not prop in self.context: + if prop not in self.context: self.context[prop] = getattr(self.doc, prop, False) def get_condition_field(self, meta): diff --git a/frappe/website/page_controllers/template_page.py b/frappe/website/page_controllers/template_page.py index f8bc82de30..7d1f94bcc5 100644 --- a/frappe/website/page_controllers/template_page.py +++ b/frappe/website/page_controllers/template_page.py @@ -2,7 +2,6 @@ import io import os import frappe -from frappe import _ from frappe.website.page_controllers.base_template_page import BaseTemplatePage from frappe.website.context import add_sidebar_and_breadcrumbs from frappe.website.render import build_response @@ -95,8 +94,6 @@ class TemplatePage(BaseTemplatePage): if self.extends_template(): self.context.base_template_path = self.context.base_template_path - - # TODO: setup index.txt ? def update_context(self): @@ -110,10 +107,9 @@ class TemplatePage(BaseTemplatePage): self.set_pymodule_properties() data = self.run_pymodule_method('get_context') - # some methods may return a "context" object - if data: self.context.update(data) - + if data: + self.context.update(data) # TODO: self.context.children = self.run_pymodule_method('get_children') self.context.developer_mode = frappe.conf.developer_mode @@ -128,7 +124,7 @@ class TemplatePage(BaseTemplatePage): def set_page_properties(self): self.context.template = self.template_path - self.context.base_template = self.context.base_template or 'templates/web.html' + self.context.base_template = self.context.base_template or 'templates/web.html' def set_properties_from_source(self): if not self.source: @@ -154,7 +150,7 @@ class TemplatePage(BaseTemplatePage): return getattr(self.pymodule, method)(self.context) except (frappe.PermissionError, frappe.DoesNotExistError, frappe.Redirect): raise - except: + except Exception: if not frappe.flags.in_migrate: frappe.errprint(frappe.utils.get_traceback()) @@ -225,7 +221,7 @@ class TemplatePage(BaseTemplatePage): self.template_path = 'www/{path}.html'.format(path=path) def set_missing_values(self): - if not "url_prefix" in self.context: + if "url_prefix" not in self.context: self.context.url_prefix = "" if self.context.url_prefix and self.context.url_prefix[-1]!='/': diff --git a/frappe/website/serve.py b/frappe/website/serve.py index ea27b07859..1a5852d036 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -1,5 +1,4 @@ import frappe -from frappe import _ from frappe.utils import cstr from frappe.website.page_controllers.document_page import DocumentPage From c2bbc99c6d478432c25fcfb23b46b119d41df58c Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 28 Apr 2021 14:06:47 +0530 Subject: [PATCH 021/164] fix: Sitemap base template route --- frappe/www/sitemap.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/www/sitemap.py b/frappe/www/sitemap.py index f8f03c45f8..9d7d8632a3 100644 --- a/frappe/www/sitemap.py +++ b/frappe/www/sitemap.py @@ -6,12 +6,12 @@ from __future__ import unicode_literals import frappe from frappe.model.document import get_controller from frappe.utils import get_datetime, nowdate, get_url -from frappe.website.router import get_pages, get_all_page_context_from_doctypes +from frappe.website.router import get_pages from six import iteritems -from six.moves.urllib.parse import quote, urljoin +from six.moves.urllib.parse import quote no_cache = 1 -base_template_path = "templates/www/sitemap.xml" +base_template_path = "www/sitemap.xml" def get_context(context): """generate the sitemap XML""" From 87fa672250dee7333dcd390bcd3ec2a26c614ca9 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 28 Apr 2021 15:03:14 +0530 Subject: [PATCH 022/164] style: Resolve code correctness warning --- frappe/app.py | 2 +- frappe/sessions.py | 2 +- frappe/website/page_controllers/web_page.py | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frappe/app.py b/frappe/app.py index 8e74595c15..387566627e 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -185,7 +185,7 @@ def make_form_dict(request): args = request.form or request.args if not isinstance(args, dict): - frappe.throw("Invalid request arguments") + frappe.throw(_("Invalid request arguments")) try: frappe.local.form_dict = frappe._dict({ k:v[0] if isinstance(v, (list, tuple)) else v \ diff --git a/frappe/sessions.py b/frappe/sessions.py index d949a3bed9..900f45627f 100644 --- a/frappe/sessions.py +++ b/frappe/sessions.py @@ -313,7 +313,7 @@ class Session: """, (self.sid, get_expiry_period_for_query(self.device))) if rec: - data = frappe._dict(eval(rec and rec[0][1] or '{}')) + data = frappe._dict(frappe.safe_eval(rec and rec[0][1] or '{}')) data.user = rec[0][0] else: self._delete_session() diff --git a/frappe/website/page_controllers/web_page.py b/frappe/website/page_controllers/web_page.py index d4af2860f8..2020d491e4 100644 --- a/frappe/website/page_controllers/web_page.py +++ b/frappe/website/page_controllers/web_page.py @@ -1,5 +1,4 @@ import frappe -from frappe import _ class WebPage(object): def __init__(self, path=None, http_status_code=None): From d961bda492750be285af6e07c49a22fb96a0b190 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 29 Apr 2021 12:43:58 +0530 Subject: [PATCH 023/164] fix: Base template path --- frappe/utils/print_format.py | 2 +- frappe/www/app.py | 1 - frappe/www/printview.py | 1 - frappe/www/robots.py | 3 +-- frappe/www/rss.py | 2 +- frappe/www/website_script.py | 2 +- 6 files changed, 4 insertions(+), 7 deletions(-) diff --git a/frappe/utils/print_format.py b/frappe/utils/print_format.py index e83a5f6c71..6537cc45d0 100644 --- a/frappe/utils/print_format.py +++ b/frappe/utils/print_format.py @@ -9,7 +9,7 @@ from PyPDF2 import PdfFileWriter no_cache = 1 -base_template_path = "templates/www/printview.html" +base_template_path = "www/printview.html" standard_format = "templates/print_formats/standard.html" @frappe.whitelist() diff --git a/frappe/www/app.py b/frappe/www/app.py index 6088c413dc..7cf4c1746e 100644 --- a/frappe/www/app.py +++ b/frappe/www/app.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals, print_function no_cache = 1 -base_template_path = "templates/www/app.html" import os, re import frappe diff --git a/frappe/www/printview.py b/frappe/www/printview.py index 04e846c41d..0b79119c7d 100644 --- a/frappe/www/printview.py +++ b/frappe/www/printview.py @@ -13,7 +13,6 @@ from six import string_types no_cache = 1 -base_template_path = "templates/www/printview.html" standard_format = "templates/print_formats/standard.html" def get_context(context): diff --git a/frappe/www/robots.py b/frappe/www/robots.py index 6a7c74cf41..bba869ef84 100644 --- a/frappe/www/robots.py +++ b/frappe/www/robots.py @@ -1,8 +1,7 @@ from __future__ import unicode_literals import frappe -base_template_path = "templates/www/robots.txt" - +base_template_path = "www/robots.txt" def get_context(context): robots_txt = ( frappe.db.get_single_value('Website Settings', 'robots_txt') or diff --git a/frappe/www/rss.py b/frappe/www/rss.py index a389304e5e..4e740424a4 100644 --- a/frappe/www/rss.py +++ b/frappe/www/rss.py @@ -7,7 +7,7 @@ from frappe.utils import escape_html, get_request_site_address, now, cstr from six.moves.urllib.parse import quote, urljoin no_cache = 1 -base_template_path = "templates/www/rss.xml" +base_template_path = "www/rss.xml" def get_context(context): """generate rss feed""" diff --git a/frappe/www/website_script.py b/frappe/www/website_script.py index 0bb5a8a80b..7401dac31b 100644 --- a/frappe/www/website_script.py +++ b/frappe/www/website_script.py @@ -6,7 +6,7 @@ import frappe from frappe.utils import strip from frappe.website.doctype.website_theme.website_theme import get_active_theme -base_template_path = "templates/www/website_script.js" +base_template_path = "www/website_script.js" def get_context(context): context.javascript = frappe.db.get_single_value('Website Script', From 0838ea40cf86153fd9c3511aa2b32f87912207fe Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 29 Apr 2021 12:44:37 +0530 Subject: [PATCH 024/164] refactor: Remove unnecessary code --- frappe/website/page_controllers/template_page.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/frappe/website/page_controllers/template_page.py b/frappe/website/page_controllers/template_page.py index 7d1f94bcc5..b050a62bad 100644 --- a/frappe/website/page_controllers/template_page.py +++ b/frappe/website/page_controllers/template_page.py @@ -86,16 +86,9 @@ class TemplatePage(BaseTemplatePage): def setup_template(self): '''Setup template source, frontmatter and markdown conversion''' self.source = self.get_raw_template() - - if self.template_path.endswith(('.md', '.html')): - self.extract_frontmatter() - + self.extract_frontmatter() self.convert_from_markdown() - if self.extends_template(): - self.context.base_template_path = self.context.base_template_path - # TODO: setup index.txt ? - def update_context(self): self.set_page_properties() self.set_properties_from_source() @@ -166,7 +159,7 @@ class TemplatePage(BaseTemplatePage): return html def extends_template(self): - return (self.template_path.endswith(('.html', '.md', )) + return (self.template_path.endswith(('.html', '.md')) and ('{%- extends' in self.source or '{% extends' in self.source)) @@ -188,6 +181,9 @@ class TemplatePage(BaseTemplatePage): return f.read() def extract_frontmatter(self): + if not self.template_path.endswith(('.md', '.html')): + return + try: # values will be used to update self res = get_frontmatter(self.source) From e101105bce30b7eae0a4f3371566ba4e85e0606a Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 11 May 2021 10:41:18 +0530 Subject: [PATCH 025/164] refactor: Import get_response instead of using serve.get_response --- frappe/tests/test_website.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/frappe/tests/test_website.py b/frappe/tests/test_website.py index 97e00b9268..5221cbe6b7 100644 --- a/frappe/tests/test_website.py +++ b/frappe/tests/test_website.py @@ -3,8 +3,7 @@ from __future__ import unicode_literals import unittest import frappe -from frappe.website import render -from frappe.website import serve +from frappe.website.serve import get_response from frappe.website.utils import get_home_page from frappe.utils import set_request @@ -51,7 +50,7 @@ class TestWebsite(unittest.TestCase): def test_page_load(self): set_request(method='POST', path='login') - response = serve.get_response() + response = get_response() self.assertEqual(response.status_code, 200) @@ -62,17 +61,17 @@ class TestWebsite(unittest.TestCase): def test_static_page(self): set_request(method='GET', path='/_test/static-file-test.png') - response = serve.get_response() + response = get_response() self.assertEqual(response.status_code, 200) def test_error_page(self): set_request(method='GET', path='/error') - response = serve.get_response() + response = get_response() self.assertEqual(response.status_code, 500) def test_login(self): set_request(method='GET', path='/login') - response = serve.get_response() + response = get_response() self.assertEqual(response.status_code, 200) html = frappe.safe_decode(response.get_data()) @@ -83,7 +82,7 @@ class TestWebsite(unittest.TestCase): def test_app(self): frappe.set_user('Administrator') set_request(method='GET', path='/app') - response = serve.get_response() + response = get_response() self.assertEqual(response.status_code, 200) html = frappe.safe_decode(response.get_data()) @@ -92,7 +91,7 @@ class TestWebsite(unittest.TestCase): def test_not_found(self): set_request(method='GET', path='/_test/missing') - response = serve.get_response() + response = get_response() self.assertEqual(response.status_code, 404) @@ -118,31 +117,31 @@ class TestWebsite(unittest.TestCase): frappe.cache().delete_key('website_redirects') set_request(method='GET', path='/testfrom') - response = serve.get_response() + response = get_response() self.assertEqual(response.status_code, 301) self.assertEqual(response.headers.get('Location'), r'://testto1') set_request(method='GET', path='/testfromregex/test') - response = serve.get_response() + response = get_response() self.assertEqual(response.status_code, 301) self.assertEqual(response.headers.get('Location'), r'://testto2') set_request(method='GET', path='/testsub/me') - response = serve.get_response() + response = get_response() self.assertEqual(response.status_code, 301) self.assertEqual(response.headers.get('Location'), r'://testto3/me') set_request(method='GET', path='/test404') - response = serve.get_response() + response = get_response() self.assertEqual(response.status_code, 404) set_request(method='GET', path='/testsource') - response = serve.get_response() + response = get_response() self.assertEqual(response.status_code, 301) self.assertEqual(response.headers.get('Location'), '/testtarget') set_request(method='GET', path='/courses/course?course=data') - response = serve.get_response() + response = get_response() self.assertEqual(response.status_code, 301) self.assertEqual(response.headers.get('Location'), '/courses/data') From 5c92e8a12962a4c8acd9037c81ae81433357b56f Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 11 May 2021 16:43:22 +0530 Subject: [PATCH 026/164] feat: Add class for standard pages (Error & NotFound) --- frappe/website/page_controllers/error_page.py | 9 +++++++++ frappe/website/page_controllers/not_found_page.py | 9 +++++++++ frappe/website/page_controllers/not_permitted_page.py | 4 ++++ 3 files changed, 22 insertions(+) create mode 100644 frappe/website/page_controllers/error_page.py create mode 100644 frappe/website/page_controllers/not_found_page.py diff --git a/frappe/website/page_controllers/error_page.py b/frappe/website/page_controllers/error_page.py new file mode 100644 index 0000000000..caa72b015d --- /dev/null +++ b/frappe/website/page_controllers/error_page.py @@ -0,0 +1,9 @@ +from frappe import _ +from frappe.website.page_controllers.not_found_page import NotFoundPage +from frappe.website.page_controllers.template_page import TemplatePage + +class ErrorPage(TemplatePage): + def __init__(self, path, http_status_code, exception): + path = 'error' + super().__init__(path=path, http_status_code=http_status_code) + self.http_status_code = getattr(exception, 'http_status_code', None) or http_status_code or 500 diff --git a/frappe/website/page_controllers/not_found_page.py b/frappe/website/page_controllers/not_found_page.py new file mode 100644 index 0000000000..d1a1797efc --- /dev/null +++ b/frappe/website/page_controllers/not_found_page.py @@ -0,0 +1,9 @@ +from frappe import _ +from frappe.website.page_controllers.template_page import TemplatePage + +class NotFoundPage(TemplatePage): + def __init__(self, path, http_status_code): + super().__init__(path=path, http_status_code=http_status_code) + self.path = '404' + self.http_status_code = 404 + self.template_path = '404' diff --git a/frappe/website/page_controllers/not_permitted_page.py b/frappe/website/page_controllers/not_permitted_page.py index e0cd6bf970..61c6b88e09 100644 --- a/frappe/website/page_controllers/not_permitted_page.py +++ b/frappe/website/page_controllers/not_permitted_page.py @@ -3,6 +3,10 @@ from frappe import _ from frappe.website.page_controllers.template_page import TemplatePage class NotPermittedPage(TemplatePage): + def __init__(self, path, http_status_code): + super().__init__(path=path, http_status_code=http_status_code) + self.http_status_code = 403 + def validate(self): frappe.local.message_title = _("Not Permitted") frappe.local.response['context'] = dict( From 9429d06a17f6cc51716ff40e51b36150d860568f Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 11 May 2021 16:44:50 +0530 Subject: [PATCH 027/164] refactor: Remove reduntant code - Use Error & NotFound page as required --- frappe/tests/test_website.py | 2 +- frappe/website/serve.py | 22 ++++++++-------------- frappe/www/_test/problematic_page.html | 1 + 3 files changed, 10 insertions(+), 15 deletions(-) create mode 100644 frappe/www/_test/problematic_page.html diff --git a/frappe/tests/test_website.py b/frappe/tests/test_website.py index 5221cbe6b7..cc500d1c02 100644 --- a/frappe/tests/test_website.py +++ b/frappe/tests/test_website.py @@ -65,7 +65,7 @@ class TestWebsite(unittest.TestCase): self.assertEqual(response.status_code, 200) def test_error_page(self): - set_request(method='GET', path='/error') + set_request(method='GET', path='/_test/problematic_page') response = get_response() self.assertEqual(response.status_code, 500) diff --git a/frappe/website/serve.py b/frappe/website/serve.py index 1a5852d036..b6586dc1f3 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -2,7 +2,9 @@ import frappe from frappe.utils import cstr from frappe.website.page_controllers.document_page import DocumentPage +from frappe.website.page_controllers.error_page import ErrorPage from frappe.website.page_controllers.list_page import ListPage +from frappe.website.page_controllers.not_found_page import NotFoundPage from frappe.website.page_controllers.not_permitted_page import NotPermittedPage from frappe.website.page_controllers.print_page import PrintPage from frappe.website.page_controllers.template_page import TemplatePage @@ -26,19 +28,11 @@ def get_response(path=None, http_status_code=200): # there is no way to determine the type of the page based on the route # so evaluate each type of page sequentially - response = StaticPage(path, http_status_code).get() - if not response: - response = TemplatePage(path, http_status_code).get() - if not response: - response = ListPage(path, http_status_code).get() - if not response: - response = WebFormPage(path, http_status_code).get() - if not response: - response = DocumentPage(path, http_status_code).get() - if not response: - response = PrintPage(path, http_status_code).get() - if not response: - response = TemplatePage('404', 404).get() + renderers = [StaticPage, TemplatePage, ListPage, WebFormPage, DocumentPage, PrintPage, NotFoundPage] + for resolver in renderers: + response = resolver(path, http_status_code).get() + if response: + break except frappe.Redirect: return build_response(path, "", 301, { "Location": frappe.flags.redirect_location or (frappe.local.response or {}).get('location'), @@ -48,6 +42,6 @@ def get_response(path=None, http_status_code=200): frappe.local.message = cstr(e) response = NotPermittedPage(path, http_status_code).get() except Exception as e: - response = TemplatePage('error', getattr(e, 'http_status_code', None) or http_status_code).get() + response = ErrorPage(path, http_status_code, exception=e).get() return response diff --git a/frappe/www/_test/problematic_page.html b/frappe/www/_test/problematic_page.html new file mode 100644 index 0000000000..5e194421d2 --- /dev/null +++ b/frappe/www/_test/problematic_page.html @@ -0,0 +1 @@ +{% raise %} From 006f0a2bafb9945c8fdf6fcb41c015e45b5967a4 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 11 May 2021 17:03:00 +0530 Subject: [PATCH 028/164] fix: Rename resolver to renderer --- frappe/website/page_controllers/error_page.py | 2 -- frappe/website/page_controllers/not_found_page.py | 1 - frappe/website/serve.py | 7 ++++--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/frappe/website/page_controllers/error_page.py b/frappe/website/page_controllers/error_page.py index caa72b015d..1f54a5f38c 100644 --- a/frappe/website/page_controllers/error_page.py +++ b/frappe/website/page_controllers/error_page.py @@ -1,5 +1,3 @@ -from frappe import _ -from frappe.website.page_controllers.not_found_page import NotFoundPage from frappe.website.page_controllers.template_page import TemplatePage class ErrorPage(TemplatePage): diff --git a/frappe/website/page_controllers/not_found_page.py b/frappe/website/page_controllers/not_found_page.py index d1a1797efc..1d15877e9d 100644 --- a/frappe/website/page_controllers/not_found_page.py +++ b/frappe/website/page_controllers/not_found_page.py @@ -1,4 +1,3 @@ -from frappe import _ from frappe.website.page_controllers.template_page import TemplatePage class NotFoundPage(TemplatePage): diff --git a/frappe/website/serve.py b/frappe/website/serve.py index b6586dc1f3..eacaef96e1 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -17,6 +17,8 @@ from frappe.website.render import build_response, resolve_path def get_response(path=None, http_status_code=200): """render html page""" query_string = None + response = None + if not path: path = frappe.local.request.path query_string = frappe.local.request.query_string @@ -25,12 +27,11 @@ def get_response(path=None, http_status_code=200): path = path.strip('/ ') resolve_redirect(path, query_string) path = resolve_path(path) - # there is no way to determine the type of the page based on the route # so evaluate each type of page sequentially renderers = [StaticPage, TemplatePage, ListPage, WebFormPage, DocumentPage, PrintPage, NotFoundPage] - for resolver in renderers: - response = resolver(path, http_status_code).get() + for renderer in renderers: + response = renderer(path, http_status_code).get() if response: break except frappe.Redirect: From 4754ab71d1cf3099b66b63bf77a770a16c64736f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 9 May 2021 19:44:58 +0530 Subject: [PATCH 029/164] perf(minor): remove unnecessary comprehensions - remove several unnecessary comprehensions from functions that accept a generator. - Using `[x for x in iter]` causes a list to be built first then passed to the outer function. - `any` and `all` can take generator instead. This makes memory usage O(1) and actually makes these functions short-circuiting. E.g. if the first condition fails then `all` will immediately return false instead of evaluating all the entries. - `sum`, `min`, `max` => memory usage become O(1) - `list`, `set`, `.join()` => roughly halves memory usage, as list is not required to be built. - lastly, it's two fewer characters to read/think about. --- frappe/automation/doctype/auto_repeat/auto_repeat.py | 2 +- frappe/contacts/address_and_contact.py | 2 +- frappe/contacts/doctype/address/address.py | 2 +- frappe/core/doctype/data_import/importer.py | 12 ++++++------ frappe/core/doctype/data_import_legacy/importer.py | 4 ++-- frappe/core/doctype/doctype/doctype.py | 6 +++--- frappe/core/doctype/domain/domain.py | 4 ++-- frappe/core/doctype/user/user.py | 2 +- frappe/core/doctype/user_type/user_type.py | 2 +- .../custom/doctype/customize_form/customize_form.py | 2 +- frappe/database/database.py | 4 ++-- .../global_search_settings/global_search_settings.py | 4 ++-- frappe/desk/doctype/tag/tag.py | 2 +- frappe/email/doctype/email_account/email_account.py | 6 +++--- frappe/email/doctype/newsletter/test_newsletter.py | 2 +- frappe/email/inbox.py | 2 +- frappe/installer.py | 2 +- .../doctype/ldap_settings/ldap_settings.py | 2 +- frappe/model/__init__.py | 4 ++-- frappe/model/base_document.py | 4 ++-- frappe/model/db_query.py | 10 +++++----- frappe/model/meta.py | 2 +- frappe/model/rename_doc.py | 2 +- frappe/permissions.py | 2 +- frappe/tests/test_commands.py | 8 ++++---- frappe/translate.py | 4 ++-- frappe/utils/__init__.py | 4 ++-- frappe/utils/backups.py | 6 +++--- frappe/utils/bot.py | 8 ++++---- frappe/utils/jinja_globals.py | 4 ++-- frappe/website/doctype/web_form/web_form.py | 2 +- .../doctype/website_slideshow/website_slideshow.py | 2 +- 32 files changed, 62 insertions(+), 62 deletions(-) diff --git a/frappe/automation/doctype/auto_repeat/auto_repeat.py b/frappe/automation/doctype/auto_repeat/auto_repeat.py index bf05baf5b6..37285ad571 100644 --- a/frappe/automation/doctype/auto_repeat/auto_repeat.py +++ b/frappe/automation/doctype/auto_repeat/auto_repeat.py @@ -334,7 +334,7 @@ class AutoRepeat(Document): if self.reference_doctype and self.reference_document: res = get_contacts_linking_to(self.reference_doctype, self.reference_document, fields=['email_id']) res += get_contacts_linked_from(self.reference_doctype, self.reference_document, fields=['email_id']) - email_ids = list(set([d.email_id for d in res])) + email_ids = set(d.email_id for d in res) if not email_ids: frappe.msgprint(_('No contacts linked to document'), alert=True) else: diff --git a/frappe/contacts/address_and_contact.py b/frappe/contacts/address_and_contact.py index 3ca9547188..43aa39f678 100644 --- a/frappe/contacts/address_and_contact.py +++ b/frappe/contacts/address_and_contact.py @@ -154,7 +154,7 @@ def filter_dynamic_link_doctypes(doctype, txt, searchfield, start, page_len, fil doctypes = frappe.db.get_all("DocField", filters=filters, fields=["parent"], distinct=True, as_list=True) - doctypes = tuple([d for d in doctypes if re.search(txt+".*", _(d[0]), re.IGNORECASE)]) + doctypes = tuple(d for d in doctypes if re.search(txt+".*", _(d[0]), re.IGNORECASE)) filters.update({ "dt": ("not in", [d[0] for d in doctypes]) diff --git a/frappe/contacts/doctype/address/address.py b/frappe/contacts/doctype/address/address.py index 84b925d50e..a6fd7b1755 100644 --- a/frappe/contacts/doctype/address/address.py +++ b/frappe/contacts/doctype/address/address.py @@ -263,7 +263,7 @@ def address_query(doctype, txt, searchfield, start, page_len, filters): def get_condensed_address(doc): fields = ["address_title", "address_line1", "address_line2", "city", "county", "state", "country"] - return ", ".join([doc.get(d) for d in fields if doc.get(d)]) + return ", ".join(doc.get(d) for d in fields if doc.get(d)) def update_preferred_address(address, field): frappe.db.set_value('Address', address, field, 0) diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py index 720fe1dda7..841319dd60 100644 --- a/frappe/core/doctype/data_import/importer.py +++ b/frappe/core/doctype/data_import/importer.py @@ -450,7 +450,7 @@ class ImportFile: for row in data_without_first_row: row_values = row.get_values(parent_column_indexes) # if the row is blank, it's a child row doc - if all([v in INVALID_VALUES for v in row_values]): + if all(v in INVALID_VALUES for v in row_values): rows.append(row) continue # if we encounter a row which has values in parent columns, @@ -607,7 +607,7 @@ class Row: if df.fieldtype == "Select": select_options = get_select_options(df) if select_options and value not in select_options: - options_string = ", ".join([frappe.bold(d) for d in select_options]) + options_string = ", ".join(frappe.bold(d) for d in select_options) msg = _("Value must be one of {0}").format(options_string) self.warnings.append( {"row": self.row_number, "field": df_as_json(df), "message": msg,} @@ -903,7 +903,7 @@ class Column: if self.df.fieldtype == "Link": # find all values that dont exist - values = list(set([cstr(v) for v in self.column_values[1:] if v])) + values = list(set(cstr(v) for v in self.column_values[1:] if v)) exists = [ d.name for d in frappe.db.get_all(self.df.options, filters={"name": ("in", values)}) ] @@ -939,11 +939,11 @@ class Column: elif self.df.fieldtype == "Select": options = get_select_options(self.df) if options: - values = list(set([cstr(v) for v in self.column_values[1:] if v])) + values = list(set(cstr(v) for v in self.column_values[1:] if v)) invalid = list(set(values) - set(options)) if invalid: - valid_values = ", ".join([frappe.bold(o) for o in options]) - invalid_values = ", ".join([frappe.bold(i) for i in invalid]) + valid_values = ", ".join(frappe.bold(o) for o in options) + invalid_values = ", ".join(frappe.bold(i) for i in invalid) self.warnings.append( { "col": self.column_number, diff --git a/frappe/core/doctype/data_import_legacy/importer.py b/frappe/core/doctype/data_import_legacy/importer.py index 35569c7186..b4be57be4f 100644 --- a/frappe/core/doctype/data_import_legacy/importer.py +++ b/frappe/core/doctype/data_import_legacy/importer.py @@ -181,7 +181,7 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, if d.get("name") and d["name"].startswith('"'): d["name"] = d["name"][1:-1] - if sum([0 if not val else 1 for val in d.values()]): + if sum(0 if not val else 1 for val in d.values()): d['doctype'] = dt if dt == doctype: doc.update(d) @@ -537,6 +537,6 @@ def get_parent_field(doctype, parenttype): def delete_child_rows(rows, doctype): """delete child rows for all parents""" - for p in list(set([r[1] for r in rows])): + for p in list(set(r[1] for r in rows)): if p: frappe.db.sql("""delete from `tab{0}` where parent=%s""".format(doctype), p) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index aff07836c0..582c5d3d89 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -196,7 +196,7 @@ class DocType(Document): self.flags.update_fields_to_fetch_queries = [] - if set(old_fields_to_fetch) != set([df.fieldname for df in new_meta.get_fields_to_fetch()]): + if set(old_fields_to_fetch) != set(df.fieldname for df in new_meta.get_fields_to_fetch()): for df in new_meta.get_fields_to_fetch(): if df.fieldname not in old_fields_to_fetch: link_fieldname, source_fieldname = df.fetch_from.split('.', 1) @@ -765,7 +765,7 @@ def validate_fields(meta): invalid_fields = ('doctype',) if fieldname in invalid_fields: frappe.throw(_("{0}: Fieldname cannot be one of {1}") - .format(docname, ", ".join([frappe.bold(d) for d in invalid_fields]))) + .format(docname, ", ".join(frappe.bold(d) for d in invalid_fields))) def check_unique_fieldname(docname, fieldname): duplicates = list(filter(None, map(lambda df: df.fieldname==fieldname and str(df.idx) or None, fields))) @@ -999,7 +999,7 @@ def validate_fields(meta): if docfield.options and (docfield.options not in data_field_options): df_str = frappe.bold(_(docfield.label)) text_str = _("{0} is an invalid Data field.").format(df_str) + "
" * 2 + _("Only Options allowed for Data field are:") + "
" - df_options_str = "
  • " + "
  • ".join([_(x) for x in data_field_options]) + "
" + df_options_str = "
  • " + "
  • ".join(_(x) for x in data_field_options) + "
" frappe.msgprint(text_str + df_options_str, title="Invalid Data Field", raise_exception=True) diff --git a/frappe/core/doctype/domain/domain.py b/frappe/core/doctype/domain/domain.py index a4e9f503ab..4f320578fb 100644 --- a/frappe/core/doctype/domain/domain.py +++ b/frappe/core/doctype/domain/domain.py @@ -111,7 +111,7 @@ class Domain(Document): # enable frappe.db.sql('''update `tabPortal Menu Item` set enabled=1 - where route in ({0})'''.format(', '.join(['"{0}"'.format(d) for d in self.data.allow_sidebar_items]))) + where route in ({0})'''.format(', '.join('"{0}"'.format(d) for d in self.data.allow_sidebar_items))) if self.data.remove_sidebar_items: # disable all @@ -119,4 +119,4 @@ class Domain(Document): # enable frappe.db.sql('''update `tabPortal Menu Item` set enabled=0 - where route in ({0})'''.format(', '.join(['"{0}"'.format(d) for d in self.data.remove_sidebar_items]))) + where route in ({0})'''.format(', '.join('"{0}"'.format(d) for d in self.data.remove_sidebar_items))) diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index a4d13a57e0..979a300a4d 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -935,7 +935,7 @@ def user_query(doctype, txt, searchfield, start, page_len, filters): LIMIT %(page_len)s OFFSET %(start)s """.format( user_type_condition = user_type_condition, - standard_users=", ".join([frappe.db.escape(u) for u in STANDARD_USERS]), + standard_users=", ".join(frappe.db.escape(u) for u in STANDARD_USERS), key=searchfield, fcond=get_filters_cond(doctype, filters, conditions), mcond=get_match_cond(doctype) diff --git a/frappe/core/doctype/user_type/user_type.py b/frappe/core/doctype/user_type/user_type.py index 0e8b692416..b3965d9b84 100644 --- a/frappe/core/doctype/user_type/user_type.py +++ b/frappe/core/doctype/user_type/user_type.py @@ -114,7 +114,7 @@ class UserType(Document): self.select_doctypes = [] select_doctypes = [] - user_doctypes = tuple([row.document_type for row in self.user_doctypes]) + user_doctypes = tuple(row.document_type for row in self.user_doctypes) for doctype in user_doctypes: doc = frappe.get_meta(doctype) diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index be0dded99c..17fd8ada05 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -356,7 +356,7 @@ class CustomizeForm(Document): def delete_custom_fields(self): meta = frappe.get_meta(self.doc_type) - fields_to_remove = (set([df.fieldname for df in meta.get("fields")]) + fields_to_remove = (set(df.fieldname for df in meta.get("fields")) - set(df.fieldname for df in self.get("fields"))) for fieldname in fields_to_remove: diff --git a/frappe/database/database.py b/frappe/database/database.py index c9c1ec3909..9ff3d25320 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -344,7 +344,7 @@ class Database(object): values[key] = value[1] if isinstance(value[1], (tuple, list)): # value is a list in tuple ("in", ("A", "B")) - _rhs = " ({0})".format(", ".join([self.escape(v) for v in value[1]])) + _rhs = " ({0})".format(", ".join(self.escape(v) for v in value[1])) del values[key] if _operator not in ["=", "!=", ">", ">=", "<", "<=", "like", "in", "not in", "not like"]: @@ -1019,7 +1019,7 @@ class Database(object): :params values: list of list of values """ insert_list = [] - fields = ", ".join(["`"+field+"`" for field in fields]) + fields = ", ".join("`"+field+"`" for field in fields) for idx, value in enumerate(values): insert_list.append(tuple(value)) diff --git a/frappe/desk/doctype/global_search_settings/global_search_settings.py b/frappe/desk/doctype/global_search_settings/global_search_settings.py index 85c9687ab3..f483376ed0 100644 --- a/frappe/desk/doctype/global_search_settings/global_search_settings.py +++ b/frappe/desk/doctype/global_search_settings/global_search_settings.py @@ -22,7 +22,7 @@ class GlobalSearchSettings(Document): dts.append(dt.document_type) if core_dts: - core_dts = (", ".join([frappe.bold(dt) for dt in core_dts])) + core_dts = (", ".join(frappe.bold(dt) for dt in core_dts)) frappe.throw(_("Core Modules {0} cannot be searched in Global Search.").format(core_dts)) if repeated_dts: @@ -61,7 +61,7 @@ def update_global_search_doctypes(): if search_doctypes.get(domain): global_search_doctypes.extend(search_doctypes.get(domain)) - doctype_list = set([dt.name for dt in frappe.get_all("DocType")]) + doctype_list = set(dt.name for dt in frappe.get_all("DocType")) allowed_in_global_search = [] for dt in global_search_doctypes: diff --git a/frappe/desk/doctype/tag/tag.py b/frappe/desk/doctype/tag/tag.py index 7e016ee91b..3ec3f05ac0 100644 --- a/frappe/desk/doctype/tag/tag.py +++ b/frappe/desk/doctype/tag/tag.py @@ -132,7 +132,7 @@ def update_tags(doc, tags): :param doc: Document to be added to global tags """ - new_tags = list(set([tag.strip() for tag in tags.split(",") if tag])) + new_tags = list(set(tag.strip() for tag in tags.split(",") if tag)) for tag in new_tags: if not frappe.db.exists("Tag Link", {"parenttype": doc.doctype, "parent": doc.name, "tag": tag}): diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index 36b662bb39..769dd57481 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -594,7 +594,7 @@ class EmailAccount(Document): # get email account user and set communication as seen users = frappe.get_all("User Email", filters={ "email_account": self.name }, fields=["parent"]) - users = list(set([ user.get("parent") for user in users ])) + users = list(set(user.get("parent") for user in users)) communication._seen = json.dumps(users) communication.flags.in_receive = True @@ -851,8 +851,8 @@ class EmailAccount(Document): email_server.update_flag(uid_list=uid_list) # mark communication as read - docnames = ",".join([ "'%s'"%flag.get("communication") for flag in flags \ - if flag.get("action") == "Read" ]) + docnames = ",".join("'%s'"%flag.get("communication") for flag in flags \ + if flag.get("action") == "Read") self.set_communication_seen_status(docnames, seen=1) # mark communication as unread diff --git a/frappe/email/doctype/newsletter/test_newsletter.py b/frappe/email/doctype/newsletter/test_newsletter.py index bd8fadc29c..073b46af44 100644 --- a/frappe/email/doctype/newsletter/test_newsletter.py +++ b/frappe/email/doctype/newsletter/test_newsletter.py @@ -44,7 +44,7 @@ class TestNewsletter(unittest.TestCase): email_queue_list = [frappe.get_doc("Email Queue", e.name) for e in frappe.get_all("Email Queue")] self.assertEqual(len(email_queue_list), 4) - recipients = set([e.recipients[0].recipient for e in email_queue_list]) + recipients = set(e.recipients[0].recipient for e in email_queue_list) self.assertTrue(set(emails).issubset(recipients)) def test_unsubscribe(self): diff --git a/frappe/email/inbox.py b/frappe/email/inbox.py index 395a2d3e2d..08eef5f7bf 100644 --- a/frappe/email/inbox.py +++ b/frappe/email/inbox.py @@ -18,7 +18,7 @@ def get_email_accounts(user=None): "all_accounts": "" } - all_accounts = ",".join([ account.get("email_account") for account in accounts ]) + all_accounts = ",".join(account.get("email_account") for account in accounts) if len(accounts) > 1: email_accounts.append({ "email_account": all_accounts, diff --git a/frappe/installer.py b/frappe/installer.py index 0cd5b136ae..cc5a6a6734 100755 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -540,7 +540,7 @@ def is_downgrade(sql_file_path, verbose=False): def is_partial(sql_file_path): with open(sql_file_path) as f: - header = " ".join([f.readline() for _ in range(5)]) + header = " ".join(f.readline() for _ in range(5)) if "Partial Backup" in header: return True return False diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py index 80dfef2693..ef72d03cde 100644 --- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py +++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py @@ -80,7 +80,7 @@ class LDAPSettings(Document): def sync_roles(self, user, additional_groups=None): - current_roles = set([d.role for d in user.get("roles")]) + current_roles = set(d.role for d in user.get("roles")) needed_roles = set() needed_roles.add(self.default_role) diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py index 205b451336..d9cbe7313e 100644 --- a/frappe/model/__init__.py +++ b/frappe/model/__init__.py @@ -166,7 +166,7 @@ def delete_fields(args_dict, delete=0): frappe.db.sql(""" DELETE FROM `tabSingles` WHERE doctype='%s' AND field IN (%s) - """ % (dt, ", ".join(["'{}'".format(f) for f in fields]))) + """ % (dt, ", ".join("'{}'".format(f) for f in fields))) else: existing_fields = frappe.db.multisql({ "mariadb": "DESC `tab%s`" % dt, @@ -189,7 +189,7 @@ def delete_fields(args_dict, delete=0): frappe.db.commit() query = "ALTER TABLE `tab%s` " % dt + \ - ", ".join(["DROP COLUMN `%s`" % f for f in fields_need_to_delete]) + ", ".join("DROP COLUMN `%s`" % f for f in fields_need_to_delete) frappe.db.sql(query) if frappe.db.db_type == 'postgres': diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 154a091b8a..34c329175a 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -358,7 +358,7 @@ class BaseDocument(object): frappe.db.sql("""INSERT INTO `tab{doctype}` ({columns}) VALUES ({values})""".format( doctype = self.doctype, - columns = ", ".join(["`"+c+"`" for c in columns]), + columns = ", ".join("`"+c+"`" for c in columns), values = ", ".join(["%s"] * len(columns)) ), list(d.values())) except Exception as e: @@ -401,7 +401,7 @@ class BaseDocument(object): frappe.db.sql("""UPDATE `tab{doctype}` SET {values} WHERE `name`=%s""".format( doctype = self.doctype, - values = ", ".join(["`"+c+"`=%s" for c in columns]) + values = ", ".join("`"+c+"`=%s" for c in columns) ), list(d.values()) + [name]) except Exception as e: if frappe.db.is_unique_key_violation(e): diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index e0c3406c46..8460880794 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -620,7 +620,7 @@ class DatabaseQuery(object): def get_share_condition(self): return """`tab{0}`.name in ({1})""".format(self.doctype, ", ".join(["%s"] * len(self.shared))) % \ - tuple([frappe.db.escape(s, percent=False) for s in self.shared]) + tuple(frappe.db.escape(s, percent=False) for s in self.shared) def add_user_permissions(self, user_permissions): meta = frappe.get_meta(self.doctype) @@ -726,8 +726,8 @@ class DatabaseQuery(object): # `idx desc, modified desc` # will covert to # `tabItem`.`idx` desc, `tabItem`.`modified` desc - args.order_by = ', '.join(['`tab{0}`.`{1}` {2}'.format(self.doctype, - f.split()[0].strip(), f.split()[1].strip()) for f in meta.sort_field.split(',')]) + args.order_by = ', '.join('`tab{0}`.`{1}` {2}'.format(self.doctype, + f.split()[0].strip(), f.split()[1].strip()) for f in meta.sort_field.split(',')) else: sort_field = meta.sort_field or 'modified' sort_order = (meta.sort_field and meta.sort_order) or 'desc' @@ -807,8 +807,8 @@ def get_order_by(doctype, meta): # `idx desc, modified desc` # will covert to # `tabItem`.`idx` desc, `tabItem`.`modified` desc - order_by = ', '.join(['`tab{0}`.`{1}` {2}'.format(doctype, - f.split()[0].strip(), f.split()[1].strip()) for f in meta.sort_field.split(',')]) + order_by = ', '.join('`tab{0}`.`{1}` {2}'.format(doctype, + f.split()[0].strip(), f.split()[1].strip()) for f in meta.sort_field.split(',')) else: sort_field = meta.sort_field or 'modified' sort_order = (meta.sort_field and meta.sort_order) or 'desc' diff --git a/frappe/model/meta.py b/frappe/model/meta.py index 66e8b08d92..1c3e7f60fb 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -667,7 +667,7 @@ def trim_tables(doctype=None): and not f.startswith("_")] if columns_to_remove: print(doctype, "columns removed:", columns_to_remove) - columns_to_remove = ", ".join(["drop `{0}`".format(c) for c in columns_to_remove]) + columns_to_remove = ", ".join("drop `{0}`".format(c) for c in columns_to_remove) query = """alter table `tab{doctype}` {columns}""".format( doctype=doctype, columns=columns_to_remove) frappe.db.sql_ddl(query) diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py index 2c9dc5d823..1567ac544b 100644 --- a/frappe/model/rename_doc.py +++ b/frappe/model/rename_doc.py @@ -144,7 +144,7 @@ def update_user_settings(old, new, link_fields): if not link_fields: return # find the user settings for the linked doctypes - linked_doctypes = set([d.parent for d in link_fields if not d.issingle]) + linked_doctypes = set(d.parent for d in link_fields if not d.issingle) user_settings_details = frappe.db.sql('''SELECT `user`, `doctype`, `data` FROM `__UserSettings` WHERE `data` like %s diff --git a/frappe/permissions.py b/frappe/permissions.py index 19f101aab5..c0ff7fc590 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -312,7 +312,7 @@ def has_controller_permissions(doc, ptype, user=None): return None def get_doctypes_with_read(): - return list(set([p.parent if type(p.parent) == str else p.parent.encode('UTF8') for p in get_valid_perms()])) + return list(set(p.parent if type(p.parent) == str else p.parent.encode('UTF8') for p in get_valid_perms())) def get_valid_perms(doctype=None, user=None): '''Get valid permissions for the current user from DocPerm and Custom DocPerm''' diff --git a/frappe/tests/test_commands.py b/frappe/tests/test_commands.py index b6cd0b575c..07bdf8791e 100644 --- a/frappe/tests/test_commands.py +++ b/frappe/tests/test_commands.py @@ -80,7 +80,7 @@ def exists_in_backup(doctypes, file): ) with gzip.open(file, "rb") as f: content = f.read().decode("utf8") - return all([predicate.format(doctype).lower() in content.lower() for doctype in doctypes]) + return all(predicate.format(doctype).lower() in content.lower() for doctype in doctypes) class BaseTestCommands(unittest.TestCase): @@ -355,12 +355,12 @@ class TestCommands(BaseTestCommands): # test 2: bare functionality for single site self.execute("bench --site {site} list-apps") self.assertEqual(self.returncode, 0) - list_apps = set([ + list_apps = set( _x.split()[0] for _x in self.stdout.split("\n") - ]) + ) doctype = frappe.get_single("Installed Applications").installed_applications if doctype: - installed_apps = set([x.app_name for x in doctype]) + installed_apps = set(x.app_name for x in doctype) else: installed_apps = set(frappe.get_installed_apps()) self.assertSetEqual(list_apps, installed_apps) diff --git a/frappe/translate.py b/frappe/translate.py index 1d8b1234c7..caa7bcfea6 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -283,8 +283,8 @@ def clear_cache(): def get_messages_for_app(app, deduplicate=True): """Returns all messages (list) for a specified `app`""" messages = [] - modules = ", ".join(['"{}"'.format(m.title().replace("_", " ")) \ - for m in frappe.local.app_modules[app]]) + modules = ", ".join('"{}"'.format(m.title().replace("_", " ")) \ + for m in frappe.local.app_modules[app]) # doctypes if modules: diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index 1da4cd3bae..db0f39b3f5 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -186,7 +186,7 @@ def random_string(length): """generate a random string""" import string from random import choice - return ''.join([choice(string.ascii_letters + string.digits) for i in range(length)]) + return ''.join(choice(string.ascii_letters + string.digits) for i in range(length)) def has_gravatar(email): @@ -305,7 +305,7 @@ def make_esc(esc_chars): """ Function generator for Escaping special characters """ - return lambda s: ''.join(['\\' + c if c in esc_chars else c for c in s]) + return lambda s: ''.join('\\' + c if c in esc_chars else c for c in s) # esc / unescape characters -- used for command line def esc(s, esc_chars): diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py index b21efc5e89..908be52452 100644 --- a/frappe/utils/backups.py +++ b/frappe/utils/backups.py @@ -307,8 +307,8 @@ class BackupGenerator: backup_summary = self.get_summary() print("Backup Summary for {0} at {1}".format(frappe.local.site, now())) - title = max([len(x) for x in backup_summary]) - path = max([len(x["path"]) for x in backup_summary.values()]) + title = max(len(x) for x in backup_summary) + path = max(len(x["path"]) for x in backup_summary.values()) for _type, info in backup_summary.items(): template = "{{0:{0}}}: {{1:{1}}} {{2}}".format(title, path) @@ -381,7 +381,7 @@ class BackupGenerator: "", ]) - generated_header = "\n".join([f"-- {x}" for x in database_header_content]) + "\n" + generated_header = "\n".join(f"-- {x}" for x in database_header_content) + "\n" with gzip.open(args.backup_path_db, "wt") as f: f.write(generated_header) diff --git a/frappe/utils/bot.py b/frappe/utils/bot.py index 45e1bd5a4e..68f49c47ed 100644 --- a/frappe/utils/bot.py +++ b/frappe/utils/bot.py @@ -40,10 +40,10 @@ class BotParser(object): def format_list(self, data): '''Format list as markdown''' - return _('I found these: ') + ', '.join([' [{title}](/app/Form/{doctype}/{name})'.format( + return _('I found these: ') + ', '.join(' [{title}](/app/Form/{doctype}/{name})'.format( title = d.title or d.name, doctype=self.get_doctype(), - name=d.name) for d in data]) + name=d.name) for d in data) def get_doctype(self): '''returns the doctype name from self.tables''' @@ -58,8 +58,8 @@ class ShowNotificationBot(BotParser): if open_items: return ("Following items need your attention:\n\n" - + "\n\n".join(["{0} [{1}](/app/List/{1})".format(d[1], d[0]) - for d in open_items if d[1] > 0])) + + "\n\n".join("{0} [{1}](/app/List/{1})".format(d[1], d[0]) + for d in open_items if d[1] > 0)) else: return 'Take it easy, nothing urgent needs your attention' diff --git a/frappe/utils/jinja_globals.py b/frappe/utils/jinja_globals.py index d8c5cb79f1..8eedca5335 100644 --- a/frappe/utils/jinja_globals.py +++ b/frappe/utils/jinja_globals.py @@ -14,10 +14,10 @@ def resolve_class(classes): return classes if isinstance(classes, (list, tuple)): - return " ".join([resolve_class(c) for c in classes]).strip() + return " ".join(resolve_class(c) for c in classes).strip() if isinstance(classes, dict): - return " ".join([classname for classname in classes if classes[classname]]).strip() + return " ".join(classname for classname in classes if classes[classname]).strip() return classes diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py index f78aaac934..5ff2165527 100644 --- a/frappe/website/doctype/web_form/web_form.py +++ b/frappe/website/doctype/web_form/web_form.py @@ -346,7 +346,7 @@ def get_context(context): if missing: frappe.throw(_('Mandatory Information missing:') + '

' - + '
'.join(['{0} ({1})'.format(d.label, d.fieldtype) for d in missing])) + + '
'.join('{0} ({1})'.format(d.label, d.fieldtype) for d in missing)) def allow_website_search_indexing(self): return False diff --git a/frappe/website/doctype/website_slideshow/website_slideshow.py b/frappe/website/doctype/website_slideshow/website_slideshow.py index 90f62d1bb1..03e26bdcfd 100644 --- a/frappe/website/doctype/website_slideshow/website_slideshow.py +++ b/frappe/website/doctype/website_slideshow/website_slideshow.py @@ -23,7 +23,7 @@ class WebsiteSlideshow(Document): files = map(lambda row: row.image, self.slideshow_items) if files: result = frappe.get_all("File", filters={ "file_url":("in", list(files)) }, fields="is_private") - if any([file.is_private for file in result]): + if any(file.is_private for file in result): frappe.throw(_("All Images attached to Website Slideshow should be public")) def get_slideshow(doc): From cbbc85424bd1ce715f813f4349b6a63211b74b96 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 12 May 2021 16:46:58 +0530 Subject: [PATCH 030/164] fix: use set instead of list for new_ags No dependent functionalities expect a list. Co-authored-by: gavin --- frappe/desk/doctype/tag/tag.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/desk/doctype/tag/tag.py b/frappe/desk/doctype/tag/tag.py index 3ec3f05ac0..a85b264cee 100644 --- a/frappe/desk/doctype/tag/tag.py +++ b/frappe/desk/doctype/tag/tag.py @@ -132,7 +132,7 @@ def update_tags(doc, tags): :param doc: Document to be added to global tags """ - new_tags = list(set(tag.strip() for tag in tags.split(",") if tag)) + new_tags = {tag.strip() for tag in tags.split(",") if tag} for tag in new_tags: if not frappe.db.exists("Tag Link", {"parenttype": doc.doctype, "parent": doc.name, "tag": tag}): @@ -187,4 +187,4 @@ def get_documents_for_tag(tag): @frappe.whitelist() def get_tags_list_for_awesomebar(): - return [t.name for t in frappe.get_list("Tag")] \ No newline at end of file + return [t.name for t in frappe.get_list("Tag")] From d8a4cf896b3eca5132d1e23d6da40491005a9f1b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 12 May 2021 16:56:16 +0530 Subject: [PATCH 031/164] perf: prefer set builder notation over constructor --- frappe/automation/doctype/auto_repeat/auto_repeat.py | 2 +- frappe/core/doctype/data_import/importer.py | 4 ++-- .../doctype/global_search_settings/global_search_settings.py | 2 +- frappe/email/doctype/email_account/email_account.py | 2 +- frappe/email/doctype/newsletter/test_newsletter.py | 2 +- frappe/model/rename_doc.py | 2 +- frappe/permissions.py | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frappe/automation/doctype/auto_repeat/auto_repeat.py b/frappe/automation/doctype/auto_repeat/auto_repeat.py index 37285ad571..dfad85d0dd 100644 --- a/frappe/automation/doctype/auto_repeat/auto_repeat.py +++ b/frappe/automation/doctype/auto_repeat/auto_repeat.py @@ -334,7 +334,7 @@ class AutoRepeat(Document): if self.reference_doctype and self.reference_document: res = get_contacts_linking_to(self.reference_doctype, self.reference_document, fields=['email_id']) res += get_contacts_linked_from(self.reference_doctype, self.reference_document, fields=['email_id']) - email_ids = set(d.email_id for d in res) + email_ids = {d.email_id for d in res} if not email_ids: frappe.msgprint(_('No contacts linked to document'), alert=True) else: diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py index 841319dd60..fed6d2cb06 100644 --- a/frappe/core/doctype/data_import/importer.py +++ b/frappe/core/doctype/data_import/importer.py @@ -903,7 +903,7 @@ class Column: if self.df.fieldtype == "Link": # find all values that dont exist - values = list(set(cstr(v) for v in self.column_values[1:] if v)) + values = list({cstr(v) for v in self.column_values[1:] if v}) exists = [ d.name for d in frappe.db.get_all(self.df.options, filters={"name": ("in", values)}) ] @@ -939,7 +939,7 @@ class Column: elif self.df.fieldtype == "Select": options = get_select_options(self.df) if options: - values = list(set(cstr(v) for v in self.column_values[1:] if v)) + values = list({cstr(v) for v in self.column_values[1:] if v}) invalid = list(set(values) - set(options)) if invalid: valid_values = ", ".join(frappe.bold(o) for o in options) diff --git a/frappe/desk/doctype/global_search_settings/global_search_settings.py b/frappe/desk/doctype/global_search_settings/global_search_settings.py index f483376ed0..fead118e75 100644 --- a/frappe/desk/doctype/global_search_settings/global_search_settings.py +++ b/frappe/desk/doctype/global_search_settings/global_search_settings.py @@ -61,7 +61,7 @@ def update_global_search_doctypes(): if search_doctypes.get(domain): global_search_doctypes.extend(search_doctypes.get(domain)) - doctype_list = set(dt.name for dt in frappe.get_all("DocType")) + doctype_list = {dt.name for dt in frappe.get_all("DocType")} allowed_in_global_search = [] for dt in global_search_doctypes: diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index 769dd57481..786a185585 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -594,7 +594,7 @@ class EmailAccount(Document): # get email account user and set communication as seen users = frappe.get_all("User Email", filters={ "email_account": self.name }, fields=["parent"]) - users = list(set(user.get("parent") for user in users)) + users = list({user.get("parent") for user in users}) communication._seen = json.dumps(users) communication.flags.in_receive = True diff --git a/frappe/email/doctype/newsletter/test_newsletter.py b/frappe/email/doctype/newsletter/test_newsletter.py index 073b46af44..4b9a0749d5 100644 --- a/frappe/email/doctype/newsletter/test_newsletter.py +++ b/frappe/email/doctype/newsletter/test_newsletter.py @@ -44,7 +44,7 @@ class TestNewsletter(unittest.TestCase): email_queue_list = [frappe.get_doc("Email Queue", e.name) for e in frappe.get_all("Email Queue")] self.assertEqual(len(email_queue_list), 4) - recipients = set(e.recipients[0].recipient for e in email_queue_list) + recipients = {e.recipients[0].recipient for e in email_queue_list} self.assertTrue(set(emails).issubset(recipients)) def test_unsubscribe(self): diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py index 1567ac544b..2c2550f39d 100644 --- a/frappe/model/rename_doc.py +++ b/frappe/model/rename_doc.py @@ -144,7 +144,7 @@ def update_user_settings(old, new, link_fields): if not link_fields: return # find the user settings for the linked doctypes - linked_doctypes = set(d.parent for d in link_fields if not d.issingle) + linked_doctypes = {d.parent for d in link_fields if not d.issingle} user_settings_details = frappe.db.sql('''SELECT `user`, `doctype`, `data` FROM `__UserSettings` WHERE `data` like %s diff --git a/frappe/permissions.py b/frappe/permissions.py index c0ff7fc590..c17f4b6bf1 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -312,7 +312,7 @@ def has_controller_permissions(doc, ptype, user=None): return None def get_doctypes_with_read(): - return list(set(p.parent if type(p.parent) == str else p.parent.encode('UTF8') for p in get_valid_perms())) + return list({p.parent if type(p.parent) == str else p.parent.encode('UTF8') for p in get_valid_perms()}) def get_valid_perms(doctype=None, user=None): '''Get valid permissions for the current user from DocPerm and Custom DocPerm''' From 6d8c691dddc502baf160b28d05c1d5baa8da8e35 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 12 May 2021 19:22:31 +0530 Subject: [PATCH 032/164] refactor: use list instead of tuple constructor --- frappe/core/doctype/user_type/user_type.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/user_type/user_type.py b/frappe/core/doctype/user_type/user_type.py index b3965d9b84..b3e61610cc 100644 --- a/frappe/core/doctype/user_type/user_type.py +++ b/frappe/core/doctype/user_type/user_type.py @@ -114,7 +114,7 @@ class UserType(Document): self.select_doctypes = [] select_doctypes = [] - user_doctypes = tuple(row.document_type for row in self.user_doctypes) + user_doctypes = [row.document_type for row in self.user_doctypes] for doctype in user_doctypes: doc = frappe.get_meta(doctype) @@ -267,4 +267,4 @@ def apply_permissions_for_non_standard_user_type(doc, method=None): user_doc.update_children() add_user_permission(doc.doctype, doc.name, doc.get(data[1])) else: - frappe.db.set_value('User Permission', perm_data[0], 'user', doc.get(data[1])) \ No newline at end of file + frappe.db.set_value('User Permission', perm_data[0], 'user', doc.get(data[1])) From d7eea226094145a4d45e4fbc6a7ef8bdfe2fad55 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 12 May 2021 19:25:21 +0530 Subject: [PATCH 033/164] chore: misc translation/semgrep fixes --- frappe/core/doctype/data_import/importer.py | 5 +---- frappe/utils/bot.py | 2 +- frappe/utils/jinja_globals.py | 4 ++-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py index fed6d2cb06..6337d919eb 100644 --- a/frappe/core/doctype/data_import/importer.py +++ b/frappe/core/doctype/data_import/importer.py @@ -929,10 +929,7 @@ class Column: self.warnings.append( { "col": self.column_number, - "message": _( - "Date format could not be determined from the values in" - " this column. Defaulting to yyyy-mm-dd." - ), + "message": _("Date format could not be determined from the values in this column. Defaulting to yyyy-mm-dd."), "type": "info", } ) diff --git a/frappe/utils/bot.py b/frappe/utils/bot.py index 68f49c47ed..33ec139b64 100644 --- a/frappe/utils/bot.py +++ b/frappe/utils/bot.py @@ -40,7 +40,7 @@ class BotParser(object): def format_list(self, data): '''Format list as markdown''' - return _('I found these: ') + ', '.join(' [{title}](/app/Form/{doctype}/{name})'.format( + return _('I found these:') + ' ' + ', '.join(' [{title}](/app/Form/{doctype}/{name})'.format( title = d.title or d.name, doctype=self.get_doctype(), name=d.name) for d in data) diff --git a/frappe/utils/jinja_globals.py b/frappe/utils/jinja_globals.py index 8eedca5335..906d557326 100644 --- a/frappe/utils/jinja_globals.py +++ b/frappe/utils/jinja_globals.py @@ -38,13 +38,13 @@ def web_block(template, values=None, **kwargs): def web_blocks(blocks): - from frappe import throw, _dict + from frappe import throw, _dict, _ from frappe.website.doctype.web_page.web_page import get_web_blocks_html web_blocks = [] for block in blocks: if not block.get("template"): - throw("Web Template is not specified") + throw(_("Web Template is not specified")) doc = _dict( { From fed99bffac29ff4cb99866d6a3e3246170525916 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 13 May 2021 13:34:56 +0530 Subject: [PATCH 034/164] fix: Set basepath and toc data to show sidebar --- frappe/website/page_controllers/template_page.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frappe/website/page_controllers/template_page.py b/frappe/website/page_controllers/template_page.py index b050a62bad..49a811b587 100644 --- a/frappe/website/page_controllers/template_page.py +++ b/frappe/website/page_controllers/template_page.py @@ -5,6 +5,7 @@ import frappe from frappe.website.page_controllers.base_template_page import BaseTemplatePage from frappe.website.context import add_sidebar_and_breadcrumbs from frappe.website.render import build_response +from frappe.website.router import get_base_template from frappe.website.utils import (extract_comment_tag, extract_title, get_next_link, get_toc) @@ -31,6 +32,7 @@ class TemplatePage(BaseTemplatePage): self.app = app self.app_path = app_path self.template_path = os.path.relpath(file_path, self.app_path) + self.basepath = os.path.dirname(file_path) return True def get_index_path_options(self, search_path): @@ -74,7 +76,6 @@ class TemplatePage(BaseTemplatePage): same folder. Also the hyphens will be coverted to underscore for python module names. This method sets the pymodule_name if it exists. ''' - self.basepath = self.template_path.rsplit('.', 1)[0] self.pymodule_name = None # replace - with _ in the internal modules names @@ -90,6 +91,9 @@ class TemplatePage(BaseTemplatePage): self.convert_from_markdown() def update_context(self): + self.context.base_template = self.context.base_template or get_base_template(self.path) + self.context.basepath = self.basepath + self.context.path = self.path self.set_page_properties() self.set_properties_from_source() self.load_colocated_files() @@ -196,7 +200,7 @@ class TemplatePage(BaseTemplatePage): def convert_from_markdown(self): if self.template_path.endswith('.md'): self.source = frappe.utils.md_to_html(self.source) - self.page_toc_html = self.source.toc_html + self.context.page_toc_html = self.source.toc_html if not self.context.show_sidebar: self.source = '
' + self.source + '
' From dbd8b66d815c8d255fb493c97eadb68671dcb844 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 13 May 2021 14:22:26 +0530 Subject: [PATCH 035/164] fix: Template basepath --- frappe/website/page_controllers/template_page.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/website/page_controllers/template_page.py b/frappe/website/page_controllers/template_page.py index 49a811b587..4db6d17541 100644 --- a/frappe/website/page_controllers/template_page.py +++ b/frappe/website/page_controllers/template_page.py @@ -76,10 +76,11 @@ class TemplatePage(BaseTemplatePage): same folder. Also the hyphens will be coverted to underscore for python module names. This method sets the pymodule_name if it exists. ''' + template_basepath = os.path.splitext(self.template_path)[0] self.pymodule_name = None # replace - with _ in the internal modules names - self.pymodule_path = os.path.join(self.basepath.replace("-", "_") + ".py") + self.pymodule_path = os.path.join(template_basepath.replace("-", "_") + ".py") if os.path.exists(os.path.join(self.app_path, self.pymodule_path)): self.pymodule_name = self.app + "." + self.pymodule_path.replace(os.path.sep, ".")[:-3] From 8b1b5c707b11f150d85d791354a7ebadccd27f43 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 13 May 2021 16:41:20 +0530 Subject: [PATCH 036/164] fix: remove unnecessary casting from set to list --- frappe/core/doctype/data_import/importer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py index 6337d919eb..6856fde4d0 100644 --- a/frappe/core/doctype/data_import/importer.py +++ b/frappe/core/doctype/data_import/importer.py @@ -936,8 +936,8 @@ class Column: elif self.df.fieldtype == "Select": options = get_select_options(self.df) if options: - values = list({cstr(v) for v in self.column_values[1:] if v}) - invalid = list(set(values) - set(options)) + values = {cstr(v) for v in self.column_values[1:] if v} + invalid = values - set(options) if invalid: valid_values = ", ".join(frappe.bold(o) for o in options) invalid_values = ", ".join(frappe.bold(i) for i in invalid) From 24434ed9dee018f4910898979f90d596f3f64dc3 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 14 May 2021 13:26:45 +0530 Subject: [PATCH 037/164] refactor: Delete unused code from render.py --- frappe/website/render.py | 174 +-------------------------------------- 1 file changed, 2 insertions(+), 172 deletions(-) diff --git a/frappe/website/render.py b/frappe/website/render.py index b7221e364c..ee80fa3b9f 100644 --- a/frappe/website/render.py +++ b/frappe/website/render.py @@ -22,112 +22,11 @@ from frappe.website.utils import (get_home_page, can_cache, delete_page_cache, from frappe.website.router import clear_sitemap, evaluate_dynamic_routes from frappe.translate import guess_language +from frappe.website.serve import get_response class PageNotFoundError(Exception): pass def render(path=None, http_status_code=None): - # temp feature flag - if True or frappe.conf.flag_new_website: - from frappe.website.serve import get_response - return get_response(path, http_status_code) - else: - return _render(path, http_status_code) - -def _render(path=None, http_status_code=None): - """render html page""" - if not path: - path = frappe.local.request.path - - try: - path = path.strip('/ ') - raise_if_disabled(path) - resolve_redirect(path) - path = resolve_path(path) - data = None - - # if in list of already known 404s, send it - if can_cache() and frappe.cache().hget('website_404', frappe.request.url): - data = render_page('404') - http_status_code = 404 - elif is_static_file(path): - return get_static_file_response() - elif is_web_form(path): - data = render_web_form(path) - else: - try: - data = render_page_by_language(path) - except frappe.PageDoesNotExistError: - doctype, name = get_doctype_from_path(path) - if doctype and name: - path = "printview" - frappe.local.form_dict.doctype = doctype - frappe.local.form_dict.name = name - elif doctype: - path = "list" - frappe.local.form_dict.doctype = doctype - else: - # 404s are expensive, cache them! - frappe.cache().hset('website_404', frappe.request.url, True) - data = render_page('404') - http_status_code = 404 - - if not data: - try: - data = render_page(path) - except frappe.PermissionError as e: - data, http_status_code = render_403(e, path) - - except frappe.PermissionError as e: - data, http_status_code = render_403(e, path) - - except frappe.Redirect as e: - raise e - - except Exception: - path = "error" - data = render_page(path) - http_status_code = 500 - - data = add_csrf_token(data) - - except frappe.Redirect: - return build_response(path, "", 301, { - "Location": frappe.flags.redirect_location or (frappe.local.response or {}).get('location'), - "Cache-Control": "no-store, no-cache, must-revalidate" - }) - - return build_response(path, data, http_status_code or 200) - -def is_static_file(path): - if ('.' not in path): - return False - extn = path.rsplit('.', 1)[-1] - if extn in ('html', 'md', 'js', 'xml', 'css', 'txt', 'py', 'json'): - return False - - for app in frappe.get_installed_apps(): - file_path = frappe.get_app_path(app, 'www') + '/' + path - if os.path.exists(file_path): - frappe.flags.file_path = file_path - return True - - return False - -def is_web_form(path): - return bool(frappe.get_all("Web Form", filters={'route': path})) - -def render_web_form(path): - data = render_page(path) - return data - -def get_static_file_response(): - try: - f = open(frappe.flags.file_path, 'rb') - except IOError: - raise NotFound - - response = Response(wrap_file(frappe.local.request.environ, f), direct_passthrough=True) - response.mimetype = mimetypes.guess_type(frappe.flags.file_path)[0] or 'application/octet-stream' - return response + return get_response(path, http_status_code) def build_response(path, data, http_status_code, headers=None): # build response @@ -168,23 +67,6 @@ def add_preload_headers(response): traceback.print_exc() -def render_page_by_language(path): - translated_languages = frappe.get_hooks("translated_languages_for_website") - user_lang = guess_language(translated_languages) - if translated_languages and user_lang in translated_languages: - try: - if path and path != "index": - lang_path = '{0}/{1}'.format(user_lang, path) - else: - lang_path = user_lang # index - - return render_page(lang_path) - except frappe.DoesNotExistError: - return render_page(path) - - else: - return render_page(path) - def render_page(path): """get page html""" out = None @@ -307,7 +189,6 @@ def set_content_type(response, data, path): def clear_cache(path=None): '''Clear website caches - :param path: (optional) for the given path''' for key in ('website_generator_routes', 'website_pages', 'website_full_index', 'sitemap_routes'): @@ -327,54 +208,3 @@ def clear_cache(path=None): for method in frappe.get_hooks("website_clear_cache"): frappe.get_attr(method)(path) - -def render_403(e, pathname): - frappe.local.message = cstr(e.message if six.PY2 else e) - frappe.local.message_title = _("Not Permitted") - frappe.local.response['context'] = dict( - indicator_color = 'red', - primary_action = '/login', - primary_label = _('Login'), - fullpage=True - ) - return render_page("message"), e.http_status_code - -def get_doctype_from_path(path): - doctypes = frappe.db.sql_list("select name from tabDocType") - - parts = path.split("/") - - doctype = parts[0] - name = parts[1] if len(parts) > 1 else None - - if doctype in doctypes: - return doctype, name - - # try scrubbed - doctype = doctype.replace("_", " ").title() - if doctype in doctypes: - return doctype, name - - return None, None - -def add_csrf_token(data): - if frappe.local.session: - return data.replace("", ''.format( - frappe.local.session.data.csrf_token)) - else: - return data - -def raise_if_disabled(path): - routes = frappe.db.get_all('Portal Menu Item', - fields=['route', 'enabled'], - filters={ - 'enabled': 0, - 'route': ['like', '%{0}'.format(path)] - } - ) - - for r in routes: - _path = r.route.lstrip('/') - if path == _path and not r.enabled: - raise frappe.PermissionError - From 4219ef98d18e1b9caf00558bf1adca96c76e3154 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 14 May 2021 13:31:13 +0530 Subject: [PATCH 038/164] fix: Update renderer sequence --- frappe/website/serve.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/serve.py b/frappe/website/serve.py index eacaef96e1..f11ba1c4fc 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -29,7 +29,7 @@ def get_response(path=None, http_status_code=200): path = resolve_path(path) # there is no way to determine the type of the page based on the route # so evaluate each type of page sequentially - renderers = [StaticPage, TemplatePage, ListPage, WebFormPage, DocumentPage, PrintPage, NotFoundPage] + renderers = [StaticPage, WebFormPage, TemplatePage, ListPage, DocumentPage, PrintPage, NotFoundPage] for renderer in renderers: response = renderer(path, http_status_code).get() if response: From 39c1a94f896fff0e1781b8df6b60047cb8b7e286 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 14 May 2021 13:53:04 +0530 Subject: [PATCH 039/164] fix: Circular import --- frappe/website/page_controllers/web_page.py | 1 + frappe/website/render.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/website/page_controllers/web_page.py b/frappe/website/page_controllers/web_page.py index 2020d491e4..4d9e642b4b 100644 --- a/frappe/website/page_controllers/web_page.py +++ b/frappe/website/page_controllers/web_page.py @@ -7,6 +7,7 @@ class WebPage(object): if not path: path = frappe.local.request.path self.path = path.strip('/ ') + self.basepath = '' def get(self): if self.validate(): diff --git a/frappe/website/render.py b/frappe/website/render.py index ee80fa3b9f..3868a91232 100644 --- a/frappe/website/render.py +++ b/frappe/website/render.py @@ -22,10 +22,10 @@ from frappe.website.utils import (get_home_page, can_cache, delete_page_cache, from frappe.website.router import clear_sitemap, evaluate_dynamic_routes from frappe.translate import guess_language -from frappe.website.serve import get_response class PageNotFoundError(Exception): pass def render(path=None, http_status_code=None): + from frappe.website.serve import get_response return get_response(path, http_status_code) def build_response(path, data, http_status_code, headers=None): From 56ee6e449cedd64856bc9af9539006d5149266c8 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 14 May 2021 14:17:52 +0530 Subject: [PATCH 040/164] refactor: Remove render method - also, move clear cache to utils.py --- frappe/utils/response.py | 3 ++- frappe/website/render.py | 33 +++++---------------------------- frappe/website/router.py | 3 --- frappe/website/utils.py | 27 ++++++++++++++++++++++++++- 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/frappe/utils/response.py b/frappe/utils/response.py index 44b037f3cc..97097ab280 100644 --- a/frappe/utils/response.py +++ b/frappe/utils/response.py @@ -217,7 +217,8 @@ def send_private_file(path): return response def handle_session_stopped(): + from frappe.website.serve import get_response frappe.respond_as_web_page(_("Updating"), _("Your system is being updated. Please refresh again after a few moments."), http_status_code=503, indicator_color='orange', fullpage = True, primary_action=None) - return frappe.website.render.render("message", http_status_code=503) + return get_response("message", http_status_code=503) diff --git a/frappe/website/render.py b/frappe/website/render.py index 3868a91232..895faa5b5f 100644 --- a/frappe/website/render.py +++ b/frappe/website/render.py @@ -6,27 +6,21 @@ import frappe from frappe import _ import frappe.sessions from frappe.utils import cstr -import os, mimetypes, json +import mimetypes, json import re -import six from six import iteritems from werkzeug.wrappers import Response from werkzeug.routing import Rule from werkzeug.wsgi import wrap_file from frappe.website.context import get_context -from frappe.website.redirect import resolve_redirect from frappe.website.utils import (get_home_page, can_cache, delete_page_cache, get_toc, get_next_link) -from frappe.website.router import clear_sitemap, evaluate_dynamic_routes -from frappe.translate import guess_language +from frappe.website.router import evaluate_dynamic_routes class PageNotFoundError(Exception): pass -def render(path=None, http_status_code=None): - from frappe.website.serve import get_response - return get_response(path, http_status_code) def build_response(path, data, http_status_code, headers=None): # build response @@ -188,23 +182,6 @@ def set_content_type(response, data, path): return data def clear_cache(path=None): - '''Clear website caches - :param path: (optional) for the given path''' - for key in ('website_generator_routes', 'website_pages', - 'website_full_index', 'sitemap_routes'): - frappe.cache().delete_value(key) - - frappe.cache().delete_value("website_404") - if path: - frappe.cache().hdel('website_redirects', path) - delete_page_cache(path) - else: - clear_sitemap() - frappe.clear_cache("Guest") - for key in ('portal_menu_items', 'home_page', 'website_route_rules', - 'doctypes_with_web_view', 'website_redirects', 'page_context', - 'website_page'): - frappe.cache().delete_value(key) - - for method in frappe.get_hooks("website_clear_cache"): - frappe.get_attr(method)(path) + # TODO: Remove this + from frappe.website.utils import clear_cache + return clear_cache(path) diff --git a/frappe/website/router.py b/frappe/website/router.py index 1e61f6dcc3..36779923b5 100644 --- a/frappe/website/router.py +++ b/frappe/website/router.py @@ -85,9 +85,6 @@ def get_page_context_from_doctype(path): return frappe.get_doc(page_info.get("doctype"), page_info.get("name")).get_page_info() -def clear_sitemap(): - delete_page_cache("*") - def get_all_page_context_from_doctypes(): ''' Get all doctype generated routes (for sitemap.xml) diff --git a/frappe/website/utils.py b/frappe/website/utils.py index efa15bdb14..8c75508d07 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -11,7 +11,6 @@ from six import iteritems from past.builtins import cmp from frappe.utils import md_to_html - def delete_page_cache(path): cache = frappe.cache() cache.delete_value('full_index') @@ -399,3 +398,29 @@ def get_html_content_based_on_type(doc, fieldname, content_type): content = '' return content + + +def clear_cache(path=None): + '''Clear website caches + :param path: (optional) for the given path''' + for key in ('website_generator_routes', 'website_pages', + 'website_full_index', 'sitemap_routes'): + frappe.cache().delete_value(key) + + frappe.cache().delete_value("website_404") + if path: + frappe.cache().hdel('website_redirects', path) + delete_page_cache(path) + else: + clear_sitemap() + frappe.clear_cache("Guest") + for key in ('portal_menu_items', 'home_page', 'website_route_rules', + 'doctypes_with_web_view', 'website_redirects', 'page_context', + 'website_page'): + frappe.cache().delete_value(key) + + for method in frappe.get_hooks("website_clear_cache"): + frappe.get_attr(method)(path) + +def clear_sitemap(): + delete_page_cache("*") From 9ba5bf1133b05d2435e74099227609970501c87c Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 14 May 2021 18:42:41 +0530 Subject: [PATCH 041/164] refactor: Move metatags related code into a separate file --- .../page_controllers/base_template_page.py | 75 ++----------------- frappe/website/website_components/metatags.py | 68 +++++++++++++++++ 2 files changed, 73 insertions(+), 70 deletions(-) create mode 100644 frappe/website/website_components/metatags.py diff --git a/frappe/website/page_controllers/base_template_page.py b/frappe/website/page_controllers/base_template_page.py index 74ca400372..c40afb7b1c 100644 --- a/frappe/website/page_controllers/base_template_page.py +++ b/frappe/website/page_controllers/base_template_page.py @@ -1,13 +1,13 @@ import frappe -from frappe.website.doctype.website_settings.website_settings import \ - get_website_settings +from frappe.website.doctype.website_settings.website_settings import get_website_settings from frappe.website.page_controllers.web_page import WebPage +from frappe.website.website_components.metatags import MetaTags class BaseTemplatePage(WebPage): def init_context(self): self.context = frappe._dict() - self.context.update(get_website_settings(self.context)) + self.context.update(get_website_settings()) self.context.update(frappe.local.conf.get("website_context") or {}) def add_csrf_token(self, html): @@ -19,7 +19,8 @@ class BaseTemplatePage(WebPage): return html def post_process_context(self): - self.add_metatags() + self.tags = MetaTags(self.path, self.context).tags + self.context.metatags = self.tags self.set_base_template_if_missing() self.set_title_with_prefix() self.update_website_context() @@ -53,69 +54,3 @@ class BaseTemplatePage(WebPage): values = frappe.get_attr(method)(self.context) if values: self.context.update(values) - - def add_metatags(self): - self.tags = frappe._dict(self.context.get("metatags") or {}) - self.init_metatags_from_context() - self.set_opengraph_tags() - self.set_twitter_tags() - self.set_meta_published_on() - self.set_metatags_from_website_route_meta() - - self.context.metatags = self.tags - - def init_metatags_from_context(self): - for key in ('title', 'description', 'image', 'author', 'url', 'published_on'): - if key not in self.tags and self.context.get(key): - self.tags[key] = self.context[key] - - if not self.tags.get('title'): - self.tags['title'] = self.context.get('name') - - if self.tags.get('image'): - self.tags['image'] = frappe.utils.get_url(self.tags['image']) - - self.tags["language"] = frappe.local.lang or "en" - - def set_opengraph_tags(self): - if "og:type" not in self.tags: - self.tags["og:type"] = "article" - - for key in ('title', 'description', 'image', 'author', 'url'): - if self.tags.get(key): - self.tags['og:' + key] = self.tags.get(key) - - def set_twitter_tags(self): - for key in ('title', 'description', 'image', 'author', 'url'): - if self.tags.get(key): - self.tags['twitter:' + key] = self.tags.get(key) - - if self.tags.get('image'): - self.tags['twitter:card'] = "summary_large_image" - else: - self.tags["twitter:card"] = "summary" - - def set_meta_published_on(self): - if "published_on" in self.tags: - self.tags["datePublished"] = self.tags["published_on"] - del self.tags["published_on"] - - def set_metatags_from_website_route_meta(self): - ''' - Get meta tags from Website Route meta - they can override the defaults set above - ''' - route = self.path - if route == '': - # homepage - route = frappe.db.get_single_value('Website Settings', 'home_page') - - route_exists = (route - and not route.endswith(('.js', '.css')) - and frappe.db.exists('Website Route Meta', route)) - - if route_exists: - website_route_meta = frappe.get_doc('Website Route Meta', route) - for meta_tag in website_route_meta.meta_tags: - d = meta_tag.get_meta_dict() - self.tags.update(d) diff --git a/frappe/website/website_components/metatags.py b/frappe/website/website_components/metatags.py new file mode 100644 index 0000000000..045bef8fe1 --- /dev/null +++ b/frappe/website/website_components/metatags.py @@ -0,0 +1,68 @@ +import frappe + +class MetaTags(): + def __init__(self, path, context): + self.path = path + self.context = context + self.tags = frappe._dict(self.context.get("metatags") or {}) + self.init_metatags_from_context() + self.set_opengraph_tags() + self.set_twitter_tags() + self.set_meta_published_on() + self.set_metatags_from_website_route_meta() + + def init_metatags_from_context(self): + for key in ('title', 'description', 'image', 'author', 'url', 'published_on'): + if key not in self.tags and self.context.get(key): + self.tags[key] = self.context[key] + + if not self.tags.get('title'): + self.tags['title'] = self.context.get('name') + + if self.tags.get('image'): + self.tags['image'] = frappe.utils.get_url(self.tags['image']) + + self.tags["language"] = frappe.local.lang or "en" + + def set_opengraph_tags(self): + if "og:type" not in self.tags: + self.tags["og:type"] = "article" + + for key in ('title', 'description', 'image', 'author', 'url'): + if self.tags.get(key): + self.tags['og:' + key] = self.tags.get(key) + + def set_twitter_tags(self): + for key in ('title', 'description', 'image', 'author', 'url'): + if self.tags.get(key): + self.tags['twitter:' + key] = self.tags.get(key) + + if self.tags.get('image'): + self.tags['twitter:card'] = "summary_large_image" + else: + self.tags["twitter:card"] = "summary" + + def set_meta_published_on(self): + if "published_on" in self.tags: + self.tags["datePublished"] = self.tags["published_on"] + del self.tags["published_on"] + + def set_metatags_from_website_route_meta(self): + ''' + Get meta tags from Website Route meta + they can override the defaults set above + ''' + route = self.path + if route == '': + # homepage + route = frappe.db.get_single_value('Website Settings', 'home_page') + + route_exists = (route + and not route.endswith(('.js', '.css')) + and frappe.db.exists('Website Route Meta', route)) + + if route_exists: + website_route_meta = frappe.get_doc('Website Route Meta', route) + for meta_tag in website_route_meta.meta_tags: + d = meta_tag.get_meta_dict() + self.tags.update(d) From 20bab0f631acd5dfbf3ddda250472568f057fc6a Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 14 May 2021 18:58:04 +0530 Subject: [PATCH 042/164] refactor: Move code to appropriate places --- frappe/utils/__init__.py | 4 +-- .../website/page_controllers/template_page.py | 36 +++---------------- frappe/website/router.py | 2 +- frappe/website/utils.py | 16 +++++++++ 4 files changed, 24 insertions(+), 34 deletions(-) diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index 43a7b82c16..845cb9be4a 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -770,9 +770,9 @@ def set_request(**kwargs): frappe.local.request = Request(builder.get_environ()) def get_html_for_route(route): - from frappe.website import render + from frappe.website.serve import get_response set_request(method='GET', path=route) - response = render.render() + response = get_response() html = frappe.safe_decode(response.get_data()) return html diff --git a/frappe/website/page_controllers/template_page.py b/frappe/website/page_controllers/template_page.py index 4db6d17541..f2664fb928 100644 --- a/frappe/website/page_controllers/template_page.py +++ b/frappe/website/page_controllers/template_page.py @@ -6,9 +6,10 @@ from frappe.website.page_controllers.base_template_page import BaseTemplatePage from frappe.website.context import add_sidebar_and_breadcrumbs from frappe.website.render import build_response from frappe.website.router import get_base_template -from frappe.website.utils import (extract_comment_tag, extract_title, - get_next_link, get_toc) +from frappe.website.utils import (extract_comment_tag, + extract_title, get_next_link, get_toc, get_frontmatter) +WEBPAGE_PY_MODULE_PROPERTIES = ("base_template_path", "template", "no_cache", "sitemap", "condition_field") class TemplatePage(BaseTemplatePage): def validate(self): @@ -36,12 +37,7 @@ class TemplatePage(BaseTemplatePage): return True def get_index_path_options(self, search_path): - return ( - search_path, - search_path + '.html', - search_path + '.md', - search_path + '/index.html', - search_path + '/index.md') + return (f'{search_path}{d}' for d in ('', '.html', '.md', '/index.html', '/index.md')) def render(self): return build_response(self.path, self.get_html(), self.http_status_code, self.headers) @@ -115,8 +111,7 @@ class TemplatePage(BaseTemplatePage): self.http_status_code = self.context.http_status_code def set_pymodule_properties(self): - for prop in ("base_template_path", "template", "no_cache", "sitemap", - "condition_field"): + for prop in WEBPAGE_PY_MODULE_PROPERTIES: if hasattr(self.pymodule, prop): self.context[prop] = getattr(self.pymodule, prop) @@ -237,24 +232,3 @@ class TemplatePage(BaseTemplatePage): def get_start_folders(): return frappe.local.flags.web_pages_folders or ('www', 'templates/pages') - -def get_frontmatter(string): - """ - Reference: https://github.com/jonbeebe/frontmatter - """ - import re - - import yaml - - fmatter = "" - body = "" - result = re.compile(r'^\s*(?:---|\+\+\+)(.*?)(?:---|\+\+\+)\s*(.+)$', re.S | re.M).search(string) - - if result: - fmatter = result.group(1) - body = result.group(2) - - return { - "attributes": yaml.safe_load(fmatter), - "body": body, - } diff --git a/frappe/website/router.py b/frappe/website/router.py index 36779923b5..b048fa55c0 100644 --- a/frappe/website/router.py +++ b/frappe/website/router.py @@ -9,7 +9,7 @@ import re import frappe from frappe.model.document import get_controller -from frappe.website.utils import can_cache, delete_page_cache, extract_comment_tag, extract_title +from frappe.website.utils import can_cache, extract_comment_tag, extract_title from werkzeug.routing import Map, Rule, NotFound def resolve_route(path): diff --git a/frappe/website/utils.py b/frappe/website/utils.py index 8c75508d07..87e47571fb 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -6,6 +6,8 @@ import functools import re import os import frappe +import re +import yaml from six import iteritems from past.builtins import cmp @@ -424,3 +426,17 @@ def clear_cache(path=None): def clear_sitemap(): delete_page_cache("*") + +def get_frontmatter(string): + "Reference: https://github.com/jonbeebe/frontmatter" + frontmatter = "" + body = "" + result = re.compile(r'^\s*(?:---|\+\+\+)(.*?)(?:---|\+\+\+)\s*(.+)$', re.S | re.M).search(string) + if result: + frontmatter = result.group(1) + body = result.group(2) + + return { + "attributes": yaml.safe_load(frontmatter), + "body": body, + } From 18497989dc71d24c3e7662382fb58cc2d84c1e0a Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 14 May 2021 21:30:28 +0530 Subject: [PATCH 043/164] refactor: Remove render_page from render.py & replace all usages of render_page with get_response --- frappe/__init__.py | 4 +- frappe/search/website_search.py | 4 +- frappe/tests/test_recorder.py | 4 +- frappe/utils/global_search.py | 4 +- .../website/doctype/web_form/test_web_form.py | 1 - frappe/website/render.py | 50 +++---------------- frappe/website/router.py | 6 +-- 7 files changed, 19 insertions(+), 54 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 02b8d71e40..bd46f0f874 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -1485,7 +1485,7 @@ def get_print(doctype=None, name=None, print_format=None, style=None, :param style: Print Format style. :param as_pdf: Return as PDF. Default False. :param password: Password to encrypt the pdf with. Default None""" - from frappe.website.render import build_page + from frappe.website.serve import get_response from frappe.utils.pdf import get_pdf local.form_dict.doctype = doctype @@ -1500,7 +1500,7 @@ def get_print(doctype=None, name=None, print_format=None, style=None, options = {'password': password} if not html: - html = build_page("printview") + html = get_response("printview") if as_pdf: return get_pdf(html, output = output, options = options) diff --git a/frappe/search/website_search.py b/frappe/search/website_search.py index 452ea2a427..ad2ebb504d 100644 --- a/frappe/search/website_search.py +++ b/frappe/search/website_search.py @@ -9,7 +9,7 @@ from whoosh.fields import ID, TEXT, Schema import frappe from frappe.search.full_text_search import FullTextSearch from frappe.utils import set_request, update_progress_bar -from frappe.website.render import render_page +from frappe.website.serve import get_response INDEX_NAME = "web_routes" @@ -61,7 +61,7 @@ class WebsiteSearch(FullTextSearch): try: set_request(method="GET", path=route) - content = render_page(route) + content = get_response(route) soup = BeautifulSoup(content, "html.parser") page_content = soup.find(class_="page_content") text_content = page_content.text if page_content else "" diff --git a/frappe/tests/test_recorder.py b/frappe/tests/test_recorder.py index 64d3c52f63..7dcccd1d71 100644 --- a/frappe/tests/test_recorder.py +++ b/frappe/tests/test_recorder.py @@ -8,7 +8,7 @@ import unittest import frappe import frappe.recorder from frappe.utils import set_request -from frappe.website.render import render_page +from frappe.website.serve import get_response import sqlparse @@ -122,5 +122,5 @@ class TestRecorder(unittest.TestCase): self.assertEqual(call['exact_copies'], query[1]) def test_error_page_rendering(self): - content = render_page("error") + content = get_response("error") self.assertIn("Error", content) diff --git a/frappe/utils/global_search.py b/frappe/utils/global_search.py index c20f3b29d4..7541e8b31d 100644 --- a/frappe/utils/global_search.py +++ b/frappe/utils/global_search.py @@ -310,14 +310,14 @@ def get_routes_to_index(): def add_route_to_global_search(route): from bs4 import BeautifulSoup - from frappe.website.render import render_page + from frappe.website.serve import get_response from frappe.utils import set_request frappe.set_user('Guest') frappe.local.no_cache = True try: set_request(method='GET', path=route) - content = render_page(route) + content = get_response(route) soup = BeautifulSoup(content, 'html.parser') page_content = soup.find(class_='page_content') text_content = page_content.text if page_content else '' diff --git a/frappe/website/doctype/web_form/test_web_form.py b/frappe/website/doctype/web_form/test_web_form.py index 78f7fd6337..0e9ce7c660 100644 --- a/frappe/website/doctype/web_form/test_web_form.py +++ b/frappe/website/doctype/web_form/test_web_form.py @@ -5,7 +5,6 @@ from __future__ import unicode_literals import frappe import unittest, json -from frappe.website.render import build_page from frappe.website.doctype.web_form.web_form import accept test_dependencies = ['Web Form'] diff --git a/frappe/website/render.py b/frappe/website/render.py index 895faa5b5f..77c1af1e87 100644 --- a/frappe/website/render.py +++ b/frappe/website/render.py @@ -1,23 +1,21 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals -import frappe -from frappe import _ -import frappe.sessions -from frappe.utils import cstr -import mimetypes, json +import json +import mimetypes import re from six import iteritems -from werkzeug.wrappers import Response from werkzeug.routing import Rule -from werkzeug.wsgi import wrap_file +from werkzeug.wrappers import Response +import frappe +import frappe.sessions +from frappe import _ from frappe.website.context import get_context -from frappe.website.utils import (get_home_page, can_cache, delete_page_cache, - get_toc, get_next_link) from frappe.website.router import evaluate_dynamic_routes +from frappe.website.utils import (can_cache, get_home_page, get_next_link, get_toc) + class PageNotFoundError(Exception): pass @@ -61,38 +59,6 @@ def add_preload_headers(response): traceback.print_exc() -def render_page(path): - """get page html""" - out = None - - if can_cache(): - # return rendered page - page_cache = frappe.cache().hget("website_page", path) - if page_cache and frappe.local.lang in page_cache: - out = page_cache[frappe.local.lang] - - if out: - frappe.local.response.from_cache = True - return out - - return build(path) - -def build(path): - if not frappe.db: - frappe.connect() - - try: - return build_page(path) - except frappe.DoesNotExistError: - hooks = frappe.get_hooks() - if hooks.website_catch_all: - path = hooks.website_catch_all[0] - return build_page(path) - else: - raise - except Exception: - raise - def build_page(path): if not getattr(frappe.local, "path", None): frappe.local.path = path diff --git a/frappe/website/router.py b/frappe/website/router.py index b048fa55c0..2e27b4c690 100644 --- a/frappe/website/router.py +++ b/frappe/website/router.py @@ -1,16 +1,16 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import io import os import re +from werkzeug.routing import Map, NotFound, Rule + import frappe from frappe.model.document import get_controller from frappe.website.utils import can_cache, extract_comment_tag, extract_title -from werkzeug.routing import Map, Rule, NotFound + def resolve_route(path): """Returns the page route object based on searching in pages and generators. From 080a5b3c6a1395fe085d6d7d7218449139353da9 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 14 May 2021 21:33:39 +0530 Subject: [PATCH 044/164] chore: Delete an unnecessary file - purifycss is no longer used. --- frappe/website/purifycss.py | 43 ------------------------------------- 1 file changed, 43 deletions(-) delete mode 100644 frappe/website/purifycss.py diff --git a/frappe/website/purifycss.py b/frappe/website/purifycss.py deleted file mode 100644 index 39e989db6e..0000000000 --- a/frappe/website/purifycss.py +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import print_function, unicode_literals -''' -Check for unused CSS Classes - -sUpdate source and target apps below and run from CLI - - bench --site [sitename] execute frappe.website.purifycss.purify.css - -''' - -import frappe, re, os - -source = frappe.get_app_path('frappe_theme', 'public', 'less', 'frappe_theme.less') -target_apps = ['erpnext_com', 'frappe_io', 'translator', 'chart_of_accounts_builder', 'frappe_theme'] - -def purifycss(): - with open(source, 'r') as f: - src = f.read() - - classes = [] - for line in src.splitlines(): - line = line.strip() - if not line: - continue - if line[0]=='@': - continue - classes.extend(re.findall('\.([^0-9][^ :&.{,(]*)', line)) - - classes = list(set(classes)) - - for app in target_apps: - for basepath, folders, files in os.walk(frappe.get_app_path(app)): - for fname in files: - if fname.endswith('.html') or fname.endswith('.md'): - #print 'checking {0}...'.format(fname) - with open(os.path.join(basepath, fname), 'r') as f: - src = f.read() - for c in classes: - if c in src: - classes.remove(c) - - for c in sorted(classes): - print(c) From c964217106538a8950961cb2a1df4445c3fb0291 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 14 May 2021 21:40:44 +0530 Subject: [PATCH 045/164] style: Fix import issues --- frappe/website/render.py | 4 ---- frappe/website/utils.py | 1 - 2 files changed, 5 deletions(-) diff --git a/frappe/website/render.py b/frappe/website/render.py index 77c1af1e87..87eddf3463 100644 --- a/frappe/website/render.py +++ b/frappe/website/render.py @@ -11,15 +11,11 @@ from werkzeug.wrappers import Response import frappe import frappe.sessions -from frappe import _ from frappe.website.context import get_context from frappe.website.router import evaluate_dynamic_routes from frappe.website.utils import (can_cache, get_home_page, get_next_link, get_toc) -class PageNotFoundError(Exception): pass - - def build_response(path, data, http_status_code, headers=None): # build response response = Response() diff --git a/frappe/website/utils.py b/frappe/website/utils.py index 87e47571fb..a5fe9c3986 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -6,7 +6,6 @@ import functools import re import os import frappe -import re import yaml from six import iteritems From c5b981524868141f2dee58fa7fc5c24e6584bc70 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 14 May 2021 23:43:36 +0530 Subject: [PATCH 046/164] fix: Add get_response_content to get page content --- frappe/__init__.py | 4 ++-- frappe/search/website_search.py | 4 ++-- frappe/utils/global_search.py | 8 ++++---- frappe/website/serve.py | 4 ++++ 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index bd46f0f874..a90fd24d7b 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -1485,7 +1485,7 @@ def get_print(doctype=None, name=None, print_format=None, style=None, :param style: Print Format style. :param as_pdf: Return as PDF. Default False. :param password: Password to encrypt the pdf with. Default None""" - from frappe.website.serve import get_response + from frappe.website.serve import get_response_content from frappe.utils.pdf import get_pdf local.form_dict.doctype = doctype @@ -1500,7 +1500,7 @@ def get_print(doctype=None, name=None, print_format=None, style=None, options = {'password': password} if not html: - html = get_response("printview") + html = get_response_content("printview") if as_pdf: return get_pdf(html, output = output, options = options) diff --git a/frappe/search/website_search.py b/frappe/search/website_search.py index ad2ebb504d..49bdade936 100644 --- a/frappe/search/website_search.py +++ b/frappe/search/website_search.py @@ -9,7 +9,7 @@ from whoosh.fields import ID, TEXT, Schema import frappe from frappe.search.full_text_search import FullTextSearch from frappe.utils import set_request, update_progress_bar -from frappe.website.serve import get_response +from frappe.website.serve import get_response_content INDEX_NAME = "web_routes" @@ -61,7 +61,7 @@ class WebsiteSearch(FullTextSearch): try: set_request(method="GET", path=route) - content = get_response(route) + content = get_response_content(route) soup = BeautifulSoup(content, "html.parser") page_content = soup.find(class_="page_content") text_content = page_content.text if page_content else "" diff --git a/frappe/utils/global_search.py b/frappe/utils/global_search.py index 7541e8b31d..4c78d63918 100644 --- a/frappe/utils/global_search.py +++ b/frappe/utils/global_search.py @@ -310,14 +310,14 @@ def get_routes_to_index(): def add_route_to_global_search(route): from bs4 import BeautifulSoup - from frappe.website.serve import get_response + from frappe.website.serve import get_response_content from frappe.utils import set_request frappe.set_user('Guest') frappe.local.no_cache = True try: set_request(method='GET', path=route) - content = get_response(route) + content = get_response_content(route) soup = BeautifulSoup(content, 'html.parser') page_content = soup.find(class_='page_content') text_content = page_content.text if page_content else '' @@ -332,8 +332,8 @@ def add_route_to_global_search(route): route=route ) sync_value_in_queue(value) - except (frappe.PermissionError, frappe.DoesNotExistError, frappe.ValidationError, Exception): - pass + except (frappe.PermissionError, frappe.DoesNotExistError, frappe.ValidationError, Exception) as e: + raise e frappe.set_user('Administrator') diff --git a/frappe/website/serve.py b/frappe/website/serve.py index f11ba1c4fc..47ca0aa1d2 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -46,3 +46,7 @@ def get_response(path=None, http_status_code=200): response = ErrorPage(path, http_status_code, exception=e).get() return response + +def get_response_content(path=None, http_status_code=200): + response = get_response(path, http_status_code) + return response.data From 9b26307fe5e2210a6c36645e220d077c1e997090 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 17 May 2021 12:23:50 +0530 Subject: [PATCH 047/164] test: Use get_response_content to get content --- frappe/tests/test_recorder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/tests/test_recorder.py b/frappe/tests/test_recorder.py index 7dcccd1d71..8a1ae36ad9 100644 --- a/frappe/tests/test_recorder.py +++ b/frappe/tests/test_recorder.py @@ -8,7 +8,7 @@ import unittest import frappe import frappe.recorder from frappe.utils import set_request -from frappe.website.serve import get_response +from frappe.website.serve import get_response_content import sqlparse @@ -122,5 +122,5 @@ class TestRecorder(unittest.TestCase): self.assertEqual(call['exact_copies'], query[1]) def test_error_page_rendering(self): - content = get_response("error") + content = get_response_content("error") self.assertIn("Error", content) From d3d4b49796c551915b965a1d195787286fc16d90 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 17 May 2021 23:00:37 +0530 Subject: [PATCH 048/164] refactor: Simplify sidebar and breadcrumbs code --- frappe/website/context.py | 303 +++--------------- .../website/page_controllers/template_page.py | 56 ++-- 2 files changed, 83 insertions(+), 276 deletions(-) diff --git a/frappe/website/context.py b/frappe/website/context.py index ed5c89f149..c8dade5b8b 100644 --- a/frappe/website/context.py +++ b/frappe/website/context.py @@ -1,206 +1,28 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt +import json +import os -from __future__ import unicode_literals -import frappe, os, json - -from frappe.website.doctype.website_settings.website_settings import get_website_settings -from frappe.website.router import get_page_context +import frappe from frappe.model.document import Document -def get_context(path, args=None): - if args and args.source: - context = args - else: - context = get_page_context(path) - if args: - context.update(args) - - if hasattr(frappe.local, 'request'): - # for (remove leading slash) - # path could be overriden in render.resolve_from_map - context["path"] = frappe.local.request.path.strip('/ ') - else: - context["path"] = path - - context.canonical = frappe.utils.get_url(frappe.utils.escape_html(context.path)) - context.route = context.path - context = build_context(context) - - # set using frappe.respond_as_web_page - if hasattr(frappe.local, 'response') and frappe.local.response.get('context'): - context.update(frappe.local.response.context) - - # to be able to inspect the context dict - # Use the macro "inspect" from macros.html - context._context_dict = context - - context.developer_mode = frappe.conf.developer_mode - - return context - -def update_controller_context(context, controller): - module = frappe.get_module(controller) - - if module: - # get config fields - for prop in ("base_template_path", "template", "no_cache", "sitemap", - "condition_field"): - if hasattr(module, prop): - context[prop] = getattr(module, prop) - - if hasattr(module, "get_context"): - import inspect - try: - if inspect.getfullargspec(module.get_context).args: - ret = module.get_context(context) - else: - ret = module.get_context() - if ret: - context.update(ret) - except (frappe.PermissionError, frappe.PageDoesNotExistError, frappe.Redirect): - raise - except: - if not any([frappe.flags.in_migrate, frappe.flags.in_website_search_build]): - frappe.errprint(frappe.utils.get_traceback()) - - if hasattr(module, "get_children"): - context.children = module.get_children(context) - - -def build_context(context): - """get_context method of doc or module is supposed to render - content templates and push it into context""" - context = frappe._dict(context) - - if not "url_prefix" in context: - context.url_prefix = "" - - if context.url_prefix and context.url_prefix[-1]!='/': - context.url_prefix += '/' - - # for backward compatibility - context.docs_base_url = '/docs' - - context.update(get_website_settings(context)) - context.update(frappe.local.conf.get("website_context") or {}) - - # provide doc - if context.doc: - context.update(context.doc.as_dict()) - context.update(context.doc.get_website_properties()) - - if not context.template: - context.template = context.doc.meta.get_web_template() - - if hasattr(context.doc, "get_context"): - ret = context.doc.get_context(context) - - if ret: - context.update(ret) - - for prop in ("no_cache", "sitemap"): - if not prop in context: - context[prop] = getattr(context.doc, prop, False) - - elif context.controller: - # controller based context - update_controller_context(context, context.controller) - - # controller context extensions - context_controller_hooks = frappe.get_hooks("extend_website_page_controller_context") or {} - for controller, extension in context_controller_hooks.items(): - if isinstance(extension, list): - for ext in extension: - if controller == context.controller: - update_controller_context(context, ext) - else: - update_controller_context(context, extension) - - add_metatags(context) - add_sidebar_and_breadcrumbs(context) - - # determine templates to be used - if not context.base_template_path: - app_base = frappe.get_hooks("base_template") - context.base_template_path = app_base[-1] if app_base else "templates/base.html" - - if context.title_prefix and context.title and not context.title.startswith(context.title_prefix): - context.title = '{0} - {1}'.format(context.title_prefix, context.title) - - # apply context from hooks - update_website_context = frappe.get_hooks('update_website_context') - for method in update_website_context: - values = frappe.get_attr(method)(context) - if values: - context.update(values) - - return context - -def load_sidebar(context, sidebar_json_path): - with open(sidebar_json_path, 'r') as sidebarfile: - try: - sidebar_json = sidebarfile.read() - context.sidebar_items = json.loads(sidebar_json) - context.show_sidebar = 1 - except json.decoder.JSONDecodeError: - frappe.throw('Invalid Sidebar JSON at ' + sidebar_json_path) - -def get_sidebar_json_path(path, look_for=False): - ''' - Get _sidebar.json path from directory path - - :param path: path of the current diretory - :param look_for: if True, look for _sidebar.json going upwards from given path - - :return: _sidebar.json path - ''' - if os.path.split(path)[1] == 'www' or path == '/' or not path: - return '' - - sidebar_json_path = os.path.join(path, '_sidebar.json') - if os.path.exists(sidebar_json_path): - return sidebar_json_path - else: - if look_for: - return get_sidebar_json_path(os.path.split(path)[0], look_for) - else: - return '' - -def add_sidebar_and_breadcrumbs(context): - '''Add sidebar and breadcrumbs to context''' - from frappe.website.router import get_page_info_from_template - if context.show_sidebar: - context.no_cache = 1 - add_sidebar_data(context) - else: - if context.basepath: - hooks = frappe.get_hooks('look_for_sidebar_json') - look_for_sidebar_json = hooks[0] if hooks else 0 - sidebar_json_path = get_sidebar_json_path( - context.basepath, - look_for_sidebar_json - ) - if sidebar_json_path: - load_sidebar(context, sidebar_json_path) - - if context.add_breadcrumbs and not context.parents: - if context.basepath: - parent_path = os.path.dirname(context.path).rstrip('/') - page_info = get_page_info_from_template(parent_path) - if page_info: - context.parents = [dict(route=parent_path, title=page_info.title)] - -def add_sidebar_data(context): - from frappe.utils.user import get_fullname_and_avatar +def get_sidebar_data(parent_sidebar, basepath): import frappe.www.list + sidebar_data = frappe._dict() - if context.show_sidebar and context.website_sidebar: - context.sidebar_items = frappe.get_all('Website Sidebar Item', - filters=dict(parent=context.website_sidebar), fields=['title', 'route', '`group`'], + hooks = frappe.get_hooks('look_for_sidebar_json') + look_for_sidebar_json = hooks[0] if hooks else 0 + + if basepath and look_for_sidebar_json: + sidebar_items = get_sidebar_items_from_sidebar_file(basepath, look_for_sidebar_json) + sidebar_data['sidebar_items'] = sidebar_items + + if not sidebar_data.sidebar_items and parent_sidebar: + sidebar_data.sidebar_items = frappe.get_all('Website Sidebar Item', + filters=dict(parent=parent_sidebar), fields=['title', 'route', '`group`'], order_by='idx asc') - if not context.sidebar_items: + if not sidebar_data.sidebar_items: sidebar_items = frappe.cache().hget('portal_menu_items', frappe.session.user) if sidebar_items == None: sidebar_items = [] @@ -224,74 +46,41 @@ def add_sidebar_data(context): add_items(sidebar_items, items_via_hooks) frappe.cache().hset('portal_menu_items', frappe.session.user, sidebar_items) + sidebar_data.sidebar_items = sidebar_items - context.sidebar_items = sidebar_items + return sidebar_data - info = get_fullname_and_avatar(frappe.session.user) - context["fullname"] = info.fullname - context["user_image"] = info.avatar - context["user"] = info.name +def get_sidebar_items_from_sidebar_file(basepath, look_for_sidebar_json): + sidebar_items = frappe._dict() + sidebar_json_path = get_sidebar_json_path(basepath, look_for_sidebar_json) + if not sidebar_json_path: return sidebar_items + with open(sidebar_json_path, 'r') as sidebarfile: + try: + sidebar_json = sidebarfile.read() + sidebar_items = json.loads(sidebar_json) + except json.decoder.JSONDecodeError: + frappe.throw('Invalid Sidebar JSON at ' + sidebar_json_path) -def add_metatags(context): - tags = frappe._dict(context.get("metatags") or {}) + return sidebar_items - if "og:type" not in tags: - tags["og:type"] = "article" +def get_sidebar_json_path(path, look_for=False): + ''' + Get _sidebar.json path from directory path - if "title" not in tags and context.title: - tags["title"] = context.title + :param path: path of the current diretory + :param look_for: if True, look for _sidebar.json going upwards from given path - title = tags.get("name") or tags.get("title") - if title: - tags["og:title"] = tags["twitter:title"] = title - tags["twitter:card"] = "summary" + :return: _sidebar.json path + ''' + if os.path.split(path)[1] == 'www' or path == '/' or not path: + return '' - if not tags.get('description') and context.description: - tags["description"] = context.description - - description = tags.get("description") - if description: - tags["og:description"] = tags["twitter:description"] = description - - if "image" not in tags and context.image: - tags["image"] = context.image - - image = tags.get("image") - if image: - tags["og:image"] = tags["twitter:image"] = tags["image"] = frappe.utils.get_url(image) - tags['twitter:card'] = "summary_large_image" - - if "author" not in tags and context.author: - tags["author"] = context.author - - tags["og:url"] = tags["url"] = frappe.utils.get_url(context.path) - - if "published_on" not in tags and context.published_on: - tags["published_on"] = context.published_on - - if "published_on" in tags: - tags["datePublished"] = tags["published_on"] - del tags["published_on"] - - tags["language"] = frappe.local.lang or "en" - - # Get meta tags from Website Route meta - # they can override the defaults set above - route = context.path - if route == '': - # homepage - route = frappe.db.get_single_value('Website Settings', 'home_page') - - route_exists = (route - and not route.endswith(('.js', '.css')) - and frappe.db.exists('Website Route Meta', route)) - - if route_exists: - website_route_meta = frappe.get_doc('Website Route Meta', route) - for meta_tag in website_route_meta.meta_tags: - d = meta_tag.get_meta_dict() - tags.update(d) - - # update tags in context - context.metatags = tags + sidebar_json_path = os.path.join(path, '_sidebar.json') + if os.path.exists(sidebar_json_path): + return sidebar_json_path + else: + if look_for: + return get_sidebar_json_path(os.path.split(path)[0], look_for) + else: + return '' diff --git a/frappe/website/page_controllers/template_page.py b/frappe/website/page_controllers/template_page.py index f2664fb928..5b001a55e7 100644 --- a/frappe/website/page_controllers/template_page.py +++ b/frappe/website/page_controllers/template_page.py @@ -3,7 +3,7 @@ import os import frappe from frappe.website.page_controllers.base_template_page import BaseTemplatePage -from frappe.website.context import add_sidebar_and_breadcrumbs +from frappe.website.context import get_sidebar_data from frappe.website.render import build_response from frappe.website.router import get_base_template from frappe.website.utils import (extract_comment_tag, @@ -12,29 +12,32 @@ from frappe.website.utils import (extract_comment_tag, WEBPAGE_PY_MODULE_PROPERTIES = ("base_template_path", "template", "no_cache", "sitemap", "condition_field") class TemplatePage(BaseTemplatePage): - def validate(self): - for app in frappe.get_installed_apps(frappe_last=True): - if self.find_page_in_app(app): - return True + def __init__(self, path, http_status_code=None): + super().__init__(path=path, http_status_code=http_status_code) + self.set_template_path() - def find_page_in_app(self, app): + def set_template_path(self): ''' Searches for file matching the path in the /www - and /templates/pages folders + and /templates/pages folders and sets path if match is found ''' - app_path = frappe.get_app_path(app) folders = get_start_folders() + for app in frappe.get_installed_apps(frappe_last=True): + app_path = frappe.get_app_path(app) - for dirname in folders: - search_path = os.path.join(app_path, dirname, self.path) - for p in self.get_index_path_options(search_path): - file_path = frappe.as_unicode(p) - if os.path.exists(file_path) and not os.path.isdir(file_path): - self.app = app - self.app_path = app_path - self.template_path = os.path.relpath(file_path, self.app_path) - self.basepath = os.path.dirname(file_path) - return True + for dirname in folders: + search_path = os.path.join(app_path, dirname, self.path) + for p in self.get_index_path_options(search_path): + file_path = frappe.as_unicode(p) + if os.path.exists(file_path) and not os.path.isdir(file_path): + self.app = app + self.app_path = app_path + self.template_path = os.path.relpath(file_path, self.app_path) + self.basepath = os.path.dirname(file_path) + return + + def validate(self): + return hasattr(self, 'template_path') and bool(self.template_path) def get_index_path_options(self, search_path): return (f'{search_path}{d}' for d in ('', '.html', '.md', '/index.html', '/index.md')) @@ -59,12 +62,20 @@ class TemplatePage(BaseTemplatePage): return html def post_process_context(self): + self.set_user_info() self.add_sidebar_and_breadcrumbs() self.set_missing_values() super(TemplatePage, self).post_process_context() def add_sidebar_and_breadcrumbs(self): - add_sidebar_and_breadcrumbs(self.context) + if self.basepath: + sidebar_data = get_sidebar_data(self.context.website_sidebar, self.basepath) or None + self.context.sidebar_items = sidebar_data.sidebar_items + + if self.context.add_breadcrumbs and not self.context.parents: + # TODO: set correct title and route for breadcrumbs + parent_path = os.path.dirname(self.path) + self.context.parents = [dict(route=parent_path, title=extract_title(source='', path=parent_path))] def set_pymodule(self): ''' @@ -229,6 +240,13 @@ class TemplatePage(BaseTemplatePage): # for backward compatibility self.context.docs_base_url = '/docs' + def set_user_info(self): + from frappe.utils.user import get_fullname_and_avatar + info = get_fullname_and_avatar(frappe.session.user) + self.context["fullname"] = info.fullname + self.context["user_image"] = info.avatar + self.context["user"] = info.name + def get_start_folders(): return frappe.local.flags.web_pages_folders or ('www', 'templates/pages') From 19879943102d669ad94b82a9ba89177f2430d8d2 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 17 May 2021 23:37:28 +0530 Subject: [PATCH 049/164] refactor: Move unwanted code from validate - Also, removed unwanted code in render.py --- frappe/website/page_controllers/error_page.py | 5 ++- frappe/website/page_controllers/list_page.py | 11 ++++--- .../page_controllers/not_found_page.py | 8 +++-- .../page_controllers/not_permitted_page.py | 9 +++-- frappe/website/page_controllers/print_page.py | 14 +++++--- .../website/page_controllers/static_page.py | 32 ++++++++++-------- frappe/website/render.py | 33 +------------------ 7 files changed, 50 insertions(+), 62 deletions(-) diff --git a/frappe/website/page_controllers/error_page.py b/frappe/website/page_controllers/error_page.py index 1f54a5f38c..554949684f 100644 --- a/frappe/website/page_controllers/error_page.py +++ b/frappe/website/page_controllers/error_page.py @@ -1,7 +1,10 @@ from frappe.website.page_controllers.template_page import TemplatePage class ErrorPage(TemplatePage): - def __init__(self, path, http_status_code, exception): + def __init__(self, path=None, http_status_code=None, exception=None): path = 'error' super().__init__(path=path, http_status_code=http_status_code) self.http_status_code = getattr(exception, 'http_status_code', None) or http_status_code or 500 + + def validate(self): + return True diff --git a/frappe/website/page_controllers/list_page.py b/frappe/website/page_controllers/list_page.py index 2cb6ed5f26..cc6f150dd2 100644 --- a/frappe/website/page_controllers/list_page.py +++ b/frappe/website/page_controllers/list_page.py @@ -3,8 +3,9 @@ from frappe.website.page_controllers.template_page import TemplatePage class ListPage(TemplatePage): def validate(self): - if frappe.db.get_value('DocType', self.path): - frappe.local.form_dict.doctype = self.path - self.set_standard_path('list') - return True - return False + return frappe.db.exists('DocType', self.path, True) + + def render(self): + frappe.local.form_dict.doctype = self.path + self.set_standard_path('list') + return super().render() diff --git a/frappe/website/page_controllers/not_found_page.py b/frappe/website/page_controllers/not_found_page.py index 1d15877e9d..aebc0006ae 100644 --- a/frappe/website/page_controllers/not_found_page.py +++ b/frappe/website/page_controllers/not_found_page.py @@ -2,7 +2,9 @@ from frappe.website.page_controllers.template_page import TemplatePage class NotFoundPage(TemplatePage): def __init__(self, path, http_status_code): + path = '404' + http_status_code = 404 super().__init__(path=path, http_status_code=http_status_code) - self.path = '404' - self.http_status_code = 404 - self.template_path = '404' + + def validate(self): + return True diff --git a/frappe/website/page_controllers/not_permitted_page.py b/frappe/website/page_controllers/not_permitted_page.py index 61c6b88e09..3ed732d80b 100644 --- a/frappe/website/page_controllers/not_permitted_page.py +++ b/frappe/website/page_controllers/not_permitted_page.py @@ -1,13 +1,18 @@ import frappe from frappe import _ from frappe.website.page_controllers.template_page import TemplatePage +from frappe.utils import cstr class NotPermittedPage(TemplatePage): - def __init__(self, path, http_status_code): + def __init__(self, path=None, http_status_code=None, exception=''): + frappe.local.message = cstr(exception) super().__init__(path=path, http_status_code=http_status_code) self.http_status_code = 403 def validate(self): + return True + + def render(self): frappe.local.message_title = _("Not Permitted") frappe.local.response['context'] = dict( indicator_color = 'red', @@ -16,4 +21,4 @@ class NotPermittedPage(TemplatePage): fullpage=True ) self.set_standard_path('message') - return True + return super().render() diff --git a/frappe/website/page_controllers/print_page.py b/frappe/website/page_controllers/print_page.py index 6daea7fcbc..574f335f5d 100644 --- a/frappe/website/page_controllers/print_page.py +++ b/frappe/website/page_controllers/print_page.py @@ -9,11 +9,15 @@ class PrintPage(TemplatePage): def validate(self): parts = self.path.split('/', 1) if len(parts)==2: - if (frappe.db.get_value('DocType', parts[0]) - and frappe.db.get_value(parts[0], parts[1])): - frappe.form_dict.doctype = parts[0] - frappe.form_dict.name = parts[1] - self.set_standard_path('printview') + if (frappe.db.exists('DocType', parts[0], True) + and frappe.db.exists(parts[0], parts[1], True)): return True return False + + def render(self): + parts = self.path.split('/', 1) + frappe.form_dict.doctype = parts[0] + frappe.form_dict.name = parts[1] + self.set_standard_path('printview') + return super().render() diff --git a/frappe/website/page_controllers/static_page.py b/frappe/website/page_controllers/static_page.py index 8f474e71be..8c07440ebb 100644 --- a/frappe/website/page_controllers/static_page.py +++ b/frappe/website/page_controllers/static_page.py @@ -7,27 +7,31 @@ from werkzeug.wsgi import wrap_file import frappe from frappe.website.page_controllers.web_page import WebPage +UNSUPPORTED_STATIC_PAGE_TYPES = ('html', 'md', 'js', 'xml', 'css', 'txt', 'py', 'json') class StaticPage(WebPage): - def validate(self): - if ('.' not in self.path): - return False - extension = self.path.rsplit('.', 1)[-1] - if extension in ('html', 'md', 'js', 'xml', 'css', 'txt', 'py', 'json'): - return False + def __init__(self, path, http_status_code=None): + super().__init__(path=path, http_status_code=http_status_code) + self.set_file_path() - if self.find_path_in_apps(): - return True - - return False - - def find_path_in_apps(self): + def set_file_path(self): + self.file_path = '' + if not self.is_valid_file_path(): return for app in frappe.get_installed_apps(): file_path = frappe.get_app_path(app, 'www') + '/' + self.path if os.path.exists(file_path): self.file_path = file_path - return True - return False + + def validate(self): + return self.is_valid_file_path() and self.file_path + + def is_valid_file_path(self): + if ('.' not in self.path): + return False + extension = self.path.rsplit('.', 1)[-1] + if extension in UNSUPPORTED_STATIC_PAGE_TYPES: + return False + return True def render(self): f = open(self.file_path, 'rb') diff --git a/frappe/website/render.py b/frappe/website/render.py index 87eddf3463..6a1d5a404e 100644 --- a/frappe/website/render.py +++ b/frappe/website/render.py @@ -11,9 +11,8 @@ from werkzeug.wrappers import Response import frappe import frappe.sessions -from frappe.website.context import get_context from frappe.website.router import evaluate_dynamic_routes -from frappe.website.utils import (can_cache, get_home_page, get_next_link, get_toc) +from frappe.website.utils import get_home_page def build_response(path, data, http_status_code, headers=None): @@ -54,36 +53,6 @@ def add_preload_headers(response): import traceback traceback.print_exc() - -def build_page(path): - if not getattr(frappe.local, "path", None): - frappe.local.path = path - - context = get_context(path) - - if context.source: - html = frappe.render_template(context.source, context) - elif context.template: - if path.endswith('min.js'): - html = frappe.get_jloader().get_source(frappe.get_jenv(), context.template)[0] - else: - html = frappe.get_template(context.template).render(context) - - if '{index}' in html: - html = html.replace('{index}', get_toc(context.route)) - - if '{next}' in html: - html = html.replace('{next}', get_next_link(context.route)) - - # html = frappe.get_template(context.base_template_path).render(context) - - if can_cache(context.no_cache): - page_cache = frappe.cache().hget("website_page", path) or {} - page_cache[frappe.local.lang] = html - frappe.cache().hset("website_page", path, page_cache) - - return html - def resolve_path(path): if not path: path = "index" From 5318697c0fd74d7fd534e1e645fdd6e34464cb08 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 18 May 2021 08:40:37 +0530 Subject: [PATCH 050/164] feat: Add redirect page --- frappe/website/page_controllers/redirect_page.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 frappe/website/page_controllers/redirect_page.py diff --git a/frappe/website/page_controllers/redirect_page.py b/frappe/website/page_controllers/redirect_page.py new file mode 100644 index 0000000000..f3a0ba6e7f --- /dev/null +++ b/frappe/website/page_controllers/redirect_page.py @@ -0,0 +1,16 @@ +import frappe +from frappe.website.render import build_response + +class RedirectPage(object): + def __init__(self, path, http_status_code=301): + self.path = path + self.http_status_code = http_status_code + + def validate(self): + return True + + def render(self): + return build_response(self.path, "", 301, { + "Location": frappe.flags.redirect_location or (frappe.local.response or {}).get('location'), + "Cache-Control": "no-store, no-cache, must-revalidate" + }) From a02e5f766a70258ab05e741afc64263f06970137 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 18 May 2021 08:43:49 +0530 Subject: [PATCH 051/164] refactor: Add path_resolver to separate route resolving part --- frappe/website/path_resolver.py | 67 +++++++++++++++++++++++++++++++++ frappe/website/serve.py | 54 +++++++++++--------------- 2 files changed, 88 insertions(+), 33 deletions(-) create mode 100644 frappe/website/path_resolver.py diff --git a/frappe/website/path_resolver.py b/frappe/website/path_resolver.py new file mode 100644 index 0000000000..d6e43f4f1f --- /dev/null +++ b/frappe/website/path_resolver.py @@ -0,0 +1,67 @@ +from frappe.website.redirect import resolve_redirect +from frappe.website.render import resolve_path +import frappe + +from frappe.website.page_controllers.document_page import DocumentPage +from frappe.website.page_controllers.list_page import ListPage +from frappe.website.page_controllers.not_found_page import NotFoundPage +from frappe.website.page_controllers.print_page import PrintPage +from frappe.website.page_controllers.template_page import TemplatePage +from frappe.website.page_controllers.static_page import StaticPage +from frappe.website.page_controllers.web_form import WebFormPage + +class PathResolver(): + def __init__(self, path, http_status_code=None): + self._path = path + self.http_status_code = http_status_code + # self.url_map = get_url_map() + + @property + def path(self): + return self._path.strip('/ ') + + def resolve(self): + '''Returns endpoint and a renderer instance that can render the endpoint''' + query_string = frappe.local.request.query_string + resolve_redirect(self.path, query_string) + endpoint = resolve_path(self.path) + # urls = self.url_map.bind_to_environ(frappe.local.request.environ) + + renderers = (StaticPage, WebFormPage, TemplatePage, ListPage, DocumentPage, PrintPage, NotFoundPage) + + for renderer in renderers: + renderer_instance = renderer(endpoint, self.http_status_code) + can_render = renderer_instance.validate() + if can_render: + return endpoint, renderer_instance + + return endpoint, None + +# #> Path > Path > resolve using url_map > path.endpoint +# # +# # +# # +# # pathclass url_map + +# def get_url_map(): +# Map([Rule('/', endpoint=get_home_page, defaults={'renderer': TemplatePage})]) + + +# query_string = None +# response = None + +# if not path: +# path = frappe.local.request.path +# query_string = frappe.local.request.query_string + +# try: +# path = path.strip('/ ') +# resolve_redirect(path, query_string) +# path = resolve_path(path) +# # there is no way to determine the type of the page based on the route +# # so evaluate each type of page sequentially +# renderers = [StaticPage, WebFormPage, TemplatePage, ListPage, DocumentPage, PrintPage, NotFoundPage] +# for renderer in renderers: +# response = renderer(path, http_status_code).get() +# if response: +# break diff --git a/frappe/website/serve.py b/frappe/website/serve.py index 47ca0aa1d2..bdec8a16ba 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -1,49 +1,37 @@ import frappe -from frappe.utils import cstr -from frappe.website.page_controllers.document_page import DocumentPage from frappe.website.page_controllers.error_page import ErrorPage -from frappe.website.page_controllers.list_page import ListPage -from frappe.website.page_controllers.not_found_page import NotFoundPage from frappe.website.page_controllers.not_permitted_page import NotPermittedPage -from frappe.website.page_controllers.print_page import PrintPage -from frappe.website.page_controllers.template_page import TemplatePage -from frappe.website.page_controllers.static_page import StaticPage -from frappe.website.page_controllers.web_form import WebFormPage - -from frappe.website.redirect import resolve_redirect -from frappe.website.render import build_response, resolve_path +from frappe.website.page_controllers.redirect_page import RedirectPage +from frappe.website.path_resolver import PathResolver +from frappe.website.utils import can_cache def get_response(path=None, http_status_code=200): - """render html page""" - query_string = None + """Resolves path and renders page""" response = None + path = path or frappe.local.request.path + endpoint = path + # if can_cache(): + # # return rendered page + # page_cache = frappe.cache().hget("website_page", path) + # if page_cache and frappe.local.lang in page_cache: + # out = page_cache[frappe.local.lang] - if not path: - path = frappe.local.request.path - query_string = frappe.local.request.query_string + # if out: + # frappe.local.response.from_cache = True + # return out try: - path = path.strip('/ ') - resolve_redirect(path, query_string) - path = resolve_path(path) - # there is no way to determine the type of the page based on the route - # so evaluate each type of page sequentially - renderers = [StaticPage, WebFormPage, TemplatePage, ListPage, DocumentPage, PrintPage, NotFoundPage] - for renderer in renderers: - response = renderer(path, http_status_code).get() - if response: - break + path_resolver = PathResolver(path, http_status_code) + endpoint, renderer_instance = path_resolver.resolve() + if renderer_instance: + response = renderer_instance.render() except frappe.Redirect: - return build_response(path, "", 301, { - "Location": frappe.flags.redirect_location or (frappe.local.response or {}).get('location'), - "Cache-Control": "no-store, no-cache, must-revalidate" - }) + return RedirectPage(endpoint or path, http_status_code).render() except frappe.PermissionError as e: - frappe.local.message = cstr(e) - response = NotPermittedPage(path, http_status_code).get() + response = NotPermittedPage(endpoint, http_status_code, exception=e).render() except Exception as e: - response = ErrorPage(path, http_status_code, exception=e).get() + response = ErrorPage(exception=e).render() return response From c8d588819f940b89e8a48a5190ecf22a188b5ece Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 18 May 2021 09:05:02 +0530 Subject: [PATCH 052/164] refactor: Move get_sidebar_data method to utils - Also, rename get_sidebar_data to get_sidebar_items & delete context.py - Remove commented text --- frappe/website/context.py | 86 ---------------- .../website/page_controllers/template_page.py | 5 +- frappe/website/path_resolver.py | 39 +------- frappe/website/serve.py | 4 +- frappe/website/utils.py | 97 +++++++++++++++++-- 5 files changed, 99 insertions(+), 132 deletions(-) delete mode 100644 frappe/website/context.py diff --git a/frappe/website/context.py b/frappe/website/context.py deleted file mode 100644 index c8dade5b8b..0000000000 --- a/frappe/website/context.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt -import json -import os - -import frappe -from frappe.model.document import Document - -def get_sidebar_data(parent_sidebar, basepath): - import frappe.www.list - sidebar_data = frappe._dict() - - hooks = frappe.get_hooks('look_for_sidebar_json') - look_for_sidebar_json = hooks[0] if hooks else 0 - - if basepath and look_for_sidebar_json: - sidebar_items = get_sidebar_items_from_sidebar_file(basepath, look_for_sidebar_json) - sidebar_data['sidebar_items'] = sidebar_items - - if not sidebar_data.sidebar_items and parent_sidebar: - sidebar_data.sidebar_items = frappe.get_all('Website Sidebar Item', - filters=dict(parent=parent_sidebar), fields=['title', 'route', '`group`'], - order_by='idx asc') - - if not sidebar_data.sidebar_items: - sidebar_items = frappe.cache().hget('portal_menu_items', frappe.session.user) - if sidebar_items == None: - sidebar_items = [] - roles = frappe.get_roles() - portal_settings = frappe.get_doc('Portal Settings', 'Portal Settings') - - def add_items(sidebar_items, items): - for d in items: - if d.get('enabled') and ((not d.get('role')) or d.get('role') in roles): - sidebar_items.append(d.as_dict() if isinstance(d, Document) else d) - - if not portal_settings.hide_standard_menu: - add_items(sidebar_items, portal_settings.get('menu')) - - if portal_settings.custom_menu: - add_items(sidebar_items, portal_settings.get('custom_menu')) - - items_via_hooks = frappe.get_hooks('portal_menu_items') - if items_via_hooks: - for i in items_via_hooks: i['enabled'] = 1 - add_items(sidebar_items, items_via_hooks) - - frappe.cache().hset('portal_menu_items', frappe.session.user, sidebar_items) - sidebar_data.sidebar_items = sidebar_items - - return sidebar_data - -def get_sidebar_items_from_sidebar_file(basepath, look_for_sidebar_json): - sidebar_items = frappe._dict() - sidebar_json_path = get_sidebar_json_path(basepath, look_for_sidebar_json) - if not sidebar_json_path: return sidebar_items - - with open(sidebar_json_path, 'r') as sidebarfile: - try: - sidebar_json = sidebarfile.read() - sidebar_items = json.loads(sidebar_json) - except json.decoder.JSONDecodeError: - frappe.throw('Invalid Sidebar JSON at ' + sidebar_json_path) - - return sidebar_items - -def get_sidebar_json_path(path, look_for=False): - ''' - Get _sidebar.json path from directory path - - :param path: path of the current diretory - :param look_for: if True, look for _sidebar.json going upwards from given path - - :return: _sidebar.json path - ''' - if os.path.split(path)[1] == 'www' or path == '/' or not path: - return '' - - sidebar_json_path = os.path.join(path, '_sidebar.json') - if os.path.exists(sidebar_json_path): - return sidebar_json_path - else: - if look_for: - return get_sidebar_json_path(os.path.split(path)[0], look_for) - else: - return '' diff --git a/frappe/website/page_controllers/template_page.py b/frappe/website/page_controllers/template_page.py index 5b001a55e7..12f459afd7 100644 --- a/frappe/website/page_controllers/template_page.py +++ b/frappe/website/page_controllers/template_page.py @@ -3,7 +3,7 @@ import os import frappe from frappe.website.page_controllers.base_template_page import BaseTemplatePage -from frappe.website.context import get_sidebar_data +from frappe.website.utils import get_sidebar_items from frappe.website.render import build_response from frappe.website.router import get_base_template from frappe.website.utils import (extract_comment_tag, @@ -69,8 +69,7 @@ class TemplatePage(BaseTemplatePage): def add_sidebar_and_breadcrumbs(self): if self.basepath: - sidebar_data = get_sidebar_data(self.context.website_sidebar, self.basepath) or None - self.context.sidebar_items = sidebar_data.sidebar_items + self.context.sidebar_items = get_sidebar_items(self.context.website_sidebar, self.basepath) if self.context.add_breadcrumbs and not self.context.parents: # TODO: set correct title and route for breadcrumbs diff --git a/frappe/website/path_resolver.py b/frappe/website/path_resolver.py index d6e43f4f1f..b9f8972380 100644 --- a/frappe/website/path_resolver.py +++ b/frappe/website/path_resolver.py @@ -1,20 +1,19 @@ -from frappe.website.redirect import resolve_redirect -from frappe.website.render import resolve_path import frappe - from frappe.website.page_controllers.document_page import DocumentPage from frappe.website.page_controllers.list_page import ListPage from frappe.website.page_controllers.not_found_page import NotFoundPage from frappe.website.page_controllers.print_page import PrintPage -from frappe.website.page_controllers.template_page import TemplatePage from frappe.website.page_controllers.static_page import StaticPage +from frappe.website.page_controllers.template_page import TemplatePage from frappe.website.page_controllers.web_form import WebFormPage +from frappe.website.redirect import resolve_redirect +from frappe.website.render import resolve_path + class PathResolver(): def __init__(self, path, http_status_code=None): self._path = path self.http_status_code = http_status_code - # self.url_map = get_url_map() @property def path(self): @@ -25,7 +24,6 @@ class PathResolver(): query_string = frappe.local.request.query_string resolve_redirect(self.path, query_string) endpoint = resolve_path(self.path) - # urls = self.url_map.bind_to_environ(frappe.local.request.environ) renderers = (StaticPage, WebFormPage, TemplatePage, ListPage, DocumentPage, PrintPage, NotFoundPage) @@ -36,32 +34,3 @@ class PathResolver(): return endpoint, renderer_instance return endpoint, None - -# #> Path > Path > resolve using url_map > path.endpoint -# # -# # -# # -# # pathclass url_map - -# def get_url_map(): -# Map([Rule('/', endpoint=get_home_page, defaults={'renderer': TemplatePage})]) - - -# query_string = None -# response = None - -# if not path: -# path = frappe.local.request.path -# query_string = frappe.local.request.query_string - -# try: -# path = path.strip('/ ') -# resolve_redirect(path, query_string) -# path = resolve_path(path) -# # there is no way to determine the type of the page based on the route -# # so evaluate each type of page sequentially -# renderers = [StaticPage, WebFormPage, TemplatePage, ListPage, DocumentPage, PrintPage, NotFoundPage] -# for renderer in renderers: -# response = renderer(path, http_status_code).get() -# if response: -# break diff --git a/frappe/website/serve.py b/frappe/website/serve.py index bdec8a16ba..9b28fd2e3b 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -1,10 +1,10 @@ import frappe - from frappe.website.page_controllers.error_page import ErrorPage from frappe.website.page_controllers.not_permitted_page import NotPermittedPage from frappe.website.page_controllers.redirect_page import RedirectPage from frappe.website.path_resolver import PathResolver -from frappe.website.utils import can_cache + +#from frappe.website.utils import can_cache def get_response(path=None, http_status_code=200): """Resolves path and renders page""" diff --git a/frappe/website/utils.py b/frappe/website/utils.py index a5fe9c3986..723d878fac 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -2,16 +2,21 @@ # MIT License. See license.txt from __future__ import unicode_literals -import functools -import re -import os -import frappe -import yaml -from six import iteritems +import functools +import json +import os +import re + +import yaml from past.builtins import cmp +from six import iteritems + +import frappe +from frappe.model.document import Document from frappe.utils import md_to_html + def delete_page_cache(path): cache = frappe.cache() cache.delete_value('full_index') @@ -439,3 +444,83 @@ def get_frontmatter(string): "attributes": yaml.safe_load(frontmatter), "body": body, } + +def get_sidebar_items(parent_sidebar, basepath): + import frappe.www.list + sidebar_items = [] + + hooks = frappe.get_hooks('look_for_sidebar_json') + look_for_sidebar_json = hooks[0] if hooks else 0 + + if basepath and look_for_sidebar_json: + sidebar_items = get_sidebar_items_from_sidebar_file(basepath, look_for_sidebar_json) + + if not sidebar_items and parent_sidebar: + sidebar_items = frappe.get_all('Website Sidebar Item', + filters=dict(parent=parent_sidebar), fields=['title', 'route', '`group`'], + order_by='idx asc') + + if not sidebar_items: + sidebar_items = get_portal_sidebar_items() + + return sidebar_items + + +def get_portal_sidebar_items(): + sidebar_items = frappe.cache().hget('portal_menu_items', frappe.session.user) + if sidebar_items == None: + sidebar_items = [] + roles = frappe.get_roles() + portal_settings = frappe.get_doc('Portal Settings', 'Portal Settings') + + def add_items(sidebar_items, items): + for d in items: + if d.get('enabled') and ((not d.get('role')) or d.get('role') in roles): + sidebar_items.append(d.as_dict() if isinstance(d, Document) else d) + + if not portal_settings.hide_standard_menu: + add_items(sidebar_items, portal_settings.get('menu')) + + if portal_settings.custom_menu: + add_items(sidebar_items, portal_settings.get('custom_menu')) + + items_via_hooks = frappe.get_hooks('portal_menu_items') + if items_via_hooks: + for i in items_via_hooks: i['enabled'] = 1 + add_items(sidebar_items, items_via_hooks) + + frappe.cache().hset('portal_menu_items', frappe.session.user, sidebar_items) + + return sidebar_items + +def get_sidebar_items_from_sidebar_file(basepath, look_for_sidebar_json): + sidebar_items = frappe._dict() + sidebar_json_path = get_sidebar_json_path(basepath, look_for_sidebar_json) + if not sidebar_json_path: return sidebar_items + + with open(sidebar_json_path, 'r') as sidebarfile: + try: + sidebar_json = sidebarfile.read() + sidebar_items = json.loads(sidebar_json) + except json.decoder.JSONDecodeError: + frappe.throw('Invalid Sidebar JSON at ' + sidebar_json_path) + + return sidebar_items + +def get_sidebar_json_path(path, look_for=False): + '''Get _sidebar.json path from directory path + :param path: path of the current diretory + :param look_for: if True, look for _sidebar.json going upwards from given path + :return: _sidebar.json path + ''' + if os.path.split(path)[1] == 'www' or path == '/' or not path: + return '' + + sidebar_json_path = os.path.join(path, '_sidebar.json') + if os.path.exists(sidebar_json_path): + return sidebar_json_path + else: + if look_for: + return get_sidebar_json_path(os.path.split(path)[0], look_for) + else: + return '' From d6a539362d1a3401f549f6788450116004d6b986 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 18 May 2021 09:13:13 +0530 Subject: [PATCH 053/164] style: Fix formatting issue --- frappe/website/page_controllers/static_page.py | 3 ++- frappe/website/utils.py | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/frappe/website/page_controllers/static_page.py b/frappe/website/page_controllers/static_page.py index 8c07440ebb..767cc872d1 100644 --- a/frappe/website/page_controllers/static_page.py +++ b/frappe/website/page_controllers/static_page.py @@ -16,7 +16,8 @@ class StaticPage(WebPage): def set_file_path(self): self.file_path = '' - if not self.is_valid_file_path(): return + if not self.is_valid_file_path(): + return for app in frappe.get_installed_apps(): file_path = frappe.get_app_path(app, 'www') + '/' + self.path if os.path.exists(file_path): diff --git a/frappe/website/utils.py b/frappe/website/utils.py index 723d878fac..2d16658efb 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -468,7 +468,7 @@ def get_sidebar_items(parent_sidebar, basepath): def get_portal_sidebar_items(): sidebar_items = frappe.cache().hget('portal_menu_items', frappe.session.user) - if sidebar_items == None: + if sidebar_items is None: sidebar_items = [] roles = frappe.get_roles() portal_settings = frappe.get_doc('Portal Settings', 'Portal Settings') @@ -486,7 +486,8 @@ def get_portal_sidebar_items(): items_via_hooks = frappe.get_hooks('portal_menu_items') if items_via_hooks: - for i in items_via_hooks: i['enabled'] = 1 + for i in items_via_hooks: + i['enabled'] = 1 add_items(sidebar_items, items_via_hooks) frappe.cache().hset('portal_menu_items', frappe.session.user, sidebar_items) @@ -494,9 +495,10 @@ def get_portal_sidebar_items(): return sidebar_items def get_sidebar_items_from_sidebar_file(basepath, look_for_sidebar_json): - sidebar_items = frappe._dict() + sidebar_items = [] sidebar_json_path = get_sidebar_json_path(basepath, look_for_sidebar_json) - if not sidebar_json_path: return sidebar_items + if not sidebar_json_path: + return sidebar_items with open(sidebar_json_path, 'r') as sidebarfile: try: From 0c575f02eae7c66861a12b9c264d8ae05c11e760 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 18 May 2021 11:43:42 +0530 Subject: [PATCH 054/164] fix: Handle encoding issue and some edge cases --- frappe/website/page_controllers/template_page.py | 2 +- frappe/website/path_resolver.py | 2 +- frappe/website/serve.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/website/page_controllers/template_page.py b/frappe/website/page_controllers/template_page.py index 12f459afd7..9f3bf66701 100644 --- a/frappe/website/page_controllers/template_page.py +++ b/frappe/website/page_controllers/template_page.py @@ -234,7 +234,7 @@ class TemplatePage(BaseTemplatePage): self.context.url_prefix += '/' self.context.path = self.path - self.context.pathname = frappe.local.path + self.context.pathname = frappe.local.path if hasattr(frappe, 'local') else self.path # for backward compatibility self.context.docs_base_url = '/docs' diff --git a/frappe/website/path_resolver.py b/frappe/website/path_resolver.py index b9f8972380..1368932792 100644 --- a/frappe/website/path_resolver.py +++ b/frappe/website/path_resolver.py @@ -21,7 +21,7 @@ class PathResolver(): def resolve(self): '''Returns endpoint and a renderer instance that can render the endpoint''' - query_string = frappe.local.request.query_string + query_string = frappe.local.request.query_string if hasattr(frappe.local, 'request') else None resolve_redirect(self.path, query_string) endpoint = resolve_path(self.path) diff --git a/frappe/website/serve.py b/frappe/website/serve.py index 9b28fd2e3b..7a93ada825 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -37,4 +37,4 @@ def get_response(path=None, http_status_code=200): def get_response_content(path=None, http_status_code=200): response = get_response(path, http_status_code) - return response.data + return str(response.data, 'utf-8') From c217b32fa91c1a8e252a343bcb21f9c7283ca2e5 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 18 May 2021 15:41:23 +0530 Subject: [PATCH 055/164] fix: Add cache_html decorator to cache HTML --- .../website/page_controllers/template_page.py | 5 +++-- frappe/website/serve.py | 10 --------- frappe/website/utils.py | 22 +++++++++++++++++++ 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/frappe/website/page_controllers/template_page.py b/frappe/website/page_controllers/template_page.py index 9f3bf66701..635b8a56fe 100644 --- a/frappe/website/page_controllers/template_page.py +++ b/frappe/website/page_controllers/template_page.py @@ -6,8 +6,8 @@ from frappe.website.page_controllers.base_template_page import BaseTemplatePage from frappe.website.utils import get_sidebar_items from frappe.website.render import build_response from frappe.website.router import get_base_template -from frappe.website.utils import (extract_comment_tag, - extract_title, get_next_link, get_toc, get_frontmatter) +from frappe.website.utils import (extract_comment_tag, extract_title, + get_next_link, get_toc, get_frontmatter, cache_html) WEBPAGE_PY_MODULE_PROPERTIES = ("base_template_path", "template", "no_cache", "sitemap", "condition_field") @@ -45,6 +45,7 @@ class TemplatePage(BaseTemplatePage): def render(self): return build_response(self.path, self.get_html(), self.http_status_code, self.headers) + @cache_html def get_html(self): # context object should be separate from self for security # because it will be accessed via the user defined template diff --git a/frappe/website/serve.py b/frappe/website/serve.py index 7a93ada825..7010b768d8 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -4,22 +4,12 @@ from frappe.website.page_controllers.not_permitted_page import NotPermittedPage from frappe.website.page_controllers.redirect_page import RedirectPage from frappe.website.path_resolver import PathResolver -#from frappe.website.utils import can_cache def get_response(path=None, http_status_code=200): """Resolves path and renders page""" response = None path = path or frappe.local.request.path endpoint = path - # if can_cache(): - # # return rendered page - # page_cache = frappe.cache().hget("website_page", path) - # if page_cache and frappe.local.lang in page_cache: - # out = page_cache[frappe.local.lang] - - # if out: - # frappe.local.response.from_cache = True - # return out try: path_resolver = PathResolver(path, http_status_code) diff --git a/frappe/website/utils.py b/frappe/website/utils.py index 2d16658efb..576d1d911c 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -7,6 +7,7 @@ import functools import json import os import re +from functools import wraps import yaml from past.builtins import cmp @@ -526,3 +527,24 @@ def get_sidebar_json_path(path, look_for=False): return get_sidebar_json_path(os.path.split(path)[0], look_for) else: return '' + +def cache_html(func): + @wraps(func) + def cache_html_decorator(*args, **kwargs): + if can_cache(): + html = None + page_cache = frappe.cache().hget("website_page", args[0].path) + if page_cache and frappe.local.lang in page_cache: + html = page_cache[frappe.local.lang] + if html: + frappe.local.response.from_cache = True + return html + html = func(*args, **kwargs) + if can_cache(): + page_cache = frappe.cache().hget("website_page", args[0].path) or {} + page_cache[frappe.local.lang] = html + frappe.cache().hset("website_page", args[0].path, page_cache) + + return html + + return cache_html_decorator From 6375818c10d73e9ae02ba7ee6e1821e3b4143857 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 18 May 2021 21:57:20 +0530 Subject: [PATCH 056/164] fix: 404 caching --- .../page_controllers/not_found_page.py | 20 +++++++++++++++++++ frappe/website/serve.py | 6 ++++++ frappe/website/utils.py | 3 ++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/frappe/website/page_controllers/not_found_page.py b/frappe/website/page_controllers/not_found_page.py index aebc0006ae..1f6bbd4b02 100644 --- a/frappe/website/page_controllers/not_found_page.py +++ b/frappe/website/page_controllers/not_found_page.py @@ -1,10 +1,30 @@ +import frappe from frappe.website.page_controllers.template_page import TemplatePage +from frappe.website.utils import can_cache +from urllib.parse import urlparse + +HOMEPAGE_PATHS = ('/', '/index', '', 'index') class NotFoundPage(TemplatePage): def __init__(self, path, http_status_code): + self.resolved_path = path path = '404' http_status_code = 404 super().__init__(path=path, http_status_code=http_status_code) def validate(self): return True + + def render(self): + if can_cache_404(self.resolved_path): + frappe.cache().hset('website_404', frappe.request.url, True) + return super().render() + +def can_cache_404(path): + # do not cache 404 for custom homepages + return can_cache() and not is_custom_home_page(path) + +def is_custom_home_page(path): + url = frappe.request.url + url_parts = urlparse(url) + return url_parts.path in HOMEPAGE_PATHS and path not in HOMEPAGE_PATHS diff --git a/frappe/website/serve.py b/frappe/website/serve.py index 7010b768d8..56fed22469 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -1,8 +1,10 @@ import frappe from frappe.website.page_controllers.error_page import ErrorPage +from frappe.website.page_controllers.not_found_page import NotFoundPage from frappe.website.page_controllers.not_permitted_page import NotPermittedPage from frappe.website.page_controllers.redirect_page import RedirectPage from frappe.website.path_resolver import PathResolver +from frappe.website.utils import can_cache def get_response(path=None, http_status_code=200): @@ -11,6 +13,10 @@ def get_response(path=None, http_status_code=200): path = path or frappe.local.request.path endpoint = path + if can_cache() and frappe.cache().hget('website_404', frappe.request.url): + response = NotFoundPage(path=path).render() + return response + try: path_resolver = PathResolver(path, http_status_code) endpoint, renderer_instance = path_resolver.resolve() diff --git a/frappe/website/utils.py b/frappe/website/utils.py index 576d1d911c..be18466714 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -540,7 +540,8 @@ def cache_html(func): frappe.local.response.from_cache = True return html html = func(*args, **kwargs) - if can_cache(): + context = args[0].context + if can_cache(context.no_cache): page_cache = frappe.cache().hget("website_page", args[0].path) or {} page_cache[frappe.local.lang] = html frappe.cache().hset("website_page", args[0].path, page_cache) From 48b9198ebd717fe3e3ac1ae1598c78dc04e91266 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 18 May 2021 23:34:30 +0530 Subject: [PATCH 057/164] test: Add test case to validate base_template_path --- frappe/website/doctype/web_page/test_web_page.py | 9 ++++++++- frappe/www/_test/_test_folder/_test_page.html | 3 +++ frappe/www/_test/_test_folder/_test_page.py | 2 ++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 frappe/www/_test/_test_folder/_test_page.html create mode 100644 frappe/www/_test/_test_folder/_test_page.py diff --git a/frappe/website/doctype/web_page/test_web_page.py b/frappe/website/doctype/web_page/test_web_page.py index daaa41f5a8..2c43acc226 100644 --- a/frappe/website/doctype/web_page/test_web_page.py +++ b/frappe/website/doctype/web_page/test_web_page.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import unittest import frappe from frappe.website.router import resolve_route -from frappe.website.serve import get_response +from frappe.website.serve import get_response, get_response_content from frappe.utils import set_request test_records = frappe.get_test_records('Web Page') @@ -73,4 +73,11 @@ class TestWebPage(unittest.TestCase): finally: web_page.delete() + def test_custom_base_template_path(self): + content = get_response_content('/_test/_test_folder/_test_page') + # assert the text in base template is rendered + self.assertTrue('

This is for testing

' in frappe.as_unicode(content)) + + # assert template block rendered + self.assertTrue('

Test content

' in frappe.as_unicode(content)) diff --git a/frappe/www/_test/_test_folder/_test_page.html b/frappe/www/_test/_test_folder/_test_page.html new file mode 100644 index 0000000000..7364235e10 --- /dev/null +++ b/frappe/www/_test/_test_folder/_test_page.html @@ -0,0 +1,3 @@ +{% block content %} +

Test content

+{% endblock %} diff --git a/frappe/www/_test/_test_folder/_test_page.py b/frappe/www/_test/_test_folder/_test_page.py new file mode 100644 index 0000000000..fa7b5f5727 --- /dev/null +++ b/frappe/www/_test/_test_folder/_test_page.py @@ -0,0 +1,2 @@ +def get_context(context): + context.base_template_path = 'frappe/templates/test/_test_base.html' From 1be1c25e587c98b62a6134496dac19cd9f4af88e Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 19 May 2021 04:46:19 +0530 Subject: [PATCH 058/164] fix: 404 caching - Move 404 resolving code to path resolver --- .../page_controllers/not_found_page.py | 28 +++++++++++-------- frappe/website/path_resolver.py | 12 ++++++-- frappe/website/serve.py | 6 ---- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/frappe/website/page_controllers/not_found_page.py b/frappe/website/page_controllers/not_found_page.py index 1f6bbd4b02..13c6358512 100644 --- a/frappe/website/page_controllers/not_found_page.py +++ b/frappe/website/page_controllers/not_found_page.py @@ -1,13 +1,16 @@ +import os +from urllib.parse import urlparse + import frappe from frappe.website.page_controllers.template_page import TemplatePage from frappe.website.utils import can_cache -from urllib.parse import urlparse -HOMEPAGE_PATHS = ('/', '/index', '', 'index') +HOMEPAGE_PATHS = ('/', '/index', 'index') class NotFoundPage(TemplatePage): def __init__(self, path, http_status_code): - self.resolved_path = path + self.request_path = path + self.request_url = frappe.local.request.url if hasattr(frappe.local, 'request') else '' path = '404' http_status_code = 404 super().__init__(path=path, http_status_code=http_status_code) @@ -16,15 +19,16 @@ class NotFoundPage(TemplatePage): return True def render(self): - if can_cache_404(self.resolved_path): - frappe.cache().hset('website_404', frappe.request.url, True) + if self.can_cache_404(): + frappe.cache().hset('website_404', self.request_url, True) return super().render() -def can_cache_404(path): - # do not cache 404 for custom homepages - return can_cache() and not is_custom_home_page(path) + def can_cache_404(self): + # do not cache 404 for custom homepages + return can_cache() and self.request_url and not self.is_custom_home_page() -def is_custom_home_page(path): - url = frappe.request.url - url_parts = urlparse(url) - return url_parts.path in HOMEPAGE_PATHS and path not in HOMEPAGE_PATHS + def is_custom_home_page(self): + url_parts = urlparse(self.request_url) + request_url = os.path.splitext(url_parts.path)[0] + request_path = os.path.splitext(self.request_path)[0] + return request_url in HOMEPAGE_PATHS and request_path not in HOMEPAGE_PATHS diff --git a/frappe/website/path_resolver.py b/frappe/website/path_resolver.py index 1368932792..1a813fac60 100644 --- a/frappe/website/path_resolver.py +++ b/frappe/website/path_resolver.py @@ -8,6 +8,7 @@ from frappe.website.page_controllers.template_page import TemplatePage from frappe.website.page_controllers.web_form import WebFormPage from frappe.website.redirect import resolve_redirect from frappe.website.render import resolve_path +from frappe.website.utils import can_cache class PathResolver(): @@ -21,8 +22,15 @@ class PathResolver(): def resolve(self): '''Returns endpoint and a renderer instance that can render the endpoint''' - query_string = frappe.local.request.query_string if hasattr(frappe.local, 'request') else None - resolve_redirect(self.path, query_string) + 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) + + resolve_redirect(self.path, request.query_string) endpoint = resolve_path(self.path) renderers = (StaticPage, WebFormPage, TemplatePage, ListPage, DocumentPage, PrintPage, NotFoundPage) diff --git a/frappe/website/serve.py b/frappe/website/serve.py index 56fed22469..7010b768d8 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -1,10 +1,8 @@ import frappe from frappe.website.page_controllers.error_page import ErrorPage -from frappe.website.page_controllers.not_found_page import NotFoundPage from frappe.website.page_controllers.not_permitted_page import NotPermittedPage from frappe.website.page_controllers.redirect_page import RedirectPage from frappe.website.path_resolver import PathResolver -from frappe.website.utils import can_cache def get_response(path=None, http_status_code=200): @@ -13,10 +11,6 @@ def get_response(path=None, http_status_code=200): path = path or frappe.local.request.path endpoint = path - if can_cache() and frappe.cache().hget('website_404', frappe.request.url): - response = NotFoundPage(path=path).render() - return response - try: path_resolver = PathResolver(path, http_status_code) endpoint, renderer_instance = path_resolver.resolve() From b193666c71a32550ce61a2a9a7bc722ef1b2a2fd Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 19 May 2021 11:34:04 +0530 Subject: [PATCH 059/164] fix: Do not set content type for paths ending with .com To avoid unnecessary download --- frappe/website/render.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/website/render.py b/frappe/website/render.py index 6a1d5a404e..c14d8e0d48 100644 --- a/frappe/website/render.py +++ b/frappe/website/render.py @@ -103,7 +103,9 @@ def set_content_type(response, data, path): response.mimetype = 'text/html' response.charset = 'utf-8' - if "." in path: + # ignore paths ending with .com to avoid unnecessary download + # https://bugs.python.org/issue22347 + if "." in path and not path.endswith('.com'): content_type, encoding = mimetypes.guess_type(path) if content_type: response.mimetype = content_type From 0b37e0dbd7a4f82d0562685723757282304611f2 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 19 May 2021 13:55:45 +0530 Subject: [PATCH 060/164] fix: Check if resolved file_path in StaticPage is a file --- frappe/website/page_controllers/static_page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/page_controllers/static_page.py b/frappe/website/page_controllers/static_page.py index 767cc872d1..b292a62922 100644 --- a/frappe/website/page_controllers/static_page.py +++ b/frappe/website/page_controllers/static_page.py @@ -20,7 +20,7 @@ class StaticPage(WebPage): return for app in frappe.get_installed_apps(): file_path = frappe.get_app_path(app, 'www') + '/' + self.path - if os.path.exists(file_path): + if os.path.isfile(file_path): self.file_path = file_path def validate(self): From 68aa1805aaca41476ea4d47cba9d88ab7cf17c18 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 19 May 2021 14:03:31 +0530 Subject: [PATCH 061/164] fix: Exception handling --- frappe/utils/global_search.py | 4 +-- .../website/doctype/web_page/test_web_page.py | 31 ++++++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/frappe/utils/global_search.py b/frappe/utils/global_search.py index 4c78d63918..a7d484a7ff 100644 --- a/frappe/utils/global_search.py +++ b/frappe/utils/global_search.py @@ -332,8 +332,8 @@ def add_route_to_global_search(route): route=route ) sync_value_in_queue(value) - except (frappe.PermissionError, frappe.DoesNotExistError, frappe.ValidationError, Exception) as e: - raise e + except Exception: + pass frappe.set_user('Administrator') diff --git a/frappe/website/doctype/web_page/test_web_page.py b/frappe/website/doctype/web_page/test_web_page.py index 2c43acc226..b40b63b0d5 100644 --- a/frappe/website/doctype/web_page/test_web_page.py +++ b/frappe/website/doctype/web_page/test_web_page.py @@ -63,7 +63,7 @@ class TestWebPage(unittest.TestCase): dynamic_route = 1, route = '/doctype-view/', content_type = 'HTML', - dymamic_template = 1, + dynamic_template = 1, main_section_html = '
{{ frappe.form_dict.doctype }}
' )).insert() @@ -81,3 +81,32 @@ class TestWebPage(unittest.TestCase): # assert template block rendered self.assertTrue('

Test content

' in frappe.as_unicode(content)) + def test_home_page(self): + content = get_response_content(path='/') + print(content) + content = get_response_content(path='/index') + print(content) + + def test_table_of_content(self): + page = get_response(path='/_test/_test_folder/_test_toc') + print(page) + + def test_sidebar_data(self): + pass + + def test_meta_tags(self): + pass + + def test_breadcrumbs(self): + pass + + def test_downloadable_file(self): + pass + + + + +# breadcrumb +# validate +# cache no-cache +# page context caching From c26c5d9ef027312ba695c962fe0427172a8db27d Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 19 May 2021 19:04:16 +0530 Subject: [PATCH 062/164] feat: Add is_valid_path method to PathResolver --- .../website/doctype/web_page/test_web_page.py | 11 ++++---- frappe/website/path_resolver.py | 26 +++++++++++-------- frappe/website/serve.py | 2 +- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/frappe/website/doctype/web_page/test_web_page.py b/frappe/website/doctype/web_page/test_web_page.py index b40b63b0d5..108b36caaa 100644 --- a/frappe/website/doctype/web_page/test_web_page.py +++ b/frappe/website/doctype/web_page/test_web_page.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals import unittest import frappe -from frappe.website.router import resolve_route +from frappe.website.path_resolver import PathResolver from frappe.website.serve import get_response, get_response_content from frappe.utils import set_request @@ -18,10 +18,11 @@ class TestWebPage(unittest.TestCase): for t in test_records: frappe.get_doc(t).insert() - def test_check_sitemap(self): - resolve_route("test-web-page-1") - resolve_route("test-web-page-1/test-web-page-2") - resolve_route("test-web-page-1/test-web-page-3") + def test_path_resolver(self): + self.assertTrue(PathResolver("test-web-page-1").is_valid_path()) + self.assertTrue(PathResolver("test-web-page-1/test-web-page-2").is_valid_path()) + self.assertTrue(PathResolver("test-web-page-1/test-web-page-3").is_valid_path()) + self.assertFalse(PathResolver("test-web-page-1/test-web-page-Random").is_valid_path()) def test_base_template(self): content = get_page_content('/_test/_test_custom_base.html') diff --git a/frappe/website/path_resolver.py b/frappe/website/path_resolver.py index 1a813fac60..b6f7dbe9f9 100644 --- a/frappe/website/path_resolver.py +++ b/frappe/website/path_resolver.py @@ -6,19 +6,15 @@ from frappe.website.page_controllers.print_page import PrintPage from frappe.website.page_controllers.static_page import StaticPage from frappe.website.page_controllers.template_page import TemplatePage from frappe.website.page_controllers.web_form import WebFormPage +from frappe.website.page_controllers.redirect_page import RedirectPage from frappe.website.redirect import resolve_redirect from frappe.website.render import resolve_path from frappe.website.utils import can_cache class PathResolver(): - def __init__(self, path, http_status_code=None): - self._path = path - self.http_status_code = http_status_code - - @property - def path(self): - return self._path.strip('/ ') + def __init__(self, path): + self.path = path.strip('/ ') def resolve(self): '''Returns endpoint and a renderer instance that can render the endpoint''' @@ -30,15 +26,23 @@ class PathResolver(): if request.url and can_cache() and frappe.cache().hget('website_404', request.url): return self.path, NotFoundPage(self.path) - resolve_redirect(self.path, request.query_string) - endpoint = resolve_path(self.path) + try: + resolve_redirect(self.path, request.query_string) + except frappe.Redirect: + return self.path, RedirectPage(self.path) + endpoint = resolve_path(self.path) renderers = (StaticPage, WebFormPage, TemplatePage, ListPage, DocumentPage, PrintPage, NotFoundPage) for renderer in renderers: - renderer_instance = renderer(endpoint, self.http_status_code) + renderer_instance = renderer(endpoint, 200) can_render = renderer_instance.validate() if can_render: return endpoint, renderer_instance - return endpoint, None + return endpoint, NotFoundPage(endpoint) + + def is_valid_path(self): + _endpoint, renderer_instance = self.resolve() + return not isinstance(renderer_instance, NotFoundPage) + diff --git a/frappe/website/serve.py b/frappe/website/serve.py index 7010b768d8..95a49cef1a 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -12,7 +12,7 @@ def get_response(path=None, http_status_code=200): endpoint = path try: - path_resolver = PathResolver(path, http_status_code) + path_resolver = PathResolver(path) endpoint, renderer_instance = path_resolver.resolve() if renderer_instance: response = renderer_instance.render() From adb4bc8e30bff16602556f8563d8a09759d58a87 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 19 May 2021 19:08:22 +0530 Subject: [PATCH 063/164] refactor: Delete unused code --- frappe/website/doctype/web_page/web_page.py | 42 +----- .../website_settings/website_settings.py | 13 +- frappe/website/router.py | 125 +----------------- frappe/website/utils.py | 19 --- 4 files changed, 14 insertions(+), 185 deletions(-) diff --git a/frappe/website/doctype/web_page/web_page.py b/frappe/website/doctype/web_page/web_page.py index d3c7d19b9a..cc41e4e339 100644 --- a/frappe/website/doctype/web_page/web_page.py +++ b/frappe/website/doctype/web_page/web_page.py @@ -1,23 +1,19 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import print_function, unicode_literals - import re -import requests -import requests.exceptions from jinja2.exceptions import TemplateSyntaxError import frappe -from frappe.utils import get_datetime, now, strip_html, quoted +from frappe import _ +from frappe.utils import get_datetime, now, quoted, strip_html from frappe.utils.jinja import render_template -from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow -from frappe.website.router import resolve_route -from frappe.website.utils import (extract_title, find_first_image, get_comment_list, - get_html_content_based_on_type) -from frappe.website.website_generator import WebsiteGenerator from frappe.utils.safe_exec import safe_exec +from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow +from frappe.website.utils import (extract_title, find_first_image, + get_comment_list, get_html_content_based_on_type) +from frappe.website.website_generator import WebsiteGenerator class WebPage(WebsiteGenerator): @@ -184,32 +180,6 @@ def check_publish_status(): frappe.db.set_value("Web Page", page.name, "published", 1) - -def check_broken_links(): - cnt = 0 - for p in frappe.db.sql("select name, main_section from `tabWeb Page`", as_dict=True): - for link in re.findall('href=["\']([^"\']*)["\']', p.main_section): - if link.startswith("http"): - try: - res = requests.get(link) - except requests.exceptions.SSLError: - res = frappe._dict({"status_code": "SSL Error"}) - except requests.exceptions.ConnectionError: - res = frappe._dict({"status_code": "Connection Error"}) - - if res.status_code!=200: - print("[{0}] {1}: {2}".format(res.status_code, p.name, link)) - cnt += 1 - else: - link = link[1:] # remove leading / - link = link.split("#")[0] - - if not resolve_route(link): - print(p.name + ":" + link) - cnt += 1 - - print("{0} links broken".format(cnt)) - def get_web_blocks_html(blocks): '''Converts a list of blocks into Raw HTML and extracts out their scripts for deduplication''' diff --git a/frappe/website/doctype/website_settings/website_settings.py b/frappe/website/doctype/website_settings/website_settings.py index f7f22aa2df..ae8e6bd7e1 100644 --- a/frappe/website/doctype/website_settings/website_settings.py +++ b/frappe/website/doctype/website_settings/website_settings.py @@ -1,15 +1,13 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt +from six.moves.urllib.parse import quote -from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import get_request_site_address, encode -from frappe.model.document import Document -from six.moves.urllib.parse import quote -from frappe.website.router import resolve_route -from frappe.website.doctype.website_theme.website_theme import add_website_theme from frappe.integrations.doctype.google_settings.google_settings import get_auth_url +from frappe.model.document import Document +from frappe.utils import encode, get_request_site_address +from frappe.website.doctype.website_theme.website_theme import add_website_theme INDEXING_SCOPES = "https://www.googleapis.com/auth/indexing" @@ -23,7 +21,8 @@ class WebsiteSettings(Document): def validate_home_page(self): if frappe.flags.in_install: return - if self.home_page and not resolve_route(self.home_page): + from frappe.website.path_resolver import PathResolver + if self.home_page and not PathResolver(self.home_page).is_valid_path(): frappe.msgprint(_("Invalid Home Page") + " (Standard pages - index, login, products, blog, about, contact)") self.home_page = '' diff --git a/frappe/website/router.py b/frappe/website/router.py index 2e27b4c690..9922647ebf 100644 --- a/frappe/website/router.py +++ b/frappe/website/router.py @@ -5,130 +5,9 @@ import io import os import re -from werkzeug.routing import Map, NotFound, Rule - import frappe -from frappe.model.document import get_controller -from frappe.website.utils import can_cache, extract_comment_tag, extract_title - - -def resolve_route(path): - """Returns the page route object based on searching in pages and generators. - The `www` folder is also a part of generator **Web Page**. - - The only exceptions are `/about` and `/contact` these will be searched in Web Pages - first before checking the standard pages.""" - - if path not in ("about", "contact"): - context = get_page_info_from_template(path) - if context: - return context - return get_page_context_from_doctype(path) - else: - context = get_page_context_from_doctype(path) - if context: - return context - return get_page_info_from_template(path) - -def get_page_context(path): - page_context = None - if can_cache(): - page_context_cache = frappe.cache().hget("page_context", path) or {} - page_context = page_context_cache.get(frappe.local.lang, None) - - if not page_context: - page_context = make_page_context(path) - if can_cache(page_context.no_cache): - page_context_cache[frappe.local.lang] = page_context - frappe.cache().hset("page_context", path, page_context_cache) - - return page_context - -def make_page_context(path): - context = resolve_route(path) - if not context: - raise frappe.PageDoesNotExistError - - context.doctype = context.ref_doctype - - if context.page_title: - context.title = context.page_title - - context.pathname = frappe.local.path - - return context - -def get_page_info_from_template(path): - '''Return page_info from path''' - for app in frappe.get_installed_apps(frappe_last=True): - app_path = frappe.get_app_path(app) - - folders = get_start_folders() - - for start in folders: - search_path = os.path.join(app_path, start, path) - options = (search_path, search_path + '.html', search_path + '.md', - search_path + '/index.html', search_path + '/index.md') - for o in options: - option = frappe.as_unicode(o) - if os.path.exists(option) and not os.path.isdir(option): - return get_page_info(option, app, start, app_path=app_path) - - return None - -def get_page_context_from_doctype(path): - page_info = get_page_info_from_doctypes(path) - if not page_info: - page_info = get_page_info_from_web_page_with_dynamic_routes(path) - - if page_info: - return frappe.get_doc(page_info.get("doctype"), - page_info.get("name")).get_page_info() - -def get_all_page_context_from_doctypes(): - ''' - Get all doctype generated routes (for sitemap.xml) - ''' - routes = frappe.cache().get_value("website_generator_routes") - if not routes: - routes = get_page_info_from_doctypes() - frappe.cache().set_value("website_generator_routes", routes) - - return routes - -def get_page_info_from_doctypes(path=None): - ''' - Find a document with matching `route` from all doctypes with `has_web_view`=1 - ''' - routes = {} - for doctype in get_doctypes_with_web_view(): - filters = {} - controller = get_controller(doctype) - meta = frappe.get_meta(doctype) - - condition_field = (meta.is_published_field or - # custom doctypes dont have controllers and no website attribute - (controller.website.condition_field if not meta.custom else None)) - - if condition_field: - filters[condition_field] = 1 - - if path: - filters['route'] = path - - try: - for r in frappe.get_all(doctype, fields = ['name', 'route', 'modified'], - filters = filters, limit = 1): - - routes[r.route] = {"doctype": doctype, "name": r.name, "modified": r.modified} - - # just want one path, return it! - if path: - return routes[r.route] - except Exception as e: - if not frappe.db.is_missing_column(e): raise e - - return routes +from frappe.website.utils import extract_comment_tag, extract_title +from werkzeug.routing import Map, Rule, NotFound def get_page_info_from_web_page_with_dynamic_routes(path): ''' diff --git a/frappe/website/utils.py b/frappe/website/utils.py index be18466714..4f19267a7d 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -371,25 +371,6 @@ def extract_comment_tag(source, tag): return None -def add_missing_headers(): - '''Walk and add missing headers in docs (to be called from bench execute)''' - path = frappe.get_app_path('erpnext', 'docs') - for basepath, folders, files in os.walk(path): - for fname in files: - if fname.endswith('.md'): - with open(os.path.join(basepath, fname), 'r') as f: - content = frappe.as_unicode(f.read()) - - if not content.startswith('# ') and not '

' in content: - with open(os.path.join(basepath, fname), 'w') as f: - if fname=='index.md': - fname = os.path.basename(basepath) - else: - fname = fname[:-3] - h = fname.replace('_', ' ').replace('-', ' ').title() - content = '# {0}\n\n'.format(h) + content - f.write(content.encode('utf-8')) - def get_html_content_based_on_type(doc, fieldname, content_type): ''' Set content based on content_type From 132434cc9476254e2abc5df2b0408f90d2b9bb76 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 20 May 2021 01:23:54 +0530 Subject: [PATCH 064/164] refactor: Delete unused code --- frappe/website/doctype/web_page/web_page.py | 42 +------ .../website_settings/website_settings.py | 13 +-- frappe/website/utils.py | 104 +----------------- 3 files changed, 15 insertions(+), 144 deletions(-) diff --git a/frappe/website/doctype/web_page/web_page.py b/frappe/website/doctype/web_page/web_page.py index d3c7d19b9a..cc41e4e339 100644 --- a/frappe/website/doctype/web_page/web_page.py +++ b/frappe/website/doctype/web_page/web_page.py @@ -1,23 +1,19 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import print_function, unicode_literals - import re -import requests -import requests.exceptions from jinja2.exceptions import TemplateSyntaxError import frappe -from frappe.utils import get_datetime, now, strip_html, quoted +from frappe import _ +from frappe.utils import get_datetime, now, quoted, strip_html from frappe.utils.jinja import render_template -from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow -from frappe.website.router import resolve_route -from frappe.website.utils import (extract_title, find_first_image, get_comment_list, - get_html_content_based_on_type) -from frappe.website.website_generator import WebsiteGenerator from frappe.utils.safe_exec import safe_exec +from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow +from frappe.website.utils import (extract_title, find_first_image, + get_comment_list, get_html_content_based_on_type) +from frappe.website.website_generator import WebsiteGenerator class WebPage(WebsiteGenerator): @@ -184,32 +180,6 @@ def check_publish_status(): frappe.db.set_value("Web Page", page.name, "published", 1) - -def check_broken_links(): - cnt = 0 - for p in frappe.db.sql("select name, main_section from `tabWeb Page`", as_dict=True): - for link in re.findall('href=["\']([^"\']*)["\']', p.main_section): - if link.startswith("http"): - try: - res = requests.get(link) - except requests.exceptions.SSLError: - res = frappe._dict({"status_code": "SSL Error"}) - except requests.exceptions.ConnectionError: - res = frappe._dict({"status_code": "Connection Error"}) - - if res.status_code!=200: - print("[{0}] {1}: {2}".format(res.status_code, p.name, link)) - cnt += 1 - else: - link = link[1:] # remove leading / - link = link.split("#")[0] - - if not resolve_route(link): - print(p.name + ":" + link) - cnt += 1 - - print("{0} links broken".format(cnt)) - def get_web_blocks_html(blocks): '''Converts a list of blocks into Raw HTML and extracts out their scripts for deduplication''' diff --git a/frappe/website/doctype/website_settings/website_settings.py b/frappe/website/doctype/website_settings/website_settings.py index f7f22aa2df..ae8e6bd7e1 100644 --- a/frappe/website/doctype/website_settings/website_settings.py +++ b/frappe/website/doctype/website_settings/website_settings.py @@ -1,15 +1,13 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt +from six.moves.urllib.parse import quote -from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import get_request_site_address, encode -from frappe.model.document import Document -from six.moves.urllib.parse import quote -from frappe.website.router import resolve_route -from frappe.website.doctype.website_theme.website_theme import add_website_theme from frappe.integrations.doctype.google_settings.google_settings import get_auth_url +from frappe.model.document import Document +from frappe.utils import encode, get_request_site_address +from frappe.website.doctype.website_theme.website_theme import add_website_theme INDEXING_SCOPES = "https://www.googleapis.com/auth/indexing" @@ -23,7 +21,8 @@ class WebsiteSettings(Document): def validate_home_page(self): if frappe.flags.in_install: return - if self.home_page and not resolve_route(self.home_page): + from frappe.website.path_resolver import PathResolver + if self.home_page and not PathResolver(self.home_page).is_valid_path(): frappe.msgprint(_("Invalid Home Page") + " (Standard pages - index, login, products, blog, about, contact)") self.home_page = '' diff --git a/frappe/website/utils.py b/frappe/website/utils.py index be18466714..9e98b9da9c 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -1,8 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals - import functools import json import os @@ -156,91 +153,15 @@ def cleanup_page_name(title): name = title.lower() name = re.sub(r'[~!@#$%^&*+()<>,."\'\?]', '', name) name = re.sub('[:/]', '-', name) - name = '-'.join(name.split()) - # replace repeating hyphens name = re.sub(r"(-)\1+", r"\1", name) - return name[:140] -def get_shade(color, percent): - color, color_format = detect_color_format(color) - r, g, b, a = color - - avg = (float(int(r) + int(g) + int(b)) / 3) - # switch dark and light shades - if avg > 128: - percent = -percent - - # stronger diff for darker shades - if percent < 25 and avg < 64: - percent = percent * 2 - - new_color = [] - for channel_value in (r, g, b): - new_color.append(get_shade_for_channel(channel_value, percent)) - - r, g, b = new_color - - return format_color(r, g, b, a, color_format) - - -def detect_color_format(color): - if color.startswith("rgba"): - color_format = "rgba" - color = [c.strip() for c in color[5:-1].split(",")] - - elif color.startswith("rgb"): - color_format = "rgb" - color = [c.strip() for c in color[4:-1].split(",")] + [1] - - else: - # assume hex - color_format = "hex" - - if color.startswith("#"): - color = color[1:] - - if len(color) == 3: - # hex in short form like #fff - color = "{0}{0}{1}{1}{2}{2}".format(*tuple(color)) - - color = [int(color[0:2], 16), int(color[2:4], 16), int(color[4:6], 16), 1] - - return color, color_format - - -def get_shade_for_channel(channel_value, percent): - v = int(channel_value) + int(int('ff', 16) * (float(percent)/100)) - if v < 0: - v=0 - if v > 255: - v=255 - - return v - - -def format_color(r, g, b, a, color_format): - if color_format == "rgba": - return "rgba({0}, {1}, {2}, {3})".format(r, g, b, a) - - elif color_format == "rgb": - return "rgb({0}, {1}, {2})".format(r, g, b) - - else: - # assume hex - return "#{0}{1}{2}".format(convert_to_hex(r), convert_to_hex(g), convert_to_hex(b)) - - -def convert_to_hex(channel_value): - h = hex(channel_value)[2:] - - if len(h) < 2: - h = "0" + h - - return h +def get_shade(color, percent=None): + frappe.msgprint('get_shade method has been deprecated!') + return color def abs_url(path): """Deconstructs and Reconstructs a URL into an absolute URL or a URL relative from root '/'""" @@ -371,25 +292,6 @@ def extract_comment_tag(source, tag): return None -def add_missing_headers(): - '''Walk and add missing headers in docs (to be called from bench execute)''' - path = frappe.get_app_path('erpnext', 'docs') - for basepath, folders, files in os.walk(path): - for fname in files: - if fname.endswith('.md'): - with open(os.path.join(basepath, fname), 'r') as f: - content = frappe.as_unicode(f.read()) - - if not content.startswith('# ') and not '

' in content: - with open(os.path.join(basepath, fname), 'w') as f: - if fname=='index.md': - fname = os.path.basename(basepath) - else: - fname = fname[:-3] - h = fname.replace('_', ' ').replace('-', ' ').title() - content = '# {0}\n\n'.format(h) + content - f.write(content.encode('utf-8')) - def get_html_content_based_on_type(doc, fieldname, content_type): ''' Set content based on content_type From 862b5320b6931a8f31164f6e234f4e71af17aea6 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 20 May 2021 01:29:02 +0530 Subject: [PATCH 065/164] refactor: Move code to appropriate files - Delete unused resolve_route method - move resolve_path & resolve_redirect to path_resolver - Remove redirect.py --- frappe/core/doctype/user/user.py | 4 +- frappe/website/path_resolver.py | 93 ++++++++++++++++++++++- frappe/website/redirect.py | 45 ----------- frappe/website/render.py | 84 +++++---------------- frappe/website/router.py | 125 +------------------------------ frappe/website/utils.py | 12 +-- frappe/www/list.py | 2 +- 7 files changed, 117 insertions(+), 248 deletions(-) delete mode 100644 frappe/website/redirect.py diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index a4d13a57e0..e202711b07 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -17,7 +17,7 @@ from frappe.utils.password import update_password as _update_password, check_pas from frappe.desk.notifications import clear_notifications from frappe.desk.doctype.notification_settings.notification_settings import create_notification_settings, toggle_notifications from frappe.utils.user import get_system_managers -from frappe.website.utils import is_signup_enabled +from frappe.website.utils import is_signup_disabled from frappe.rate_limiter import rate_limit from frappe.utils.background_jobs import enqueue from frappe.core.doctype.user_type.user_type import user_linked_with_permission_on_doctype @@ -843,7 +843,7 @@ def verify_password(password): @frappe.whitelist(allow_guest=True) def sign_up(email, full_name, redirect_to): - if not is_signup_enabled(): + if is_signup_disabled(): frappe.throw(_('Sign Up is disabled'), title='Not Allowed') user = frappe.db.get("User", {"email": email}) diff --git a/frappe/website/path_resolver.py b/frappe/website/path_resolver.py index b6f7dbe9f9..39edf0d9a8 100644 --- a/frappe/website/path_resolver.py +++ b/frappe/website/path_resolver.py @@ -1,15 +1,18 @@ +import re + +from werkzeug.routing import Rule + import frappe from frappe.website.page_controllers.document_page import DocumentPage from frappe.website.page_controllers.list_page import ListPage from frappe.website.page_controllers.not_found_page import NotFoundPage from frappe.website.page_controllers.print_page import PrintPage +from frappe.website.page_controllers.redirect_page import RedirectPage from frappe.website.page_controllers.static_page import StaticPage from frappe.website.page_controllers.template_page import TemplatePage from frappe.website.page_controllers.web_form import WebFormPage -from frappe.website.page_controllers.redirect_page import RedirectPage -from frappe.website.redirect import resolve_redirect -from frappe.website.render import resolve_path -from frappe.website.utils import can_cache +from frappe.website.router import evaluate_dynamic_routes +from frappe.website.utils import can_cache, get_home_page class PathResolver(): @@ -46,3 +49,85 @@ class PathResolver(): _endpoint, renderer_instance = self.resolve() return not isinstance(renderer_instance, NotFoundPage) + +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) diff --git a/frappe/website/redirect.py b/frappe/website/redirect.py deleted file mode 100644 index e66c0a3b7b..0000000000 --- a/frappe/website/redirect.py +++ /dev/null @@ -1,45 +0,0 @@ -from __future__ import unicode_literals - -import re, frappe - -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 - diff --git a/frappe/website/render.py b/frappe/website/render.py index c14d8e0d48..d5587ad98b 100644 --- a/frappe/website/render.py +++ b/frappe/website/render.py @@ -6,13 +6,10 @@ import mimetypes import re from six import iteritems -from werkzeug.routing import Rule from werkzeug.wrappers import Response import frappe import frappe.sessions -from frappe.website.router import evaluate_dynamic_routes -from frappe.website.utils import get_home_page def build_response(path, data, http_status_code, headers=None): @@ -30,6 +27,26 @@ def build_response(path, data, http_status_code, headers=None): return response +def set_content_type(response, data, path): + if isinstance(data, dict): + response.mimetype = 'application/json' + response.charset = 'utf-8' + data = json.dumps(data) + return data + + response.mimetype = 'text/html' + response.charset = 'utf-8' + + # ignore paths ending with .com to avoid unnecessary download + # https://bugs.python.org/issue22347 + if "." in path and not path.endswith('.com'): + content_type, encoding = mimetypes.guess_type(path) + if content_type: + response.mimetype = content_type + if encoding: + response.charset = encoding + + return data def add_preload_headers(response): from bs4 import BeautifulSoup @@ -53,67 +70,6 @@ def add_preload_headers(response): import traceback traceback.print_exc() -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) - -def set_content_type(response, data, path): - if isinstance(data, dict): - response.mimetype = 'application/json' - response.charset = 'utf-8' - data = json.dumps(data) - return data - - response.mimetype = 'text/html' - response.charset = 'utf-8' - - # ignore paths ending with .com to avoid unnecessary download - # https://bugs.python.org/issue22347 - if "." in path and not path.endswith('.com'): - content_type, encoding = mimetypes.guess_type(path) - if content_type: - response.mimetype = content_type - if encoding: - response.charset = encoding - - return data - def clear_cache(path=None): # TODO: Remove this from frappe.website.utils import clear_cache diff --git a/frappe/website/router.py b/frappe/website/router.py index 2e27b4c690..9922647ebf 100644 --- a/frappe/website/router.py +++ b/frappe/website/router.py @@ -5,130 +5,9 @@ import io import os import re -from werkzeug.routing import Map, NotFound, Rule - import frappe -from frappe.model.document import get_controller -from frappe.website.utils import can_cache, extract_comment_tag, extract_title - - -def resolve_route(path): - """Returns the page route object based on searching in pages and generators. - The `www` folder is also a part of generator **Web Page**. - - The only exceptions are `/about` and `/contact` these will be searched in Web Pages - first before checking the standard pages.""" - - if path not in ("about", "contact"): - context = get_page_info_from_template(path) - if context: - return context - return get_page_context_from_doctype(path) - else: - context = get_page_context_from_doctype(path) - if context: - return context - return get_page_info_from_template(path) - -def get_page_context(path): - page_context = None - if can_cache(): - page_context_cache = frappe.cache().hget("page_context", path) or {} - page_context = page_context_cache.get(frappe.local.lang, None) - - if not page_context: - page_context = make_page_context(path) - if can_cache(page_context.no_cache): - page_context_cache[frappe.local.lang] = page_context - frappe.cache().hset("page_context", path, page_context_cache) - - return page_context - -def make_page_context(path): - context = resolve_route(path) - if not context: - raise frappe.PageDoesNotExistError - - context.doctype = context.ref_doctype - - if context.page_title: - context.title = context.page_title - - context.pathname = frappe.local.path - - return context - -def get_page_info_from_template(path): - '''Return page_info from path''' - for app in frappe.get_installed_apps(frappe_last=True): - app_path = frappe.get_app_path(app) - - folders = get_start_folders() - - for start in folders: - search_path = os.path.join(app_path, start, path) - options = (search_path, search_path + '.html', search_path + '.md', - search_path + '/index.html', search_path + '/index.md') - for o in options: - option = frappe.as_unicode(o) - if os.path.exists(option) and not os.path.isdir(option): - return get_page_info(option, app, start, app_path=app_path) - - return None - -def get_page_context_from_doctype(path): - page_info = get_page_info_from_doctypes(path) - if not page_info: - page_info = get_page_info_from_web_page_with_dynamic_routes(path) - - if page_info: - return frappe.get_doc(page_info.get("doctype"), - page_info.get("name")).get_page_info() - -def get_all_page_context_from_doctypes(): - ''' - Get all doctype generated routes (for sitemap.xml) - ''' - routes = frappe.cache().get_value("website_generator_routes") - if not routes: - routes = get_page_info_from_doctypes() - frappe.cache().set_value("website_generator_routes", routes) - - return routes - -def get_page_info_from_doctypes(path=None): - ''' - Find a document with matching `route` from all doctypes with `has_web_view`=1 - ''' - routes = {} - for doctype in get_doctypes_with_web_view(): - filters = {} - controller = get_controller(doctype) - meta = frappe.get_meta(doctype) - - condition_field = (meta.is_published_field or - # custom doctypes dont have controllers and no website attribute - (controller.website.condition_field if not meta.custom else None)) - - if condition_field: - filters[condition_field] = 1 - - if path: - filters['route'] = path - - try: - for r in frappe.get_all(doctype, fields = ['name', 'route', 'modified'], - filters = filters, limit = 1): - - routes[r.route] = {"doctype": doctype, "name": r.name, "modified": r.modified} - - # just want one path, return it! - if path: - return routes[r.route] - except Exception as e: - if not frappe.db.is_missing_column(e): raise e - - return routes +from frappe.website.utils import extract_comment_tag, extract_title +from werkzeug.routing import Map, Rule, NotFound def get_page_info_from_web_page_with_dynamic_routes(path): ''' diff --git a/frappe/website/utils.py b/frappe/website/utils.py index 9e98b9da9c..bfe7d5867d 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -136,14 +136,8 @@ def get_home_page_via_hooks(): return home_page -def is_signup_enabled(): - if getattr(frappe.local, "is_signup_enabled", None) is None: - frappe.local.is_signup_enabled = True - if frappe.utils.cint(frappe.db.get_value("Website Settings", - "Website Settings", "disable_signup")): - frappe.local.is_signup_enabled = False - - return frappe.local.is_signup_enabled +def is_signup_disabled(): + return frappe.db.get_single_value('Website Settings', 'disable_signup', True) def cleanup_page_name(title): """make page name from title""" @@ -353,7 +347,7 @@ def get_sidebar_items(parent_sidebar, basepath): sidebar_items = [] hooks = frappe.get_hooks('look_for_sidebar_json') - look_for_sidebar_json = hooks[0] if hooks else 0 + look_for_sidebar_json = hooks[0] if hooks else frappe.flags.look_for_sidebar if basepath and look_for_sidebar_json: sidebar_items = get_sidebar_items_from_sidebar_file(basepath, look_for_sidebar_json) diff --git a/frappe/www/list.py b/frappe/www/list.py index fc4dc602c3..28bf73e575 100644 --- a/frappe/www/list.py +++ b/frappe/www/list.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe, json from frappe.utils import cint, quoted -from frappe.website.render import resolve_path +from frappe.website.path_resolver import resolve_path from frappe.model.document import get_controller, Document from frappe import _ From 948d7d2b564f4b2d198d38e23a774ccca7604ba1 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 20 May 2021 13:20:05 +0530 Subject: [PATCH 066/164] test: Add sidebar test --- frappe/website/doctype/web_page/test_web_page.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/frappe/website/doctype/web_page/test_web_page.py b/frappe/website/doctype/web_page/test_web_page.py index 108b36caaa..ada42f7a75 100644 --- a/frappe/website/doctype/web_page/test_web_page.py +++ b/frappe/website/doctype/web_page/test_web_page.py @@ -82,6 +82,15 @@ class TestWebPage(unittest.TestCase): # assert template block rendered self.assertTrue('

Test content

' in frappe.as_unicode(content)) + def test_json_sidebar_data(self): + frappe.flags.look_for_sidebar = False + content = get_response_content('/_test/_test_folder/_test_page') + self.assertTrue('Test Sidebar' not in frappe.as_unicode(content)) + frappe.flags.look_for_sidebar = True + content = get_response_content('/_test/_test_folder/_test_page') + self.assertTrue('Test Sidebar' in frappe.as_unicode(content)) + frappe.flags.look_for_sidebar = False + def test_home_page(self): content = get_response_content(path='/') print(content) From 5bd80106813398f4ad4ca7d0315519d3fdbdb941 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 20 May 2021 13:24:44 +0530 Subject: [PATCH 067/164] style: Translate string --- frappe/website/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/website/utils.py b/frappe/website/utils.py index bfe7d5867d..814bd0101e 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -11,6 +11,7 @@ from past.builtins import cmp from six import iteritems import frappe +from frappe import _ from frappe.model.document import Document from frappe.utils import md_to_html @@ -154,7 +155,7 @@ def cleanup_page_name(title): def get_shade(color, percent=None): - frappe.msgprint('get_shade method has been deprecated!') + frappe.msgprint(_('get_shade method has been deprecated.')) return color def abs_url(path): From 0a89c9e32e625e53cbe5ea151aff42031fb833a5 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 20 May 2021 13:25:04 +0530 Subject: [PATCH 068/164] fix: Add canonical link --- frappe/website/page_controllers/base_template_page.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/website/page_controllers/base_template_page.py b/frappe/website/page_controllers/base_template_page.py index c40afb7b1c..af7fc43293 100644 --- a/frappe/website/page_controllers/base_template_page.py +++ b/frappe/website/page_controllers/base_template_page.py @@ -32,6 +32,7 @@ class BaseTemplatePage(WebPage): # to be able to inspect the context dict # Use the macro "inspect" from macros.html self.context._context_dict = self.context + self.context.canonical = frappe.utils.get_url(frappe.utils.escape_html(self.path)) # context sends us a new template path if self.context.template: From d7696f5f95de82fac1a429e71d3c705d37725b32 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 20 May 2021 13:44:35 +0530 Subject: [PATCH 069/164] test: Add test files --- frappe/website/doctype/web_page/test_web_page.py | 7 ------- frappe/www/_test/_sidebar.json | 6 ++++++ frappe/www/_test/_test_folder/__init__.py | 0 frappe/www/_test/_test_folder/_test_page.html | 1 + frappe/www/_test/_test_folder/_test_toc.md | 9 +++++++++ frappe/www/_test/_test_folder/new.csv/index.html | 12 ++++++++++++ 6 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 frappe/www/_test/_sidebar.json create mode 100644 frappe/www/_test/_test_folder/__init__.py create mode 100644 frappe/www/_test/_test_folder/_test_toc.md create mode 100644 frappe/www/_test/_test_folder/new.csv/index.html diff --git a/frappe/website/doctype/web_page/test_web_page.py b/frappe/website/doctype/web_page/test_web_page.py index ada42f7a75..c5d250a4a0 100644 --- a/frappe/website/doctype/web_page/test_web_page.py +++ b/frappe/website/doctype/web_page/test_web_page.py @@ -93,16 +93,10 @@ class TestWebPage(unittest.TestCase): def test_home_page(self): content = get_response_content(path='/') - print(content) content = get_response_content(path='/index') - print(content) def test_table_of_content(self): page = get_response(path='/_test/_test_folder/_test_toc') - print(page) - - def test_sidebar_data(self): - pass def test_meta_tags(self): pass @@ -118,5 +112,4 @@ class TestWebPage(unittest.TestCase): # breadcrumb # validate -# cache no-cache # page context caching diff --git a/frappe/www/_test/_sidebar.json b/frappe/www/_test/_sidebar.json new file mode 100644 index 0000000000..a2567e94da --- /dev/null +++ b/frappe/www/_test/_sidebar.json @@ -0,0 +1,6 @@ +[ + { + "route": "/_test/_test_folder", + "title": "Test Sidebar" + } +] diff --git a/frappe/www/_test/_test_folder/__init__.py b/frappe/www/_test/_test_folder/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/www/_test/_test_folder/_test_page.html b/frappe/www/_test/_test_folder/_test_page.html index 7364235e10..900f07459d 100644 --- a/frappe/www/_test/_test_folder/_test_page.html +++ b/frappe/www/_test/_test_folder/_test_page.html @@ -1,3 +1,4 @@ {% block content %} +{% include "templates/includes/web_sidebar.html" %}

Test content

{% endblock %} diff --git a/frappe/www/_test/_test_folder/_test_toc.md b/frappe/www/_test/_test_folder/_test_toc.md new file mode 100644 index 0000000000..8d05b120a5 --- /dev/null +++ b/frappe/www/_test/_test_folder/_test_toc.md @@ -0,0 +1,9 @@ +# Level 1 + +## Level 1.1 + +## Level 1.2 + +## Level 1.3 + +### Level 1.3.1 diff --git a/frappe/www/_test/_test_folder/new.csv/index.html b/frappe/www/_test/_test_folder/new.csv/index.html new file mode 100644 index 0000000000..7a1bb69558 --- /dev/null +++ b/frappe/www/_test/_test_folder/new.csv/index.html @@ -0,0 +1,12 @@ + + + + + + + Document + + + Test Page + + From 1a0a095b34f33ddaba7615b9bddb1d4d8a1df30a Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 24 May 2021 14:55:42 +0530 Subject: [PATCH 070/164] fix: Template path for document_page --- frappe/website/page_controllers/document_page.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/website/page_controllers/document_page.py b/frappe/website/page_controllers/document_page.py index 537b8ac658..c79e385a68 100644 --- a/frappe/website/page_controllers/document_page.py +++ b/frappe/website/page_controllers/document_page.py @@ -53,8 +53,8 @@ class DocumentPage(BaseTemplatePage): self.init_context() self.update_context() self.post_process_context() - - html = frappe.get_template(self.context.template_path).render(self.context) + template_path = self.context.template_path or self.context.template or '' + html = frappe.get_template(template_path).render(self.context) html = self.add_csrf_token(html) return build_response(self.path, html, self.http_status_code or 200, self.headers) From 15f5b697d312c4ee6174fd4e40828c41d3eb581c Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 24 May 2021 19:52:31 +0530 Subject: [PATCH 071/164] fix: Website template path --- frappe/website/page_controllers/base_template_page.py | 7 +++++-- frappe/website/page_controllers/document_page.py | 11 ++++++----- frappe/website/website_generator.py | 2 ++ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/frappe/website/page_controllers/base_template_page.py b/frappe/website/page_controllers/base_template_page.py index af7fc43293..4d282dab9d 100644 --- a/frappe/website/page_controllers/base_template_page.py +++ b/frappe/website/page_controllers/base_template_page.py @@ -5,6 +5,10 @@ from frappe.website.website_components.metatags import MetaTags class BaseTemplatePage(WebPage): + def __init__(self, path, http_status_code): + super().__init__(path=path, http_status_code=http_status_code) + self.template_path = '' + def init_context(self): self.context = frappe._dict() self.context.update(get_website_settings()) @@ -35,8 +39,7 @@ class BaseTemplatePage(WebPage): self.context.canonical = frappe.utils.get_url(frappe.utils.escape_html(self.path)) # context sends us a new template path - if self.context.template: - self.template_path = self.context.template + self.template_path = self.context.template or '' def set_base_template_if_missing(self): if not self.context.base_template_path: diff --git a/frappe/website/page_controllers/document_page.py b/frappe/website/page_controllers/document_page.py index c79e385a68..6ab384e77d 100644 --- a/frappe/website/page_controllers/document_page.py +++ b/frappe/website/page_controllers/document_page.py @@ -53,8 +53,7 @@ class DocumentPage(BaseTemplatePage): self.init_context() self.update_context() self.post_process_context() - template_path = self.context.template_path or self.context.template or '' - html = frappe.get_template(template_path).render(self.context) + html = frappe.get_template(self.template_path).render(self.context) html = self.add_csrf_token(html) return build_response(self.path, html, self.http_status_code or 200, self.headers) @@ -62,10 +61,12 @@ class DocumentPage(BaseTemplatePage): def update_context(self): self.context.doc = self.doc self.context.update(self.context.doc.as_dict()) - self.context.update(self.context.doc.get_website_properties()) + self.context.update(self.context.doc.get_page_info()) - if not self.context.template_path: - self.context.template_path = self.context.doc.meta.get_web_template() + self.template_path = self.context.template or self.template_path + + if not self.template_path: + self.template_path = self.context.doc.meta.get_web_template() if hasattr(self.doc, "get_context"): ret = self.doc.get_context(self.context) diff --git a/frappe/website/website_generator.py b/frappe/website/website_generator.py index fc08abeed9..f2638fd86d 100644 --- a/frappe/website/website_generator.py +++ b/frappe/website/website_generator.py @@ -129,6 +129,8 @@ class WebsiteGenerator(Document): if not route.page_title: route.page_title = self.get(self.get_title_field()) + route.title = route.page_title + return route def send_indexing_request(self, operation_type='URL_UPDATED'): From 63ce0c51361422c3386f6a62a3e3334560f5a71d Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 24 May 2021 21:53:09 +0530 Subject: [PATCH 072/164] fix: Fallback to template_path if template is not set --- frappe/website/page_controllers/base_template_page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/page_controllers/base_template_page.py b/frappe/website/page_controllers/base_template_page.py index 4d282dab9d..012f9fccc9 100644 --- a/frappe/website/page_controllers/base_template_page.py +++ b/frappe/website/page_controllers/base_template_page.py @@ -39,7 +39,7 @@ class BaseTemplatePage(WebPage): self.context.canonical = frappe.utils.get_url(frappe.utils.escape_html(self.path)) # context sends us a new template path - self.template_path = self.context.template or '' + self.template_path = self.context.template or self.template_path def set_base_template_if_missing(self): if not self.context.base_template_path: From cbd00179cb16e0f08b8283d6f1d0f0e815cefef1 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 24 May 2021 22:31:23 +0530 Subject: [PATCH 073/164] fix: Move set_missing_values to base_template_page - Since it is common for all inherited pages from base_template_page --- .../page_controllers/base_template_page.py | 30 ++++++++++++------- .../website/page_controllers/template_page.py | 11 +------ 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/frappe/website/page_controllers/base_template_page.py b/frappe/website/page_controllers/base_template_page.py index 012f9fccc9..f06869fd5f 100644 --- a/frappe/website/page_controllers/base_template_page.py +++ b/frappe/website/page_controllers/base_template_page.py @@ -28,18 +28,10 @@ class BaseTemplatePage(WebPage): self.set_base_template_if_missing() self.set_title_with_prefix() self.update_website_context() - - # set using frappe.respond_as_web_page - if hasattr(frappe.local, 'response') and frappe.local.response.get('context'): - self.context.update(frappe.local.response.context) - - # to be able to inspect the context dict - # Use the macro "inspect" from macros.html - self.context._context_dict = self.context - self.context.canonical = frappe.utils.get_url(frappe.utils.escape_html(self.path)) - # context sends us a new template path self.template_path = self.context.template or self.template_path + self.context._context_dict = self.context + self.set_missing_values() def set_base_template_if_missing(self): if not self.context.base_template_path: @@ -51,6 +43,24 @@ class BaseTemplatePage(WebPage): and not self.context.title.startswith(self.context.title_prefix)): self.context.title = '{0} - {1}'.format(self.context.title_prefix, self.context.title) + def set_missing_values(self): + # set using frappe.respond_as_web_page + if hasattr(frappe.local, 'response') and frappe.local.response.get('context'): + self.context.update(frappe.local.response.context) + + # to be able to inspect the context dict + # Use the macro "inspect" from macros.html + self.context.canonical = frappe.utils.get_url(frappe.utils.escape_html(self.path)) + + if "url_prefix" not in self.context: + self.context.url_prefix = "" + + if self.context.url_prefix and self.context.url_prefix[-1]!='/': + self.context.url_prefix += '/' + + self.context.path = self.path + self.context.pathname = frappe.local.path if hasattr(frappe, 'local') else self.path + def update_website_context(self): # apply context from hooks update_website_context = frappe.get_hooks('update_website_context') diff --git a/frappe/website/page_controllers/template_page.py b/frappe/website/page_controllers/template_page.py index 635b8a56fe..cd30fa02c8 100644 --- a/frappe/website/page_controllers/template_page.py +++ b/frappe/website/page_controllers/template_page.py @@ -65,7 +65,6 @@ class TemplatePage(BaseTemplatePage): def post_process_context(self): self.set_user_info() self.add_sidebar_and_breadcrumbs() - self.set_missing_values() super(TemplatePage, self).post_process_context() def add_sidebar_and_breadcrumbs(self): @@ -228,15 +227,7 @@ class TemplatePage(BaseTemplatePage): self.template_path = 'www/{path}.html'.format(path=path) def set_missing_values(self): - if "url_prefix" not in self.context: - self.context.url_prefix = "" - - if self.context.url_prefix and self.context.url_prefix[-1]!='/': - self.context.url_prefix += '/' - - self.context.path = self.path - self.context.pathname = frappe.local.path if hasattr(frappe, 'local') else self.path - + super().set_missing_values() # for backward compatibility self.context.docs_base_url = '/docs' From 70a8bd5945da8db42838cc61efe8312237d9e91d Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 25 May 2021 08:21:06 +0530 Subject: [PATCH 074/164] refactor: Rename validate function to can_render --- frappe/website/page_controllers/document_page.py | 2 +- frappe/website/page_controllers/error_page.py | 2 +- frappe/website/page_controllers/list_page.py | 2 +- frappe/website/page_controllers/not_found_page.py | 2 +- frappe/website/page_controllers/not_permitted_page.py | 2 +- frappe/website/page_controllers/print_page.py | 2 +- frappe/website/page_controllers/redirect_page.py | 2 +- frappe/website/page_controllers/static_page.py | 2 +- frappe/website/page_controllers/template_page.py | 2 +- frappe/website/page_controllers/web_form.py | 2 +- frappe/website/page_controllers/web_page.py | 2 +- frappe/website/path_resolver.py | 5 ++--- 12 files changed, 13 insertions(+), 14 deletions(-) diff --git a/frappe/website/page_controllers/document_page.py b/frappe/website/page_controllers/document_page.py index 6ab384e77d..1a42b5debb 100644 --- a/frappe/website/page_controllers/document_page.py +++ b/frappe/website/page_controllers/document_page.py @@ -7,7 +7,7 @@ from frappe.website.router import (get_doctypes_with_web_view, class DocumentPage(BaseTemplatePage): - def validate(self): + def can_render(self): ''' Find a document with matching `route` from all doctypes with `has_web_view`=1 ''' diff --git a/frappe/website/page_controllers/error_page.py b/frappe/website/page_controllers/error_page.py index 554949684f..52903d076b 100644 --- a/frappe/website/page_controllers/error_page.py +++ b/frappe/website/page_controllers/error_page.py @@ -6,5 +6,5 @@ class ErrorPage(TemplatePage): super().__init__(path=path, http_status_code=http_status_code) self.http_status_code = getattr(exception, 'http_status_code', None) or http_status_code or 500 - def validate(self): + def can_render(self): return True diff --git a/frappe/website/page_controllers/list_page.py b/frappe/website/page_controllers/list_page.py index cc6f150dd2..e3b92a2771 100644 --- a/frappe/website/page_controllers/list_page.py +++ b/frappe/website/page_controllers/list_page.py @@ -2,7 +2,7 @@ import frappe from frappe.website.page_controllers.template_page import TemplatePage class ListPage(TemplatePage): - def validate(self): + def can_render(self): return frappe.db.exists('DocType', self.path, True) def render(self): diff --git a/frappe/website/page_controllers/not_found_page.py b/frappe/website/page_controllers/not_found_page.py index 13c6358512..1f687ddb98 100644 --- a/frappe/website/page_controllers/not_found_page.py +++ b/frappe/website/page_controllers/not_found_page.py @@ -15,7 +15,7 @@ class NotFoundPage(TemplatePage): http_status_code = 404 super().__init__(path=path, http_status_code=http_status_code) - def validate(self): + def can_render(self): return True def render(self): diff --git a/frappe/website/page_controllers/not_permitted_page.py b/frappe/website/page_controllers/not_permitted_page.py index 3ed732d80b..75a8b756d8 100644 --- a/frappe/website/page_controllers/not_permitted_page.py +++ b/frappe/website/page_controllers/not_permitted_page.py @@ -9,7 +9,7 @@ class NotPermittedPage(TemplatePage): super().__init__(path=path, http_status_code=http_status_code) self.http_status_code = 403 - def validate(self): + def can_render(self): return True def render(self): diff --git a/frappe/website/page_controllers/print_page.py b/frappe/website/page_controllers/print_page.py index 574f335f5d..790ee75585 100644 --- a/frappe/website/page_controllers/print_page.py +++ b/frappe/website/page_controllers/print_page.py @@ -6,7 +6,7 @@ class PrintPage(TemplatePage): default path returns a printable object (based on permission) /Quotation/Q-0001 ''' - def validate(self): + def can_render(self): parts = self.path.split('/', 1) if len(parts)==2: if (frappe.db.exists('DocType', parts[0], True) diff --git a/frappe/website/page_controllers/redirect_page.py b/frappe/website/page_controllers/redirect_page.py index f3a0ba6e7f..0bfa8897a3 100644 --- a/frappe/website/page_controllers/redirect_page.py +++ b/frappe/website/page_controllers/redirect_page.py @@ -6,7 +6,7 @@ class RedirectPage(object): self.path = path self.http_status_code = http_status_code - def validate(self): + def can_render(self): return True def render(self): diff --git a/frappe/website/page_controllers/static_page.py b/frappe/website/page_controllers/static_page.py index b292a62922..0c75d6feb3 100644 --- a/frappe/website/page_controllers/static_page.py +++ b/frappe/website/page_controllers/static_page.py @@ -23,7 +23,7 @@ class StaticPage(WebPage): if os.path.isfile(file_path): self.file_path = file_path - def validate(self): + def can_render(self): return self.is_valid_file_path() and self.file_path def is_valid_file_path(self): diff --git a/frappe/website/page_controllers/template_page.py b/frappe/website/page_controllers/template_page.py index cd30fa02c8..c9797b88c9 100644 --- a/frappe/website/page_controllers/template_page.py +++ b/frappe/website/page_controllers/template_page.py @@ -36,7 +36,7 @@ class TemplatePage(BaseTemplatePage): self.basepath = os.path.dirname(file_path) return - def validate(self): + def can_render(self): return hasattr(self, 'template_path') and bool(self.template_path) def get_index_path_options(self, search_path): diff --git a/frappe/website/page_controllers/web_form.py b/frappe/website/page_controllers/web_form.py index d7ab02d880..c1afaafbe5 100644 --- a/frappe/website/page_controllers/web_form.py +++ b/frappe/website/page_controllers/web_form.py @@ -2,5 +2,5 @@ from frappe.website.page_controllers.web_page import WebPage import frappe class WebFormPage(WebPage): - def validate(self): + def can_render(self): return bool(frappe.get_all("Web Form", filters={'route': self.path})) diff --git a/frappe/website/page_controllers/web_page.py b/frappe/website/page_controllers/web_page.py index 4d9e642b4b..0407afe532 100644 --- a/frappe/website/page_controllers/web_page.py +++ b/frappe/website/page_controllers/web_page.py @@ -13,7 +13,7 @@ class WebPage(object): if self.validate(): return self.render() - def validate(self): + def can_render(self): pass def render(self): diff --git a/frappe/website/path_resolver.py b/frappe/website/path_resolver.py index 39edf0d9a8..854948740c 100644 --- a/frappe/website/path_resolver.py +++ b/frappe/website/path_resolver.py @@ -32,15 +32,14 @@ class PathResolver(): try: resolve_redirect(self.path, request.query_string) except frappe.Redirect: - return self.path, RedirectPage(self.path) + return frappe.flags.redirect_location, RedirectPage(self.path) endpoint = resolve_path(self.path) renderers = (StaticPage, WebFormPage, TemplatePage, ListPage, DocumentPage, PrintPage, NotFoundPage) for renderer in renderers: renderer_instance = renderer(endpoint, 200) - can_render = renderer_instance.validate() - if can_render: + if renderer_instance.can_render(): return endpoint, renderer_instance return endpoint, NotFoundPage(endpoint) From d0915a709bc9984bf205be66193bfd767c598944 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 25 May 2021 08:22:45 +0530 Subject: [PATCH 075/164] fix: Make http_status_code non mandatory --- frappe/website/page_controllers/base_template_page.py | 2 +- frappe/website/serve.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frappe/website/page_controllers/base_template_page.py b/frappe/website/page_controllers/base_template_page.py index f06869fd5f..d7770ccd93 100644 --- a/frappe/website/page_controllers/base_template_page.py +++ b/frappe/website/page_controllers/base_template_page.py @@ -5,7 +5,7 @@ from frappe.website.website_components.metatags import MetaTags class BaseTemplatePage(WebPage): - def __init__(self, path, http_status_code): + def __init__(self, path, http_status_code=None): super().__init__(path=path, http_status_code=http_status_code) self.template_path = '' diff --git a/frappe/website/serve.py b/frappe/website/serve.py index 95a49cef1a..a761f479f3 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -14,8 +14,7 @@ def get_response(path=None, http_status_code=200): try: path_resolver = PathResolver(path) endpoint, renderer_instance = path_resolver.resolve() - if renderer_instance: - response = renderer_instance.render() + response = renderer_instance.render() except frappe.Redirect: return RedirectPage(endpoint or path, http_status_code).render() except frappe.PermissionError as e: From bb68dd7323974771fec691c212a954e4f2683999 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 25 May 2021 09:24:32 +0530 Subject: [PATCH 076/164] fix: Add comment properties back with deprecation warning --- .../website/page_controllers/template_page.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/frappe/website/page_controllers/template_page.py b/frappe/website/page_controllers/template_page.py index c9797b88c9..69e9cceb9f 100644 --- a/frappe/website/page_controllers/template_page.py +++ b/frappe/website/page_controllers/template_page.py @@ -1,5 +1,6 @@ import io import os +import click import frappe from frappe.website.page_controllers.base_template_page import BaseTemplatePage @@ -11,6 +12,17 @@ from frappe.website.utils import (extract_comment_tag, extract_title, WEBPAGE_PY_MODULE_PROPERTIES = ("base_template_path", "template", "no_cache", "sitemap", "condition_field") +COMMENT_PROPERTY_KEY_VALUE_MAP = { + "no-breadcrumbs": ("no_breadcrumbs", 1), + "show-sidebar": ("show_sidebar", 1), + "add-breadcrumbs": ("add_breadcrumbs", 1), + "no-header": ("no_header", 1), + "add-next-prev-links": ("add_next_prev_links", 1), + "no-cache": ("no_cache", 1), + "no-sitemap": ("sitemap", 0), + "sitemap": ("sitemap", 1) +} + class TemplatePage(BaseTemplatePage): def __init__(self, path, http_status_code=None): super().__init__(path=path, http_status_code=http_status_code) @@ -147,6 +159,15 @@ class TemplatePage(BaseTemplatePage): self.source = '''{{% extends "{0}" %}} {{% block page_content %}}{1}{{% endblock %}}'''.format(context.base_template, self.source) + self.set_properties_via_comments() + + def set_properties_via_comments(self): + for comment, (context_key, value) in COMMENT_PROPERTY_KEY_VALUE_MAP.items(): + comment_tag = f"" + if comment_tag in self.source: + self.context[context_key] = value + click.echo(f'⚠️ DEPRECATION WARNING: {comment_tag} will be deprecated on 2021-12-31.') + def run_pymodule_method(self, method): if hasattr(self.pymodule, method): try: From 8317e00712210a802e363c96d3f446f084ddb061 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 25 May 2021 09:27:26 +0530 Subject: [PATCH 077/164] refactor: Rename page_controllers folder to page_renderers --- .../base_template_page.py | 2 +- .../document_page.py | 2 +- .../error_page.py | 2 +- .../list_page.py | 2 +- .../not_found_page.py | 2 +- .../not_permitted_page.py | 2 +- .../print_page.py | 2 +- .../redirect_page.py | 0 .../static_page.py | 2 +- .../template_page.py | 2 +- .../web_form.py | 2 +- .../web_page.py | 0 frappe/website/path_resolver.py | 16 ++++++++-------- frappe/website/serve.py | 6 +++--- 14 files changed, 21 insertions(+), 21 deletions(-) rename frappe/website/{page_controllers => page_renderers}/base_template_page.py (97%) rename frappe/website/{page_controllers => page_renderers}/document_page.py (96%) rename frappe/website/{page_controllers => page_renderers}/error_page.py (81%) rename frappe/website/{page_controllers => page_renderers}/list_page.py (77%) rename frappe/website/{page_controllers => page_renderers}/not_found_page.py (93%) rename frappe/website/{page_controllers => page_renderers}/not_permitted_page.py (89%) rename frappe/website/{page_controllers => page_renderers}/print_page.py (88%) rename frappe/website/{page_controllers => page_renderers}/redirect_page.py (100%) rename frappe/website/{page_controllers => page_renderers}/static_page.py (95%) rename frappe/website/{page_controllers => page_renderers}/template_page.py (99%) rename frappe/website/{page_controllers => page_renderers}/web_form.py (69%) rename frappe/website/{page_controllers => page_renderers}/web_page.py (100%) diff --git a/frappe/website/page_controllers/base_template_page.py b/frappe/website/page_renderers/base_template_page.py similarity index 97% rename from frappe/website/page_controllers/base_template_page.py rename to frappe/website/page_renderers/base_template_page.py index d7770ccd93..8405270a57 100644 --- a/frappe/website/page_controllers/base_template_page.py +++ b/frappe/website/page_renderers/base_template_page.py @@ -1,6 +1,6 @@ import frappe from frappe.website.doctype.website_settings.website_settings import get_website_settings -from frappe.website.page_controllers.web_page import WebPage +from frappe.website.page_renderers.web_page import WebPage from frappe.website.website_components.metatags import MetaTags diff --git a/frappe/website/page_controllers/document_page.py b/frappe/website/page_renderers/document_page.py similarity index 96% rename from frappe/website/page_controllers/document_page.py rename to frappe/website/page_renderers/document_page.py index 1a42b5debb..34dc42e203 100644 --- a/frappe/website/page_controllers/document_page.py +++ b/frappe/website/page_renderers/document_page.py @@ -1,6 +1,6 @@ import frappe from frappe.model.document import get_controller -from frappe.website.page_controllers.base_template_page import BaseTemplatePage +from frappe.website.page_renderers.base_template_page import BaseTemplatePage from frappe.website.render import build_response from frappe.website.router import (get_doctypes_with_web_view, get_page_info_from_web_page_with_dynamic_routes) diff --git a/frappe/website/page_controllers/error_page.py b/frappe/website/page_renderers/error_page.py similarity index 81% rename from frappe/website/page_controllers/error_page.py rename to frappe/website/page_renderers/error_page.py index 52903d076b..3501c77765 100644 --- a/frappe/website/page_controllers/error_page.py +++ b/frappe/website/page_renderers/error_page.py @@ -1,4 +1,4 @@ -from frappe.website.page_controllers.template_page import TemplatePage +from frappe.website.page_renderers.template_page import TemplatePage class ErrorPage(TemplatePage): def __init__(self, path=None, http_status_code=None, exception=None): diff --git a/frappe/website/page_controllers/list_page.py b/frappe/website/page_renderers/list_page.py similarity index 77% rename from frappe/website/page_controllers/list_page.py rename to frappe/website/page_renderers/list_page.py index e3b92a2771..61c781ea14 100644 --- a/frappe/website/page_controllers/list_page.py +++ b/frappe/website/page_renderers/list_page.py @@ -1,5 +1,5 @@ import frappe -from frappe.website.page_controllers.template_page import TemplatePage +from frappe.website.page_renderers.template_page import TemplatePage class ListPage(TemplatePage): def can_render(self): diff --git a/frappe/website/page_controllers/not_found_page.py b/frappe/website/page_renderers/not_found_page.py similarity index 93% rename from frappe/website/page_controllers/not_found_page.py rename to frappe/website/page_renderers/not_found_page.py index 1f687ddb98..af510fecfc 100644 --- a/frappe/website/page_controllers/not_found_page.py +++ b/frappe/website/page_renderers/not_found_page.py @@ -2,7 +2,7 @@ import os from urllib.parse import urlparse import frappe -from frappe.website.page_controllers.template_page import TemplatePage +from frappe.website.page_renderers.template_page import TemplatePage from frappe.website.utils import can_cache HOMEPAGE_PATHS = ('/', '/index', 'index') diff --git a/frappe/website/page_controllers/not_permitted_page.py b/frappe/website/page_renderers/not_permitted_page.py similarity index 89% rename from frappe/website/page_controllers/not_permitted_page.py rename to frappe/website/page_renderers/not_permitted_page.py index 75a8b756d8..e69299f5c5 100644 --- a/frappe/website/page_controllers/not_permitted_page.py +++ b/frappe/website/page_renderers/not_permitted_page.py @@ -1,6 +1,6 @@ import frappe from frappe import _ -from frappe.website.page_controllers.template_page import TemplatePage +from frappe.website.page_renderers.template_page import TemplatePage from frappe.utils import cstr class NotPermittedPage(TemplatePage): diff --git a/frappe/website/page_controllers/print_page.py b/frappe/website/page_renderers/print_page.py similarity index 88% rename from frappe/website/page_controllers/print_page.py rename to frappe/website/page_renderers/print_page.py index 790ee75585..05d4026e2b 100644 --- a/frappe/website/page_controllers/print_page.py +++ b/frappe/website/page_renderers/print_page.py @@ -1,5 +1,5 @@ import frappe -from frappe.website.page_controllers.template_page import TemplatePage +from frappe.website.page_renderers.template_page import TemplatePage class PrintPage(TemplatePage): ''' diff --git a/frappe/website/page_controllers/redirect_page.py b/frappe/website/page_renderers/redirect_page.py similarity index 100% rename from frappe/website/page_controllers/redirect_page.py rename to frappe/website/page_renderers/redirect_page.py diff --git a/frappe/website/page_controllers/static_page.py b/frappe/website/page_renderers/static_page.py similarity index 95% rename from frappe/website/page_controllers/static_page.py rename to frappe/website/page_renderers/static_page.py index 0c75d6feb3..fc3edbe216 100644 --- a/frappe/website/page_controllers/static_page.py +++ b/frappe/website/page_renderers/static_page.py @@ -5,7 +5,7 @@ from werkzeug.wrappers import Response from werkzeug.wsgi import wrap_file import frappe -from frappe.website.page_controllers.web_page import WebPage +from frappe.website.page_renderers.web_page import WebPage UNSUPPORTED_STATIC_PAGE_TYPES = ('html', 'md', 'js', 'xml', 'css', 'txt', 'py', 'json') diff --git a/frappe/website/page_controllers/template_page.py b/frappe/website/page_renderers/template_page.py similarity index 99% rename from frappe/website/page_controllers/template_page.py rename to frappe/website/page_renderers/template_page.py index 69e9cceb9f..467d74b283 100644 --- a/frappe/website/page_controllers/template_page.py +++ b/frappe/website/page_renderers/template_page.py @@ -3,7 +3,7 @@ import os import click import frappe -from frappe.website.page_controllers.base_template_page import BaseTemplatePage +from frappe.website.page_renderers.base_template_page import BaseTemplatePage from frappe.website.utils import get_sidebar_items from frappe.website.render import build_response from frappe.website.router import get_base_template diff --git a/frappe/website/page_controllers/web_form.py b/frappe/website/page_renderers/web_form.py similarity index 69% rename from frappe/website/page_controllers/web_form.py rename to frappe/website/page_renderers/web_form.py index c1afaafbe5..094ae78d71 100644 --- a/frappe/website/page_controllers/web_form.py +++ b/frappe/website/page_renderers/web_form.py @@ -1,4 +1,4 @@ -from frappe.website.page_controllers.web_page import WebPage +from frappe.website.page_renderers.web_page import WebPage import frappe class WebFormPage(WebPage): diff --git a/frappe/website/page_controllers/web_page.py b/frappe/website/page_renderers/web_page.py similarity index 100% rename from frappe/website/page_controllers/web_page.py rename to frappe/website/page_renderers/web_page.py diff --git a/frappe/website/path_resolver.py b/frappe/website/path_resolver.py index 854948740c..9816900677 100644 --- a/frappe/website/path_resolver.py +++ b/frappe/website/path_resolver.py @@ -3,14 +3,14 @@ import re from werkzeug.routing import Rule import frappe -from frappe.website.page_controllers.document_page import DocumentPage -from frappe.website.page_controllers.list_page import ListPage -from frappe.website.page_controllers.not_found_page import NotFoundPage -from frappe.website.page_controllers.print_page import PrintPage -from frappe.website.page_controllers.redirect_page import RedirectPage -from frappe.website.page_controllers.static_page import StaticPage -from frappe.website.page_controllers.template_page import TemplatePage -from frappe.website.page_controllers.web_form import WebFormPage +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 diff --git a/frappe/website/serve.py b/frappe/website/serve.py index a761f479f3..fe7fc77064 100644 --- a/frappe/website/serve.py +++ b/frappe/website/serve.py @@ -1,7 +1,7 @@ import frappe -from frappe.website.page_controllers.error_page import ErrorPage -from frappe.website.page_controllers.not_permitted_page import NotPermittedPage -from frappe.website.page_controllers.redirect_page import RedirectPage +from frappe.website.page_renderers.error_page import ErrorPage +from frappe.website.page_renderers.not_permitted_page import NotPermittedPage +from frappe.website.page_renderers.redirect_page import RedirectPage from frappe.website.path_resolver import PathResolver From 86897c1808063efccbc8929250c3d2ca203b9c9c Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 25 May 2021 10:53:35 +0530 Subject: [PATCH 078/164] refactor: Remove render.py and move all utility functions to utils.py - Replace or remove all render imports --- frappe/cache_manager.py | 2 +- frappe/commands/utils.py | 8 +- frappe/core/doctype/comment/comment.py | 2 +- frappe/core/doctype/doctype/doctype.py | 9 +-- frappe/installer.py | 4 +- frappe/migrate.py | 4 +- frappe/patches.txt | 2 +- .../templates/includes/comments/comments.py | 2 +- .../about_us_settings/about_us_settings.py | 6 +- .../doctype/blog_category/blog_category.py | 2 +- frappe/website/doctype/blog_post/blog_post.py | 2 +- .../doctype/blog_settings/blog_settings.py | 4 +- .../contact_us_settings.py | 2 +- .../doctype/help_article/help_article.py | 2 +- .../portal_settings/portal_settings.py | 2 +- .../doctype/web_template/web_template.py | 2 +- .../doctype/website_script/website_script.py | 2 +- .../website_settings/website_settings.py | 2 +- .../website_slideshow/website_slideshow.py | 2 +- .../website/page_renderers/document_page.py | 2 +- .../website/page_renderers/redirect_page.py | 2 +- .../website/page_renderers/template_page.py | 6 +- frappe/website/page_renderers/web_page.py | 4 - frappe/website/render.py | 76 ------------------- frappe/website/router.py | 49 +----------- frappe/website/utils.py | 60 +++++++++++++++ frappe/website/website_generator.py | 2 +- .../_test/_test_folder/new.csv/__init__.py | 0 28 files changed, 96 insertions(+), 166 deletions(-) delete mode 100644 frappe/website/render.py create mode 100644 frappe/www/_test/_test_folder/new.csv/__init__.py diff --git a/frappe/cache_manager.py b/frappe/cache_manager.py index 7330c83102..516f13de39 100644 --- a/frappe/cache_manager.py +++ b/frappe/cache_manager.py @@ -55,7 +55,7 @@ def clear_domain_cache(user=None): cache.delete_value(domain_cache_keys) def clear_global_cache(): - from frappe.website.render import clear_cache as clear_website_cache + from frappe.website.utils import clear_cache as clear_website_cache clear_doctype_cache() clear_website_cache() diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 4da0f6bb78..a6937a5dc4 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -69,14 +69,14 @@ def watch(apps=None): def clear_cache(context): "Clear cache, doctype cache and defaults" import frappe.sessions - import frappe.website.render + from frappe.website.utils import clear_cache as clear_website_cache from frappe.desk.notifications import clear_notifications for site in context.sites: try: frappe.connect(site) frappe.clear_cache() clear_notifications() - frappe.website.render.clear_cache() + clear_website_cache() finally: frappe.destroy() if not context.sites: @@ -86,12 +86,12 @@ def clear_cache(context): @pass_context def clear_website_cache(context): "Clear website cache" - import frappe.website.render + from frappe.website.utils import clear_cache as clear_website_cache for site in context.sites: try: frappe.init(site=site) frappe.connect() - frappe.website.render.clear_cache() + clear_website_cache() finally: frappe.destroy() if not context.sites: diff --git a/frappe/core/doctype/comment/comment.py b/frappe/core/doctype/comment/comment.py index ad5d60500b..6090947393 100644 --- a/frappe/core/doctype/comment/comment.py +++ b/frappe/core/doctype/comment/comment.py @@ -11,7 +11,7 @@ from frappe.core.doctype.user.user import extract_mentions from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification,\ get_title, get_title_html from frappe.utils import get_fullname -from frappe.website.render import clear_cache +from frappe.website.utils import clear_cache from frappe.database.schema import add_column from frappe.exceptions import ImplicitCommitError diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 84673f990a..8186aac2b0 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -13,7 +13,6 @@ from six import iteritems # imports - module imports import frappe -import frappe.website.render from frappe import _ from frappe.utils import now, cint from frappe.model import no_value_fields, default_fields, data_fieldtypes, table_fields, data_field_options @@ -27,6 +26,7 @@ from frappe.model.docfield import supports_translation from frappe.modules.import_file import get_file_path from frappe.model.meta import Meta from frappe.desk.utils import validate_route_conflict +from frappe.website.utils import clear_cache class InvalidFieldNameError(frappe.ValidationError): pass class UniqueFieldnameError(frappe.ValidationError): pass @@ -251,7 +251,7 @@ class DocType(Document): frappe.throw(_('Field "route" is mandatory for Web Views'), title='Missing Field') # clear website cache - frappe.website.render.clear_cache() + clear_cache() def change_modified_of_parent(self): """Change the timestamp of parent DocType if the current one is a child to clear caches.""" @@ -553,11 +553,6 @@ class DocType(Document): from frappe.modules.export_file import export_to_files export_to_files(record_list=[['DocType', self.name]], create_init=True) - def import_doc(self): - """Import from standard folder `[module]/doctype/[name]/[name].json`.""" - from frappe.modules.import_module import import_from_files - import_from_files(record_list=[[self.module, 'doctype', self.name]]) - def make_controller_template(self): """Make boilerplate controller template.""" make_boilerplate("controller._py", self) diff --git a/frappe/installer.py b/frappe/installer.py index d7d885d60e..138d5abc1b 100755 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -282,10 +282,10 @@ def remove_app(app_name, dry_run=False, yes=False, no_backup=False, force=False) def post_install(rebuild_website=False): - from frappe.website import render + from frappe.website.utils import clear_cache as clear_website_cache if rebuild_website: - render.clear_cache() + clear_website_cache() init_singles() frappe.db.commit() diff --git a/frappe/migrate.py b/frappe/migrate.py index 619510fe5e..0fe7c2e034 100644 --- a/frappe/migrate.py +++ b/frappe/migrate.py @@ -15,7 +15,7 @@ from frappe.utils.connections import check_connection from frappe.utils.dashboard import sync_dashboards from frappe.cache_manager import clear_global_cache from frappe.desk.notifications import clear_notifications -from frappe.website import render +from frappe.website.utils import clear_cache as clear_website_cache from frappe.core.doctype.language.language import sync_languages from frappe.modules.utils import sync_customizations from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs @@ -78,7 +78,7 @@ Otherwise, check the server logs and ensure that all the required services are r frappe.get_doc('Portal Settings', 'Portal Settings').sync_menu() # syncs statics - render.clear_cache() + clear_website_cache() # updating installed applications data frappe.get_single('Installed Applications').update_versions() diff --git a/frappe/patches.txt b/frappe/patches.txt index e70be0a37b..a2a3f73592 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -93,7 +93,7 @@ frappe.patches.v4_0.create_custom_field_for_owner_match frappe.patches.v4_0.enable_scheduler_in_system_settings execute:frappe.db.sql("update tabReport set apply_user_permissions=1") #2014-06-03 frappe.patches.v4_0.replace_deprecated_timezones -execute:import frappe.website.render; frappe.website.render.clear_cache("login"); #2014-06-10 +execute:import frappe.website.utils; frappe.website.utils.clear_cache("login"); #2014-06-10 frappe.patches.v4_0.fix_attach_field_file_url execute:frappe.permissions.reset_perms("User") #2015-03-24 execute:frappe.db.sql("""delete from `tabUserRole` where ifnull(parentfield, '')='' or ifnull(`role`, '')=''""") #2014-08-18 diff --git a/frappe/templates/includes/comments/comments.py b/frappe/templates/includes/comments/comments.py index d08eb12ba8..b1cd98f2b9 100644 --- a/frappe/templates/includes/comments/comments.py +++ b/frappe/templates/includes/comments/comments.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe import re -from frappe.website.render import clear_cache +from frappe.website.utils import clear_cache from frappe.utils import add_to_date, now from frappe import _ diff --git a/frappe/website/doctype/about_us_settings/about_us_settings.py b/frappe/website/doctype/about_us_settings/about_us_settings.py index 5b93cdcede..1ef832ba99 100644 --- a/frappe/website/doctype/about_us_settings/about_us_settings.py +++ b/frappe/website/doctype/about_us_settings/about_us_settings.py @@ -9,11 +9,11 @@ import frappe from frappe.model.document import Document class AboutUsSettings(Document): - + def on_update(self): - from frappe.website.render import clear_cache + from frappe.website.utils import clear_cache clear_cache("about") - + def get_args(): obj = frappe.get_doc("About Us Settings") return { diff --git a/frappe/website/doctype/blog_category/blog_category.py b/frappe/website/doctype/blog_category/blog_category.py index 375ba5b6a3..b8252c993f 100644 --- a/frappe/website/doctype/blog_category/blog_category.py +++ b/frappe/website/doctype/blog_category/blog_category.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals from frappe.website.website_generator import WebsiteGenerator -from frappe.website.render import clear_cache +from frappe.website.utils import clear_cache class BlogCategory(WebsiteGenerator): def autoname(self): diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py index 5671540682..b8a443525f 100644 --- a/frappe/website/doctype/blog_post/blog_post.py +++ b/frappe/website/doctype/blog_post/blog_post.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.website.website_generator import WebsiteGenerator -from frappe.website.render import clear_cache +from frappe.website.utils import clear_cache from frappe.utils import today, cint, global_date_format, get_fullname, strip_html_tags, markdown, sanitize_html from math import ceil from frappe.website.utils import (find_first_image, get_html_content_based_on_type, diff --git a/frappe/website/doctype/blog_settings/blog_settings.py b/frappe/website/doctype/blog_settings/blog_settings.py index 0ed98b9b87..2580806ef1 100644 --- a/frappe/website/doctype/blog_settings/blog_settings.py +++ b/frappe/website/doctype/blog_settings/blog_settings.py @@ -9,8 +9,8 @@ import frappe from frappe.model.document import Document class BlogSettings(Document): - + def on_update(self): - from frappe.website.render import clear_cache + from frappe.website.utils import clear_cache clear_cache("blog") clear_cache("writers") \ No newline at end of file diff --git a/frappe/website/doctype/contact_us_settings/contact_us_settings.py b/frappe/website/doctype/contact_us_settings/contact_us_settings.py index 24e9811a47..c237a13bda 100644 --- a/frappe/website/doctype/contact_us_settings/contact_us_settings.py +++ b/frappe/website/doctype/contact_us_settings/contact_us_settings.py @@ -11,5 +11,5 @@ from frappe.model.document import Document class ContactUsSettings(Document): def on_update(self): - from frappe.website.render import clear_cache + from frappe.website.utils import clear_cache clear_cache("contact") \ No newline at end of file diff --git a/frappe/website/doctype/help_article/help_article.py b/frappe/website/doctype/help_article/help_article.py index fa26cfef99..555d6c9268 100644 --- a/frappe/website/doctype/help_article/help_article.py +++ b/frappe/website/doctype/help_article/help_article.py @@ -93,7 +93,7 @@ def get_sidebar_items(): def clear_cache(): clear_website_cache() - from frappe.website.render import clear_cache + from frappe.website.utils import clear_cache clear_cache() def clear_website_cache(path=None): diff --git a/frappe/website/doctype/portal_settings/portal_settings.py b/frappe/website/doctype/portal_settings/portal_settings.py index 1bfbc70d60..84ee678c1e 100644 --- a/frappe/website/doctype/portal_settings/portal_settings.py +++ b/frappe/website/doctype/portal_settings/portal_settings.py @@ -45,7 +45,7 @@ class PortalSettings(Document): # clear web cache (for menus!) frappe.clear_cache(user='Guest') - from frappe.website.render import clear_cache + from frappe.website.utils import clear_cache clear_cache() # clears role based home pages diff --git a/frappe/website/doctype/web_template/web_template.py b/frappe/website/doctype/web_template/web_template.py index 2fd5bfa179..712cf3c1e4 100644 --- a/frappe/website/doctype/web_template/web_template.py +++ b/frappe/website/doctype/web_template/web_template.py @@ -9,7 +9,7 @@ from shutil import rmtree import frappe from frappe.model.document import Document -from frappe.website.render import clear_cache +from frappe.website.utils import clear_cache from frappe import _ from frappe.modules.export_file import ( write_document_file, diff --git a/frappe/website/doctype/website_script/website_script.py b/frappe/website/doctype/website_script/website_script.py index 5648c27fd6..d1f9f595ed 100644 --- a/frappe/website/doctype/website_script/website_script.py +++ b/frappe/website/doctype/website_script/website_script.py @@ -14,5 +14,5 @@ class WebsiteScript(Document): """clear cache""" frappe.clear_cache(user = 'Guest') - from frappe.website.render import clear_cache + from frappe.website.utils import clear_cache clear_cache() \ No newline at end of file diff --git a/frappe/website/doctype/website_settings/website_settings.py b/frappe/website/doctype/website_settings/website_settings.py index ae8e6bd7e1..e5dec24fbb 100644 --- a/frappe/website/doctype/website_settings/website_settings.py +++ b/frappe/website/doctype/website_settings/website_settings.py @@ -68,7 +68,7 @@ class WebsiteSettings(Document): # clear web cache (for menus!) frappe.clear_cache(user = 'Guest') - from frappe.website.render import clear_cache + from frappe.website.utils import clear_cache clear_cache() # clears role based home pages diff --git a/frappe/website/doctype/website_slideshow/website_slideshow.py b/frappe/website/doctype/website_slideshow/website_slideshow.py index 90f62d1bb1..5e6353c5cd 100644 --- a/frappe/website/doctype/website_slideshow/website_slideshow.py +++ b/frappe/website/doctype/website_slideshow/website_slideshow.py @@ -15,7 +15,7 @@ class WebsiteSlideshow(Document): def on_update(self): # a slide show can be in use and any change in it should get reflected - from frappe.website.render import clear_cache + from frappe.website.utils import clear_cache clear_cache() def validate_images(self): diff --git a/frappe/website/page_renderers/document_page.py b/frappe/website/page_renderers/document_page.py index 34dc42e203..2f272ffd15 100644 --- a/frappe/website/page_renderers/document_page.py +++ b/frappe/website/page_renderers/document_page.py @@ -1,7 +1,7 @@ import frappe from frappe.model.document import get_controller from frappe.website.page_renderers.base_template_page import BaseTemplatePage -from frappe.website.render import build_response +from frappe.website.utils import build_response from frappe.website.router import (get_doctypes_with_web_view, get_page_info_from_web_page_with_dynamic_routes) diff --git a/frappe/website/page_renderers/redirect_page.py b/frappe/website/page_renderers/redirect_page.py index 0bfa8897a3..2049c375e8 100644 --- a/frappe/website/page_renderers/redirect_page.py +++ b/frappe/website/page_renderers/redirect_page.py @@ -1,5 +1,5 @@ import frappe -from frappe.website.render import build_response +from frappe.website.utils import build_response class RedirectPage(object): def __init__(self, path, http_status_code=301): diff --git a/frappe/website/page_renderers/template_page.py b/frappe/website/page_renderers/template_page.py index 467d74b283..d8032c6b9c 100644 --- a/frappe/website/page_renderers/template_page.py +++ b/frappe/website/page_renderers/template_page.py @@ -4,11 +4,9 @@ import click import frappe from frappe.website.page_renderers.base_template_page import BaseTemplatePage -from frappe.website.utils import get_sidebar_items -from frappe.website.render import build_response from frappe.website.router import get_base_template -from frappe.website.utils import (extract_comment_tag, extract_title, - get_next_link, get_toc, get_frontmatter, cache_html) +from frappe.website.utils import (extract_comment_tag, extract_title, get_next_link, + get_toc, get_frontmatter, cache_html, get_sidebar_items, build_response) WEBPAGE_PY_MODULE_PROPERTIES = ("base_template_path", "template", "no_cache", "sitemap", "condition_field") diff --git a/frappe/website/page_renderers/web_page.py b/frappe/website/page_renderers/web_page.py index 0407afe532..5c3807c0ee 100644 --- a/frappe/website/page_renderers/web_page.py +++ b/frappe/website/page_renderers/web_page.py @@ -9,10 +9,6 @@ class WebPage(object): self.path = path.strip('/ ') self.basepath = '' - def get(self): - if self.validate(): - return self.render() - def can_render(self): pass diff --git a/frappe/website/render.py b/frappe/website/render.py deleted file mode 100644 index d5587ad98b..0000000000 --- a/frappe/website/render.py +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -import json -import mimetypes -import re - -from six import iteritems -from werkzeug.wrappers import Response - -import frappe -import frappe.sessions - - -def build_response(path, data, http_status_code, headers=None): - # build response - response = Response() - response.data = set_content_type(response, data, path) - response.status_code = http_status_code - response.headers["X-Page-Name"] = path.encode("ascii", errors="xmlcharrefreplace") - response.headers["X-From-Cache"] = frappe.local.response.from_cache or False - - add_preload_headers(response) - if headers: - for key, val in iteritems(headers): - response.headers[key] = val.encode("ascii", errors="xmlcharrefreplace") - - return response - -def set_content_type(response, data, path): - if isinstance(data, dict): - response.mimetype = 'application/json' - response.charset = 'utf-8' - data = json.dumps(data) - return data - - response.mimetype = 'text/html' - response.charset = 'utf-8' - - # ignore paths ending with .com to avoid unnecessary download - # https://bugs.python.org/issue22347 - if "." in path and not path.endswith('.com'): - content_type, encoding = mimetypes.guess_type(path) - if content_type: - response.mimetype = content_type - if encoding: - response.charset = encoding - - return data - -def add_preload_headers(response): - from bs4 import BeautifulSoup - - try: - preload = [] - soup = BeautifulSoup(response.data, "lxml") - for elem in soup.find_all('script', src=re.compile(".*")): - preload.append(("script", elem.get("src"))) - - for elem in soup.find_all('link', rel="stylesheet"): - preload.append(("style", elem.get("href"))) - - links = [] - for _type, link in preload: - links.append("<{}>; rel=preload; as={}".format(link, _type)) - - if links: - response.headers["Link"] = ",".join(links) - except Exception: - import traceback - traceback.print_exc() - -def clear_cache(path=None): - # TODO: Remove this - from frappe.website.utils import clear_cache - return clear_cache(path) diff --git a/frappe/website/router.py b/frappe/website/router.py index 9922647ebf..23fd5cb21f 100644 --- a/frappe/website/router.py +++ b/frappe/website/router.py @@ -6,7 +6,7 @@ import os import re import frappe -from frappe.website.utils import extract_comment_tag, extract_title +from frappe.website.utils import extract_title from werkzeug.routing import Map, Rule, NotFound def get_page_info_from_web_page_with_dynamic_routes(path): @@ -143,14 +143,12 @@ def get_page_info(path, app, start, basepath=None, app_path=None, fname=None): # get the source setup_source(page_info) - # extract properties from HTML comments - load_properties_from_source(page_info) + if not page_info.title: + page_info.title = extract_title(page_info.source, page_info.route) # extract properties from controller attributes load_properties_from_controller(page_info) - page_info.build_version = frappe.utils.get_build_version() - return page_info def get_frontmatter(string): @@ -252,47 +250,6 @@ def setup_index(page_info): if os.path.exists(index_txt_path): page_info.index = open(index_txt_path, 'r').read().splitlines() -def load_properties_from_source(page_info): - '''Load properties like no_cache, title from source html''' - - if not page_info.title: - page_info.title = extract_title(page_info.source, page_info.route) - - base_template = extract_comment_tag(page_info.source, 'base_template') - if base_template: - page_info.base_template = base_template - - if (page_info.base_template - and "{%- extends" not in page_info.source - and "{% extends" not in page_info.source - and "" not in page_info.source): - page_info.source = '''{{% extends "{0}" %}} - {{% block page_content %}}{1}{{% endblock %}}'''.format(page_info.base_template, page_info.source) - - if "" in page_info.source: - page_info.no_breadcrumbs = 1 - - if "" in page_info.source: - page_info.show_sidebar = 1 - - if "" in page_info.source: - page_info.add_breadcrumbs = 1 - - if "" in page_info.source: - page_info.no_header = 1 - - if "" in page_info.source: - page_info.add_next_prev_links = 1 - - if "" in page_info.source: - page_info.no_cache = 1 - - if "" in page_info.source: - page_info.sitemap = 0 - - if "" in page_info.source: - page_info.sitemap = 1 - def load_properties_from_controller(page_info): if not page_info.controller: return diff --git a/frappe/website/utils.py b/frappe/website/utils.py index 814bd0101e..f57cfa7ec5 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -2,6 +2,7 @@ # MIT License. See license.txt import functools import json +import mimetypes import os import re from functools import wraps @@ -9,6 +10,7 @@ from functools import wraps import yaml from past.builtins import cmp from six import iteritems +from werkzeug.wrappers import Response import frappe from frappe import _ @@ -446,3 +448,61 @@ def cache_html(func): return html return cache_html_decorator + +def build_response(path, data, http_status_code, headers=None): + # build response + response = Response() + response.data = set_content_type(response, data, path) + response.status_code = http_status_code + response.headers["X-Page-Name"] = path.encode("ascii", errors="xmlcharrefreplace") + response.headers["X-From-Cache"] = frappe.local.response.from_cache or False + + add_preload_headers(response) + if headers: + for key, val in iteritems(headers): + response.headers[key] = val.encode("ascii", errors="xmlcharrefreplace") + + return response + +def set_content_type(response, data, path): + if isinstance(data, dict): + response.mimetype = 'application/json' + response.charset = 'utf-8' + data = json.dumps(data) + return data + + response.mimetype = 'text/html' + response.charset = 'utf-8' + + # ignore paths ending with .com to avoid unnecessary download + # https://bugs.python.org/issue22347 + if "." in path and not path.endswith('.com'): + content_type, encoding = mimetypes.guess_type(path) + if content_type: + response.mimetype = content_type + if encoding: + response.charset = encoding + + return data + +def add_preload_headers(response): + from bs4 import BeautifulSoup + + try: + preload = [] + soup = BeautifulSoup(response.data, "lxml") + for elem in soup.find_all('script', src=re.compile(".*")): + preload.append(("script", elem.get("src"))) + + for elem in soup.find_all('link', rel="stylesheet"): + preload.append(("style", elem.get("href"))) + + links = [] + for _type, link in preload: + links.append("<{}>; rel=preload; as={}".format(link, _type)) + + if links: + response.headers["Link"] = ",".join(links) + except Exception: + import traceback + traceback.print_exc() diff --git a/frappe/website/website_generator.py b/frappe/website/website_generator.py index f2638fd86d..8b0a1bfd2d 100644 --- a/frappe/website/website_generator.py +++ b/frappe/website/website_generator.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe.website.utils import cleanup_page_name -from frappe.website.render import clear_cache +from frappe.website.utils import clear_cache from frappe.modules import get_module_name from frappe.search.website_search import update_index_for_path, remove_document_from_index diff --git a/frappe/www/_test/_test_folder/new.csv/__init__.py b/frappe/www/_test/_test_folder/new.csv/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From d88ce8e807fe7acbc5e8c1cb9bec8fd6c52bb432 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 25 May 2021 13:32:20 +0530 Subject: [PATCH 079/164] test: Add test to check replacement of {next} & {index} identifiers --- frappe/website/doctype/web_page/test_web_page.py | 16 +++++++++++----- frappe/www/_test/_test_folder/_test_page.html | 1 + frappe/www/_test/_test_folder/index.md | 3 +++ 3 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 frappe/www/_test/_test_folder/index.md diff --git a/frappe/website/doctype/web_page/test_web_page.py b/frappe/website/doctype/web_page/test_web_page.py index c5d250a4a0..b9bd425781 100644 --- a/frappe/website/doctype/web_page/test_web_page.py +++ b/frappe/website/doctype/web_page/test_web_page.py @@ -91,12 +91,18 @@ class TestWebPage(unittest.TestCase): self.assertTrue('Test Sidebar' in frappe.as_unicode(content)) frappe.flags.look_for_sidebar = False - def test_home_page(self): - content = get_response_content(path='/') - content = get_response_content(path='/index') + def test_index_and_next_comment(self): + content = get_response_content('/_test/_test_folder') + # test if {index} was rendered + self.assertTrue(' Test Page' \ + in frappe.as_unicode(content)) + self.assertTrue(' Test Toc' \ + in frappe.as_unicode(content)) - def test_table_of_content(self): - page = get_response(path='/_test/_test_folder/_test_toc') + content = get_response_content('/_test/_test_folder/_test_page') + # test if {next} was rendered + self.assertTrue('Next: Test Toc' \ + in frappe.as_unicode(content)) def test_meta_tags(self): pass diff --git a/frappe/www/_test/_test_folder/_test_page.html b/frappe/www/_test/_test_folder/_test_page.html index 900f07459d..123d619e38 100644 --- a/frappe/www/_test/_test_folder/_test_page.html +++ b/frappe/www/_test/_test_folder/_test_page.html @@ -1,4 +1,5 @@ {% block content %} {% include "templates/includes/web_sidebar.html" %}

Test content

+{next} {% endblock %} diff --git a/frappe/www/_test/_test_folder/index.md b/frappe/www/_test/_test_folder/index.md new file mode 100644 index 0000000000..d0b531a642 --- /dev/null +++ b/frappe/www/_test/_test_folder/index.md @@ -0,0 +1,3 @@ +# Index + +{index} \ No newline at end of file From ca56b21290b52a1fde8c34db718ee264756274ef Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 25 May 2021 15:01:20 +0530 Subject: [PATCH 080/164] test: Check all cases of setting homepage --- frappe/tests/test_website.py | 63 ++++++++++++++++++++++++----- frappe/website/utils.py | 3 ++ frappe/www/_test/_test_home_page.py | 2 + 3 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 frappe/www/_test/_test_home_page.py diff --git a/frappe/tests/test_website.py b/frappe/tests/test_website.py index cc500d1c02..6b9530a8ce 100644 --- a/frappe/tests/test_website.py +++ b/frappe/tests/test_website.py @@ -4,7 +4,7 @@ import unittest import frappe from frappe.website.serve import get_response -from frappe.website.utils import get_home_page +from frappe.website.utils import get_home_page, clear_website_cache from frappe.utils import set_request class TestWebsite(unittest.TestCase): @@ -14,32 +14,29 @@ class TestWebsite(unittest.TestCase): def tearDown(self): frappe.set_user('Administrator') - def test_home_page_for_role(self): + def test_home_page(self): frappe.set_user('Administrator') - frappe.delete_doc_if_exists('User', 'test-user-for-home-page@example.com') - frappe.delete_doc_if_exists('Role', 'home-page-test') - frappe.delete_doc_if_exists('Web Page', 'home-page-test') + # test home page via role user = frappe.get_doc(dict( doctype='User', email='test-user-for-home-page@example.com', - first_name='test')).insert() + first_name='test')).insert(ignore_if_duplicate=True) role = frappe.get_doc(dict( doctype = 'Role', role_name = 'home-page-test', desk_access = 0, - home_page = '/home-page-test' - )).insert() + )).insert(ignore_if_duplicate=True) user.add_roles(role.name) user.save() + frappe.db.set_value('Role', 'home-page-test', 'home_page', 'home-page-test') frappe.set_user('test-user-for-home-page@example.com') self.assertEqual(get_home_page(), 'home-page-test') frappe.set_user('Administrator') - role.home_page = '' - role.save() + frappe.db.set_value('Role', 'home-page-test', 'home_page', '') # home page via portal settings frappe.db.set_value('Portal Settings', None, 'default_portal_home', 'test-portal-home') @@ -48,6 +45,42 @@ class TestWebsite(unittest.TestCase): frappe.cache().hdel('home_page', frappe.session.user) self.assertEqual(get_home_page(), 'test-portal-home') + frappe.db.set_value("Portal Settings", None, "default_portal_home", '') + clear_website_cache() + + # home page via website settings + frappe.db.set_value("Website Settings", None, "home_page", 'contact') + self.assertEqual(get_home_page(), 'contact') + + frappe.db.set_value("Website Settings", None, "home_page", None) + clear_website_cache() + + # fallback homepage + self.assertEqual(get_home_page(), 'me') + + # fallback homepage for guest + frappe.set_user('Guest') + self.assertEqual(get_home_page(), 'login') + frappe.set_user('Administrator') + + # test homepage via hooks + clear_website_cache() + set_home_page_hook('get_website_user_home_page', 'frappe.www._test._test_home_page.get_website_user_home_page') + self.assertEqual(get_home_page(), '_test/_test_folder') + + clear_website_cache() + set_home_page_hook('website_user_home_page', 'login') + self.assertEqual(get_home_page(), 'login') + + clear_website_cache() + set_home_page_hook('home_page', 'about') + self.assertEqual(get_home_page(), 'about') + + clear_website_cache() + set_home_page_hook('role_home_page', {'home-page-test': 'home-page-test'}) + self.assertEqual(get_home_page(), 'home-page-test') + + def test_page_load(self): set_request(method='POST', path='login') response = get_response() @@ -147,3 +180,13 @@ class TestWebsite(unittest.TestCase): delattr(frappe.hooks, 'website_redirects') frappe.cache().delete_key('app_hooks') + + +def set_home_page_hook(key, value): + from frappe import hooks + # reset home_page hooks + for hook in ('get_website_user_home_page','website_user_home_page','role_home_page','home_page'): + if hasattr(hooks, hook): + delattr(hooks, hook) + + setattr(hooks, key, value) diff --git a/frappe/website/utils.py b/frappe/website/utils.py index f57cfa7ec5..a6d9535ec6 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -328,6 +328,9 @@ def clear_cache(path=None): for method in frappe.get_hooks("website_clear_cache"): frappe.get_attr(method)(path) +def clear_website_cache(path=None): + clear_cache(path) + def clear_sitemap(): delete_page_cache("*") diff --git a/frappe/www/_test/_test_home_page.py b/frappe/www/_test/_test_home_page.py new file mode 100644 index 0000000000..936399c700 --- /dev/null +++ b/frappe/www/_test/_test_home_page.py @@ -0,0 +1,2 @@ +def get_website_user_home_page(user): + return '/_test/_test_folder' \ No newline at end of file From e6d701b038545fbdba72d7d968776bcb0f5f7356 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 25 May 2021 15:02:18 +0530 Subject: [PATCH 081/164] refactor: Use clear_website_cache instead of clear_cache --- frappe/cache_manager.py | 2 +- frappe/commands/utils.py | 4 ++-- frappe/installer.py | 2 +- frappe/migrate.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/cache_manager.py b/frappe/cache_manager.py index 516f13de39..6b937731b0 100644 --- a/frappe/cache_manager.py +++ b/frappe/cache_manager.py @@ -55,7 +55,7 @@ def clear_domain_cache(user=None): cache.delete_value(domain_cache_keys) def clear_global_cache(): - from frappe.website.utils import clear_cache as clear_website_cache + from frappe.website.utils import clear_website_cache clear_doctype_cache() clear_website_cache() diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index a6937a5dc4..b45f36ad0b 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -69,7 +69,7 @@ def watch(apps=None): def clear_cache(context): "Clear cache, doctype cache and defaults" import frappe.sessions - from frappe.website.utils import clear_cache as clear_website_cache + from frappe.website.utils import clear_website_cache from frappe.desk.notifications import clear_notifications for site in context.sites: try: @@ -86,7 +86,7 @@ def clear_cache(context): @pass_context def clear_website_cache(context): "Clear website cache" - from frappe.website.utils import clear_cache as clear_website_cache + from frappe.website.utils import clear_website_cache for site in context.sites: try: frappe.init(site=site) diff --git a/frappe/installer.py b/frappe/installer.py index 138d5abc1b..b81d0a03b4 100755 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -282,7 +282,7 @@ def remove_app(app_name, dry_run=False, yes=False, no_backup=False, force=False) def post_install(rebuild_website=False): - from frappe.website.utils import clear_cache as clear_website_cache + from frappe.website.utils import clear_website_cache if rebuild_website: clear_website_cache() diff --git a/frappe/migrate.py b/frappe/migrate.py index 0fe7c2e034..ada0860ae4 100644 --- a/frappe/migrate.py +++ b/frappe/migrate.py @@ -15,7 +15,7 @@ from frappe.utils.connections import check_connection from frappe.utils.dashboard import sync_dashboards from frappe.cache_manager import clear_global_cache from frappe.desk.notifications import clear_notifications -from frappe.website.utils import clear_cache as clear_website_cache +from frappe.website.utils import clear_website_cache from frappe.core.doctype.language.language import sync_languages from frappe.modules.utils import sync_customizations from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs From 78206bea8fb2894138f0c9b6076c2cedf976c827 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 25 May 2021 16:39:48 +0530 Subject: [PATCH 082/164] test: Clear hook cache after updating hook --- frappe/tests/test_website.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frappe/tests/test_website.py b/frappe/tests/test_website.py index 6b9530a8ce..69425cdc45 100644 --- a/frappe/tests/test_website.py +++ b/frappe/tests/test_website.py @@ -146,9 +146,6 @@ class TestWebsite(unittest.TestCase): }) website_settings.save() - frappe.cache().delete_key('app_hooks') - frappe.cache().delete_key('website_redirects') - set_request(method='GET', path='/testfrom') response = get_response() self.assertEqual(response.status_code, 301) @@ -190,3 +187,5 @@ def set_home_page_hook(key, value): delattr(hooks, hook) setattr(hooks, key, value) + frappe.cache().delete_key('app_hooks') + From 886d16c87177d4a2954616ae6dcd1799be1db876 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 25 May 2021 19:45:53 +0200 Subject: [PATCH 083/164] feat: add context to confirm dailog --- frappe/public/js/frappe/ui/messages.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/ui/messages.js b/frappe/public/js/frappe/ui/messages.js index 2e8ba7d206..f465250af9 100644 --- a/frappe/public/js/frappe/ui/messages.js +++ b/frappe/public/js/frappe/ui/messages.js @@ -26,13 +26,13 @@ frappe.throw = function(msg) { frappe.confirm = function(message, confirm_action, reject_action) { var d = new frappe.ui.Dialog({ - title: __("Confirm"), - primary_action_label: __("Yes"), + title: __("Confirm", null, "Title of confirmation dialog"), + primary_action_label: __("Yes", null, "Approve confirmation dialog"), primary_action: () => { confirm_action && confirm_action(); d.hide(); }, - secondary_action_label: __("No"), + secondary_action_label: __("No", null, "Dismiss confirmation dialog"), secondary_action: () => d.hide(), }); From d3121d753ead5511ecc6a0e57e6e6825901c68ad Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 25 May 2021 19:47:15 +0200 Subject: [PATCH 084/164] feat: add context to prompt dialog --- frappe/public/js/frappe/ui/messages.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/ui/messages.js b/frappe/public/js/frappe/ui/messages.js index f465250af9..067fed233c 100644 --- a/frappe/public/js/frappe/ui/messages.js +++ b/frappe/public/js/frappe/ui/messages.js @@ -88,9 +88,9 @@ frappe.prompt = function(fields, callback, title, primary_label) { if(!$.isArray(fields)) fields = [fields]; var d = new frappe.ui.Dialog({ fields: fields, - title: title || __("Enter Value"), + title: title || __("Enter Value", null, "Title of prompt dialog"), }); - d.set_primary_action(primary_label || __("Submit"), function() { + d.set_primary_action(primary_label || __("Submit", null, "Primary action of prompt dialog"), function() { var values = d.get_values(); if(!values) { return; From d7d0ddf6adee391714e5b4f31ab559fe61c322c5 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Wed, 26 May 2021 02:54:04 +0000 Subject: [PATCH 085/164] fix: package.json & yarn.lock to reduce vulnerabilities The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JS-NORMALIZEURL-1296539 --- package.json | 2 +- yarn.lock | 1022 ++++++++++++++++++++------------------------------ 2 files changed, 418 insertions(+), 606 deletions(-) diff --git a/package.json b/package.json index e666a2e1ce..25bf0b0695 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "bootstrap": "4.5.0", "cliui": "^7.0.4", "cookie": "^0.4.0", - "cssnano": "^4.1.10", + "cssnano": "^5.0.0", "driver.js": "^0.9.8", "express": "^4.17.1", "fast-deep-equal": "^2.0.1", diff --git a/yarn.lock b/yarn.lock index 86719d81f4..96a6059ecc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,6 +7,27 @@ resolved "https://registry.yarnpkg.com/@assemblyscript/loader/-/loader-0.10.1.tgz#70e45678f06c72fa2e350e8553ec4a4d72b92e06" integrity sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg== +"@babel/code-frame@^7.0.0": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" + integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== + dependencies: + "@babel/highlight" "^7.12.13" + +"@babel/helper-validator-identifier@^7.14.0": + version "7.14.0" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz#d26cad8a47c65286b15df1547319a5d0bcf27288" + integrity sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A== + +"@babel/highlight@^7.12.13": + version "7.14.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.0.tgz#3197e375711ef6bf834e67d0daec88e4f46113cf" + integrity sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg== + dependencies: + "@babel/helper-validator-identifier" "^7.14.0" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@deepcode/dcignore@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@deepcode/dcignore/-/dcignore-1.0.2.tgz#39e4a3df7dde8811925330506e4bb3fbf3c288d8" @@ -361,6 +382,11 @@ dependencies: defer-to-connect "^2.0.0" +"@trysound/sax@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.1.1.tgz#3348564048e7a2d7398c935d466c0414ebb6a669" + integrity sha512-Z6DoceYb/1xSg5+e+ZlPZ9v0N16ZvZ+wYMraFue4HYrE4ttONKtsvruIRf6t9TBR0YvSOfi1hUU0fJfBLCDYow== + "@types/babel-types@*", "@types/babel-types@^7.0.0": version "7.0.9" resolved "https://registry.yarnpkg.com/@types/babel-types/-/babel-types-7.0.9.tgz#01d7b86949f455402a94c788883fe4ba574cad41" @@ -463,10 +489,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.4.tgz#76c3cb3a12909510f52e5dc04a6298cdf9504ffd" integrity sha512-oVeL12C6gQS/GAExndigSaLxTrKpQPxewx9bOcwfvJiJge4rr7wNaph4J+ns5hrmIV2as5qxqN8YKthn9qh0jw== -"@types/q@^1.5.1": - version "1.5.1" - resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.1.tgz#48fd98c1561fe718b61733daed46ff115b496e18" - integrity sha512-eqz8c/0kwNi/OEHQfvIuJVLTst3in0e7uTKeuY+WL/zfKn0xVujOTp42bS/vUUokhK5P2BppLd9JXMOMHcgbjA== +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== "@types/responselike@*", "@types/responselike@^1.0.0": version "1.0.0" @@ -595,7 +621,7 @@ align-text@^0.1.1, align-text@^0.1.3: longest "^1.0.1" repeat-string "^1.5.2" -alphanum-sort@^1.0.0: +alphanum-sort@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= @@ -958,7 +984,7 @@ body-parser@1.19.0: raw-body "2.4.0" type-is "~1.6.17" -boolbase@^1.0.0, boolbase@~1.0.0: +boolbase@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= @@ -1009,7 +1035,7 @@ browserify-zlib@^0.1.4: dependencies: pako "~0.2.0" -browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.16.3: +browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.16.0, browserslist@^4.16.3: version "4.16.6" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ== @@ -1077,24 +1103,10 @@ call-bind@^1.0.2: function-bind "^1.1.1" get-intrinsic "^1.0.2" -caller-callsite@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" - integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= - dependencies: - callsites "^2.0.0" - -caller-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" - integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= - dependencies: - caller-callsite "^2.0.0" - -callsites@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" - integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== camelcase-keys@^2.0.0: version "2.1.0" @@ -1166,7 +1178,7 @@ chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.3.0, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.0.0, chalk@^2.3.0, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1313,21 +1325,12 @@ clone@^2.1.1, clone@^2.1.2: resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= -coa@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" - integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== - dependencies: - "@types/q" "^1.5.1" - chalk "^2.4.1" - q "^1.1.2" - code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= -color-convert@^1.9.0, color-convert@^1.9.1: +color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== @@ -1346,26 +1349,15 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -color-name@^1.0.0, color-name@~1.1.4: +color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.5.2: - version "1.5.3" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" - integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/color/-/color-3.1.0.tgz#d8e9fb096732875774c84bf922815df0308d0ffc" - integrity sha512-CwyopLkuRYO5ei2EpzpIh6LqJMt6Mt+jZhO5VI5f/wJLZriXQE32/SSqzmrh+QB+AZT81Cj8yv+7zwToW8ahZg== - dependencies: - color-convert "^1.9.1" - color-string "^1.5.2" +colord@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/colord/-/colord-2.0.0.tgz#f8c19f2526b7dc5b22d6e57ef102f03a2a43a3d8" + integrity sha512-WMDFJfoY3wqPZNpKUFdse3HhD5BHCbE9JCdxRzoVH+ywRITGOeWAHNkGEmyxLlErEpN9OLMWgdM9dWQtDk5dog== colorette@^1.2.1, colorette@^1.2.2: version "1.2.2" @@ -1379,6 +1371,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +commander@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + component-bind@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" @@ -1492,16 +1489,16 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -cosmiconfig@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.1.0.tgz#6c5c35e97f37f985061cdf653f114784231185cf" - integrity sha512-kCNPvthka8gvLtzAxQXvWo4FxqRB+ftRZyPZNuab5ngvM9Y7yw7hbEysglptLgpkGX9nAOKTBVkHUAe8xtYR6Q== +cosmiconfig@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" + integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== dependencies: - import-fresh "^2.0.0" - is-directory "^0.3.1" - js-yaml "^3.9.0" - lodash.get "^4.4.2" - parse-json "^4.0.0" + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" cross-spawn@^3.0.0: version "3.0.1" @@ -1532,17 +1529,21 @@ crypto-random-string@^2.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== -css-color-names@0.0.4, css-color-names@^0.0.4: +css-color-names@^0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= -css-declaration-sorter@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz#c198940f63a76d7e36c1e71018b001721054cb22" - integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA== +css-color-names@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-1.0.1.tgz#6ff7ee81a823ad46e020fa2fd6ab40a887e2ba67" + integrity sha512-/loXYOch1qU1biStIFsHH8SxTmOseh1IJqFvy8IujXOm1h+QjUdDhkzOrR5HG8K8mlxREj0yfi8ewCHx0eMxzA== + +css-declaration-sorter@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.0.3.tgz#9dfd8ea0df4cc7846827876fafb52314890c21a9" + integrity sha512-52P95mvW1SMzuRZegvpluT6yEv0FqQusydKQPZsNN5Q7hh8EwQvN8E2nwuJ16BBvNN6LcoIZXu/Bk58DAhrrxw== dependencies: - postcss "^7.0.1" timsort "^0.3.0" css-parse@~2.0.0: @@ -1552,20 +1553,16 @@ css-parse@~2.0.0: dependencies: css "^2.0.0" -css-select-base-adapter@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" - integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== - -css-select@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.0.2.tgz#ab4386cec9e1f668855564b17c3733b43b2a5ede" - integrity sha512-dSpYaDVoWaELjvZ3mS6IKZM/y2PMPa/XYoEfYNZePL4U/XgyxZNroHEHReDx/d+VgXh9VbCTtFqLkFbmeqeaRQ== +css-select@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-3.1.2.tgz#d52cbdc6fee379fba97fb0d3925abbd18af2d9d8" + integrity sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA== dependencies: boolbase "^1.0.0" - css-what "^2.1.2" - domutils "^1.7.0" - nth-check "^1.0.2" + css-what "^4.0.0" + domhandler "^4.0.0" + domutils "^2.4.3" + nth-check "^2.0.0" css-selector-tokenizer@^0.7.0: version "0.7.1" @@ -1576,36 +1573,18 @@ css-selector-tokenizer@^0.7.0: fastparse "^1.1.1" regexpu-core "^1.0.0" -css-tree@1.0.0-alpha.28: - version "1.0.0-alpha.28" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.28.tgz#8e8968190d886c9477bc8d61e96f61af3f7ffa7f" - integrity sha512-joNNW1gCp3qFFzj4St6zk+Wh/NBv0vM5YbEreZk0SD4S23S+1xBKb6cLDg2uj4P4k/GUMlIm6cKIDqIG+vdt0w== +css-tree@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== dependencies: - mdn-data "~1.1.0" - source-map "^0.5.3" + mdn-data "2.0.14" + source-map "^0.6.1" -css-tree@1.0.0-alpha.29: - version "1.0.0-alpha.29" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.29.tgz#3fa9d4ef3142cbd1c301e7664c1f352bd82f5a39" - integrity sha512-sRNb1XydwkW9IOci6iB2xmy8IGCj6r/fr+JWitvJ2JxQRPzN3T4AGGVWCMlVmVwM1gtgALJRmGIlWv5ppnGGkg== - dependencies: - mdn-data "~1.1.0" - source-map "^0.5.3" - -css-unit-converter@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.1.tgz#d9b9281adcfd8ced935bdbaba83786897f64e996" - integrity sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY= - -css-url-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/css-url-regex/-/css-url-regex-1.1.0.tgz#83834230cc9f74c457de59eebd1543feeb83b7ec" - integrity sha1-g4NCMMyfdMRX3lnuvRVD/uuDt+w= - -css-what@^2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" - integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== +css-what@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-4.0.0.tgz#35e73761cab2eeb3d3661126b23d7aa0e8432233" + integrity sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A== css@^2.0.0: version "2.2.4" @@ -1622,90 +1601,66 @@ cssesc@^0.1.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" integrity sha1-yBSQPkViM3GgR3tAEJqq++6t27Q= -cssesc@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703" - integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg== - cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -cssnano-preset-default@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76" - integrity sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA== +cssnano-preset-default@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.1.tgz#5cd783caed942cc94159aeb10583af4691445b8c" + integrity sha512-kAhR71Tascmnjlhl4UegGA3KGGbMLXHkkqVpA9idsRT1JmIhIsz1C3tDpBeQMUw5EX5Rfb1HGc/PRqD2AFk3Vg== dependencies: - css-declaration-sorter "^4.0.1" - cssnano-util-raw-cache "^4.0.1" - postcss "^7.0.0" - postcss-calc "^7.0.1" - postcss-colormin "^4.0.3" - postcss-convert-values "^4.0.1" - postcss-discard-comments "^4.0.2" - postcss-discard-duplicates "^4.0.2" - postcss-discard-empty "^4.0.1" - postcss-discard-overridden "^4.0.1" - postcss-merge-longhand "^4.0.11" - postcss-merge-rules "^4.0.3" - postcss-minify-font-values "^4.0.2" - postcss-minify-gradients "^4.0.2" - postcss-minify-params "^4.0.2" - postcss-minify-selectors "^4.0.2" - postcss-normalize-charset "^4.0.1" - postcss-normalize-display-values "^4.0.2" - postcss-normalize-positions "^4.0.2" - postcss-normalize-repeat-style "^4.0.2" - postcss-normalize-string "^4.0.2" - postcss-normalize-timing-functions "^4.0.2" - postcss-normalize-unicode "^4.0.1" - postcss-normalize-url "^4.0.1" - postcss-normalize-whitespace "^4.0.2" - postcss-ordered-values "^4.1.2" - postcss-reduce-initial "^4.0.3" - postcss-reduce-transforms "^4.0.2" - postcss-svgo "^4.0.2" - postcss-unique-selectors "^4.0.1" + css-declaration-sorter "^6.0.3" + cssnano-utils "^2.0.1" + postcss-calc "^8.0.0" + postcss-colormin "^5.1.1" + postcss-convert-values "^5.0.1" + postcss-discard-comments "^5.0.1" + postcss-discard-duplicates "^5.0.1" + postcss-discard-empty "^5.0.1" + postcss-discard-overridden "^5.0.1" + postcss-merge-longhand "^5.0.2" + postcss-merge-rules "^5.0.1" + postcss-minify-font-values "^5.0.1" + postcss-minify-gradients "^5.0.1" + postcss-minify-params "^5.0.1" + postcss-minify-selectors "^5.1.0" + postcss-normalize-charset "^5.0.1" + postcss-normalize-display-values "^5.0.1" + postcss-normalize-positions "^5.0.1" + postcss-normalize-repeat-style "^5.0.1" + postcss-normalize-string "^5.0.1" + postcss-normalize-timing-functions "^5.0.1" + postcss-normalize-unicode "^5.0.1" + postcss-normalize-url "^5.0.1" + postcss-normalize-whitespace "^5.0.1" + postcss-ordered-values "^5.0.1" + postcss-reduce-initial "^5.0.1" + postcss-reduce-transforms "^5.0.1" + postcss-svgo "^5.0.1" + postcss-unique-selectors "^5.0.1" -cssnano-util-get-arguments@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f" - integrity sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8= +cssnano-utils@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-2.0.1.tgz#8660aa2b37ed869d2e2f22918196a9a8b6498ce2" + integrity sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ== -cssnano-util-get-match@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d" - integrity sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0= - -cssnano-util-raw-cache@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz#b26d5fd5f72a11dfe7a7846fb4c67260f96bf282" - integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA== +cssnano@^5.0.0: + version "5.0.4" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.4.tgz#5ca90729c94c71c4bc3d45abb543be10740bf381" + integrity sha512-I+fDW74CJ4yb31765ov9xXe70XLZvFTXjwhmA//VgAAuSAU34Oblbe94Q9zffiCX1VhcSfQWARQnwhz+Nqgb4Q== dependencies: - postcss "^7.0.0" + cosmiconfig "^7.0.0" + cssnano-preset-default "^5.1.1" + is-resolvable "^1.1.0" -cssnano-util-same-parent@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" - integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== - -cssnano@^4.1.10: - version "4.1.10" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2" - integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ== +csso@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" + integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== dependencies: - cosmiconfig "^5.0.0" - cssnano-preset-default "^4.0.7" - is-resolvable "^1.0.0" - postcss "^7.0.0" - -csso@^3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/csso/-/csso-3.5.1.tgz#7b9eb8be61628973c1b261e169d2f024008e758b" - integrity sha512-vrqULLffYU1Q2tLdJvaCYbONStnfkfimRxXNaGjxMldI0C7JPBC4rB1RyjhfdZ4m1frm8pM9uRPKH3d2knZ8gg== - dependencies: - css-tree "1.0.0-alpha.29" + css-tree "^1.1.2" currently-unhandled@^0.4.1: version "0.4.1" @@ -1928,33 +1883,35 @@ doctypes@^1.1.0: resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9" integrity sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk= -dom-serializer@0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" - integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA== +dom-serializer@^1.0.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" + integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== dependencies: - domelementtype "^1.3.0" - entities "^1.1.1" + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" -domelementtype@1, domelementtype@^1.3.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" - integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== +domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" + integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== -domutils@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" - integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== +domhandler@^4.0.0, domhandler@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.0.tgz#f9768a5f034be60a89a27c2e4d0f74eba0d8b059" + integrity sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA== dependencies: - dom-serializer "0" - domelementtype "1" + domelementtype "^2.2.0" -dot-prop@^4.1.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.1.tgz#45884194a71fc2cda71cbb4bceb3a4dd2f433ba4" - integrity sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ== +domutils@^2.4.3: + version "2.6.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.6.0.tgz#2e15c04185d43fb16ae7057cb76433c6edb938b7" + integrity sha512-y0BezHuy4MDYxh6OvolXYsH+1EMGmFbwv5FKW7ovwMG6zTPWqNPq3WF9ayZssFq+UlKdffGLbOEaghNdaOm1WA== dependencies: - is-obj "^1.0.0" + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" dot-prop@^5.2.0: version "5.2.0" @@ -2102,10 +2059,10 @@ engine.io@~3.5.0: engine.io-parser "~2.2.0" ws "~7.4.2" -entities@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" - integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== errno@^0.1.1: version "0.1.7" @@ -2121,18 +2078,6 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.12.0, es-abstract@^1.5.1: - version "1.13.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" - integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== - dependencies: - es-to-primitive "^1.2.0" - function-bind "^1.1.1" - has "^1.0.3" - is-callable "^1.1.4" - is-regex "^1.0.4" - object-keys "^1.0.12" - es-abstract@^1.17.0-next.1, es-abstract@^1.17.4, es-abstract@^1.17.5: version "1.17.5" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" @@ -2163,15 +2108,6 @@ es-get-iterator@^1.1.0: is-string "^1.0.5" isarray "^2.0.5" -es-to-primitive@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" - integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -2914,7 +2850,7 @@ has-yarn@^2.1.0: resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== -has@^1.0.0, has@^1.0.1, has@^1.0.3: +has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== @@ -2993,11 +2929,6 @@ hsla-regex@^1.0.0: resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= -html-comment-regex@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" - integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== - http-cache-semantics@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" @@ -3084,13 +3015,13 @@ immediate@~3.0.5: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= -import-fresh@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" - integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== dependencies: - caller-path "^2.0.0" - resolve-from "^3.0.0" + parent-module "^1.0.0" + resolve-from "^4.0.0" import-lazy@^2.1.0: version "2.1.0" @@ -3162,10 +3093,10 @@ ipaddr.js@1.9.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== -is-absolute-url@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" - integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= +is-absolute-url@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" + integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== is-arguments@^1.0.4: version "1.0.4" @@ -3177,11 +3108,6 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - is-bigint@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.0.tgz#73da8c33208d00f130e9b5e15d23eac9215601c4" @@ -3221,7 +3147,7 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" -is-color-stop@^1.0.0: +is-color-stop@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U= @@ -3255,11 +3181,6 @@ is-deflate@^1.0.0: resolved "https://registry.yarnpkg.com/is-deflate/-/is-deflate-1.0.0.tgz#c862901c3c161fb09dac7cdc7e784f80e98f2f14" integrity sha1-yGKQHDwWH7CdrHzcfnhPgOmPLxQ= -is-directory@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" - integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= - is-docker@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.0.0.tgz#2cb0df0e75e2d064fe1864c37cdeacb7b2dcf25b" @@ -3350,11 +3271,6 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-obj@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" - integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= - is-obj@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" @@ -3378,13 +3294,6 @@ is-regex@^1.0.3: call-bind "^1.0.2" has-symbols "^1.0.1" -is-regex@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" - integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= - dependencies: - has "^1.0.1" - is-regex@^1.0.5: version "1.1.0" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff" @@ -3392,7 +3301,7 @@ is-regex@^1.0.5: dependencies: has-symbols "^1.0.1" -is-resolvable@^1.0.0: +is-resolvable@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== @@ -3412,13 +3321,6 @@ is-string@^1.0.4, is-string@^1.0.5: resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== -is-svg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" - integrity sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ== - dependencies: - html-comment-regex "^1.1.0" - is-symbol@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" @@ -3543,7 +3445,12 @@ js-stringify@^1.0.1: resolved "https://registry.yarnpkg.com/js-stringify/-/js-stringify-1.0.2.tgz#1736fddfd9724f28a3682adc6230ae7e4e9679db" integrity sha1-Fzb939lyTyijaCrcYjCufk6Weds= -js-yaml@^3.12.0, js-yaml@^3.13.1, js-yaml@^3.9.0: +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: version "3.14.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== @@ -3576,10 +3483,10 @@ json-buffer@3.0.1: resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== -json-parse-better-errors@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json-schema-traverse@^0.4.1: version "0.4.1" @@ -3740,6 +3647,11 @@ lie@~3.3.0: dependencies: immediate "~3.0.5" +lines-and-columns@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" + integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -4129,10 +4041,10 @@ md5@^2.3.0: crypt "0.0.2" is-buffer "~1.1.6" -mdn-data@~1.1.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-1.1.4.tgz#50b5d4ffc4575276573c4eedb8780812a8419f01" - integrity sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA== +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== media-typer@0.3.0: version "0.3.0" @@ -4248,11 +4160,6 @@ minimatch@^3.0.4, minimatch@~3.0.2: dependencies: brace-expansion "^1.1.7" -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - minimist@^1.1.3, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" @@ -4290,13 +4197,6 @@ mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mkdirp@~0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= - dependencies: - minimist "0.0.8" - moment-timezone@^0.5.28: version "0.5.28" resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.28.tgz#f093d789d091ed7b055d82aa81a82467f72e4338" @@ -4505,16 +4405,16 @@ normalize-range@^0.1.2: resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= -normalize-url@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" - integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== - normalize-url@^4.1.0: version "4.5.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== +normalize-url@^4.5.0: + version "4.5.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -4532,12 +4432,12 @@ npm-run-path@^2.0.0: gauge "~2.7.3" set-blocking "~2.0.0" -nth-check@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" - integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== +nth-check@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.0.tgz#1bb4f6dac70072fc313e8c9cd1417b5074c0a125" + integrity sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q== dependencies: - boolbase "~1.0.0" + boolbase "^1.0.0" num2fraction@^1.2.2: version "1.2.2" @@ -4597,24 +4497,6 @@ object.assign@^4.1.0: has-symbols "^1.0.0" object-keys "^1.0.11" -object.getownpropertydescriptors@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" - integrity sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY= - dependencies: - define-properties "^1.1.2" - es-abstract "^1.5.1" - -object.values@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.0.tgz#bf6810ef5da3e5325790eaaa2be213ea84624da9" - integrity sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.12.0" - function-bind "^1.1.1" - has "^1.0.3" - omggif@^1.0.5: version "1.0.10" resolved "https://registry.yarnpkg.com/omggif/-/omggif-1.0.10.tgz#ddaaf90d4a42f532e9e7cb3a95ecdd47f17c7b19" @@ -4765,6 +4647,13 @@ parchment@^1.1.4: resolved "https://registry.yarnpkg.com/parchment/-/parchment-1.1.4.tgz#aeded7ab938fe921d4c34bc339ce1168bc2ffde5" integrity sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg== +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + parse-data-uri@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/parse-data-uri/-/parse-data-uri-0.2.0.tgz#bf04d851dd5c87b0ab238e5d01ace494b604b4c9" @@ -4779,13 +4668,15 @@ parse-json@^2.2.0: dependencies: error-ex "^1.2.0" -parse-json@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: + "@babel/code-frame" "^7.0.0" error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" parse-link-header@^1.0.1: version "1.0.1" @@ -4860,6 +4751,11 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + peek-stream@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/peek-stream/-/peek-stream-1.1.3.tgz#3b35d84b7ccbbd262fff31dc10da56856ead6d67" @@ -4938,124 +4834,104 @@ popper.js@^1.16.0: resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ== -postcss-calc@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.1.tgz#36d77bab023b0ecbb9789d84dcb23c4941145436" - integrity sha512-oXqx0m6tb4N3JGdmeMSc/i91KppbYsFZKdH0xMOqK8V1rJlzrKlTdokz8ozUXLVejydRN6u2IddxpcijRj2FqQ== +postcss-calc@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.0.0.tgz#a05b87aacd132740a5db09462a3612453e5df90a" + integrity sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g== dependencies: - css-unit-converter "^1.1.1" - postcss "^7.0.5" - postcss-selector-parser "^5.0.0-rc.4" - postcss-value-parser "^3.3.1" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.0.2" -postcss-colormin@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" - integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw== +postcss-colormin@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.1.1.tgz#834d262f6021f832d9085e355f08ade288a92a1d" + integrity sha512-SyTmqKKN6PyYNeeKEC0hqIP5CDuprO1hHurdW1aezDyfofDUOn7y7MaxcolbsW3oazPwFiGiY30XRiW1V4iZpA== dependencies: - browserslist "^4.0.0" - color "^3.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + browserslist "^4.16.0" + colord "^2.0.0" + postcss-value-parser "^4.1.0" -postcss-convert-values@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f" - integrity sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ== +postcss-convert-values@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.0.1.tgz#4ec19d6016534e30e3102fdf414e753398645232" + integrity sha512-C3zR1Do2BkKkCgC0g3sF8TS0koF2G+mN8xxayZx3f10cIRmTaAnpgpRQZjNekTZxM2ciSPoh2IWJm0VZx8NoQg== dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.1.0" -postcss-discard-comments@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" - integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg== - dependencies: - postcss "^7.0.0" +postcss-discard-comments@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz#9eae4b747cf760d31f2447c27f0619d5718901fe" + integrity sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg== -postcss-discard-duplicates@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb" - integrity sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ== - dependencies: - postcss "^7.0.0" +postcss-discard-duplicates@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.1.tgz#68f7cc6458fe6bab2e46c9f55ae52869f680e66d" + integrity sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA== -postcss-discard-empty@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765" - integrity sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w== - dependencies: - postcss "^7.0.0" +postcss-discard-empty@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz#ee136c39e27d5d2ed4da0ee5ed02bc8a9f8bf6d8" + integrity sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw== -postcss-discard-overridden@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57" - integrity sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg== - dependencies: - postcss "^7.0.0" +postcss-discard-overridden@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz#454b41f707300b98109a75005ca4ab0ff2743ac6" + integrity sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q== -postcss-merge-longhand@^4.0.11: - version "4.0.11" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" - integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw== +postcss-merge-longhand@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.0.2.tgz#277ada51d9a7958e8ef8cf263103c9384b322a41" + integrity sha512-BMlg9AXSI5G9TBT0Lo/H3PfUy63P84rVz3BjCFE9e9Y9RXQZD3+h3YO1kgTNsNJy7bBc1YQp8DmSnwLIW5VPcw== dependencies: - css-color-names "0.0.4" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - stylehacks "^4.0.0" + css-color-names "^1.0.1" + postcss-value-parser "^4.1.0" + stylehacks "^5.0.1" -postcss-merge-rules@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650" - integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ== +postcss-merge-rules@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.0.1.tgz#4ff61c5089d86845184a0f149e88d687028bef7e" + integrity sha512-UR6R5Ph0c96QB9TMBH3ml8/kvPCThPHepdhRqAbvMRDRHQACPC8iM5NpfIC03+VRMZTGXy4L/BvFzcDFCgb+fA== dependencies: - browserslist "^4.0.0" + browserslist "^4.16.0" caniuse-api "^3.0.0" - cssnano-util-same-parent "^4.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - vendors "^1.0.0" + cssnano-utils "^2.0.1" + postcss-selector-parser "^6.0.5" + vendors "^1.0.3" -postcss-minify-font-values@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6" - integrity sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg== +postcss-minify-font-values@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.0.1.tgz#a90cefbfdaa075bd3dbaa1b33588bb4dc268addf" + integrity sha512-7JS4qIsnqaxk+FXY1E8dHBDmraYFWmuL6cgt0T1SWGRO5bzJf8sUoelwa4P88LEWJZweHevAiDKxHlofuvtIoA== dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.1.0" -postcss-minify-gradients@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471" - integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q== +postcss-minify-gradients@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.0.1.tgz#2dc79fd1a1afcb72a9e727bc549ce860f93565d2" + integrity sha512-odOwBFAIn2wIv+XYRpoN2hUV3pPQlgbJ10XeXPq8UY2N+9ZG42xu45lTn/g9zZ+d70NKSQD6EOi6UiCMu3FN7g== dependencies: - cssnano-util-get-arguments "^4.0.0" - is-color-stop "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + cssnano-utils "^2.0.1" + is-color-stop "^1.1.0" + postcss-value-parser "^4.1.0" -postcss-minify-params@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874" - integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg== +postcss-minify-params@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.0.1.tgz#371153ba164b9d8562842fdcd929c98abd9e5b6c" + integrity sha512-4RUC4k2A/Q9mGco1Z8ODc7h+A0z7L7X2ypO1B6V8057eVK6mZ6xwz6QN64nHuHLbqbclkX1wyzRnIrdZehTEHw== dependencies: - alphanum-sort "^1.0.0" - browserslist "^4.0.0" - cssnano-util-get-arguments "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + alphanum-sort "^1.0.2" + browserslist "^4.16.0" + cssnano-utils "^2.0.1" + postcss-value-parser "^4.1.0" uniqs "^2.0.0" -postcss-minify-selectors@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8" - integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g== +postcss-minify-selectors@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.1.0.tgz#4385c845d3979ff160291774523ffa54eafd5a54" + integrity sha512-NzGBXDa7aPsAcijXZeagnJBKBPMYLaJJzB8CQh6ncvyl2sIndLVWfbcDi0SBjRWk5VqEjXvf8tYwzoKf4Z07og== dependencies: - alphanum-sort "^1.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" + alphanum-sort "^1.0.2" + postcss-selector-parser "^6.0.5" postcss-modules-extract-imports@^3.0.0: version "3.0.0" @@ -5127,133 +5003,96 @@ postcss-modules@^4.0.0: postcss-modules-values "^4.0.0" string-hash "^1.1.1" -postcss-normalize-charset@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4" - integrity sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g== - dependencies: - postcss "^7.0.0" +postcss-normalize-charset@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz#121559d1bebc55ac8d24af37f67bd4da9efd91d0" + integrity sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg== -postcss-normalize-display-values@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a" - integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ== +postcss-normalize-display-values@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.1.tgz#62650b965981a955dffee83363453db82f6ad1fd" + integrity sha512-uupdvWk88kLDXi5HEyI9IaAJTE3/Djbcrqq8YgjvAVuzgVuqIk3SuJWUisT2gaJbZm1H9g5k2w1xXilM3x8DjQ== dependencies: - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + cssnano-utils "^2.0.1" + postcss-value-parser "^4.1.0" -postcss-normalize-positions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f" - integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA== +postcss-normalize-positions@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.0.1.tgz#868f6af1795fdfa86fbbe960dceb47e5f9492fe5" + integrity sha512-rvzWAJai5xej9yWqlCb1OWLd9JjW2Ex2BCPzUJrbaXmtKtgfL8dBMOOMTX6TnvQMtjk3ei1Lswcs78qKO1Skrg== dependencies: - cssnano-util-get-arguments "^4.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.1.0" -postcss-normalize-repeat-style@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c" - integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q== +postcss-normalize-repeat-style@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.1.tgz#cbc0de1383b57f5bb61ddd6a84653b5e8665b2b5" + integrity sha512-syZ2itq0HTQjj4QtXZOeefomckiV5TaUO6ReIEabCh3wgDs4Mr01pkif0MeVwKyU/LHEkPJnpwFKRxqWA/7O3w== dependencies: - cssnano-util-get-arguments "^4.0.0" - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + cssnano-utils "^2.0.1" + postcss-value-parser "^4.1.0" -postcss-normalize-string@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c" - integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA== +postcss-normalize-string@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.0.1.tgz#d9eafaa4df78c7a3b973ae346ef0e47c554985b0" + integrity sha512-Ic8GaQ3jPMVl1OEn2U//2pm93AXUcF3wz+OriskdZ1AOuYV25OdgS7w9Xu2LO5cGyhHCgn8dMXh9bO7vi3i9pA== dependencies: - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.1.0" -postcss-normalize-timing-functions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9" - integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A== +postcss-normalize-timing-functions@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.1.tgz#8ee41103b9130429c6cbba736932b75c5e2cb08c" + integrity sha512-cPcBdVN5OsWCNEo5hiXfLUnXfTGtSFiBU9SK8k7ii8UD7OLuznzgNRYkLZow11BkQiiqMcgPyh4ZqXEEUrtQ1Q== dependencies: - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + cssnano-utils "^2.0.1" + postcss-value-parser "^4.1.0" -postcss-normalize-unicode@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz#841bd48fdcf3019ad4baa7493a3d363b52ae1cfb" - integrity sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg== +postcss-normalize-unicode@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.1.tgz#82d672d648a411814aa5bf3ae565379ccd9f5e37" + integrity sha512-kAtYD6V3pK0beqrU90gpCQB7g6AOfP/2KIPCVBKJM2EheVsBQmx/Iof+9zR9NFKLAx4Pr9mDhogB27pmn354nA== dependencies: - browserslist "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + browserslist "^4.16.0" + postcss-value-parser "^4.1.0" -postcss-normalize-url@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1" - integrity sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA== +postcss-normalize-url@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.0.1.tgz#ffa9fe545935d8b57becbbb7934dd5e245513183" + integrity sha512-hkbG0j58Z1M830/CJ73VsP7gvlG1yF+4y7Fd1w4tD2c7CaA2Psll+pQ6eQhth9y9EaqZSLzamff/D0MZBMbYSg== dependencies: - is-absolute-url "^2.0.0" - normalize-url "^3.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + is-absolute-url "^3.0.3" + normalize-url "^4.5.0" + postcss-value-parser "^4.1.0" -postcss-normalize-whitespace@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82" - integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA== +postcss-normalize-whitespace@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.1.tgz#b0b40b5bcac83585ff07ead2daf2dcfbeeef8e9a" + integrity sha512-iPklmI5SBnRvwceb/XH568yyzK0qRVuAG+a1HFUsFRf11lEJTiQQa03a4RSCQvLKdcpX7XsI1Gen9LuLoqwiqA== dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.1.0" -postcss-ordered-values@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee" - integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw== +postcss-ordered-values@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.0.1.tgz#79ef6e2bd267ccad3fc0c4f4a586dfd01c131f64" + integrity sha512-6mkCF5BQ25HvEcDfrMHCLLFHlraBSlOXFnQMHYhSpDO/5jSR1k8LdEXOkv+7+uzW6o6tBYea1Km0wQSRkPJkwA== dependencies: - cssnano-util-get-arguments "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + cssnano-utils "^2.0.1" + postcss-value-parser "^4.1.0" -postcss-reduce-initial@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" - integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA== +postcss-reduce-initial@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.0.1.tgz#9d6369865b0f6f6f6b165a0ef5dc1a4856c7e946" + integrity sha512-zlCZPKLLTMAqA3ZWH57HlbCjkD55LX9dsRyxlls+wfuRfqCi5mSlZVan0heX5cHr154Dq9AfbH70LyhrSAezJw== dependencies: - browserslist "^4.0.0" + browserslist "^4.16.0" caniuse-api "^3.0.0" - has "^1.0.0" - postcss "^7.0.0" -postcss-reduce-transforms@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29" - integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg== +postcss-reduce-transforms@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.1.tgz#93c12f6a159474aa711d5269923e2383cedcf640" + integrity sha512-a//FjoPeFkRuAguPscTVmRQUODP+f3ke2HqFNgGPwdYnpeC29RZdCBvGRGTsKpMURb/I3p6jdKoBQ2zI+9Q7kA== dependencies: - cssnano-util-get-match "^4.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-selector-parser@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz#4f875f4afb0c96573d5cf4d74011aee250a7e865" - integrity sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU= - dependencies: - dot-prop "^4.1.1" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss-selector-parser@^5.0.0-rc.4: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c" - integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ== - dependencies: - cssesc "^2.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" + cssnano-utils "^2.0.1" + postcss-value-parser "^4.1.0" postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: version "6.0.4" @@ -5265,31 +5104,32 @@ postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: uniq "^1.0.1" util-deprecate "^1.0.2" -postcss-svgo@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.2.tgz#17b997bc711b333bab143aaed3b8d3d6e3d38258" - integrity sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw== +postcss-selector-parser@^6.0.5: + version "6.0.6" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz#2c5bba8174ac2f6981ab631a42ab0ee54af332ea" + integrity sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg== dependencies: - is-svg "^3.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - svgo "^1.0.0" + cssesc "^3.0.0" + util-deprecate "^1.0.2" -postcss-unique-selectors@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac" - integrity sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg== +postcss-svgo@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.0.1.tgz#6ed5e01e164e59204978994d844c653a331a8100" + integrity sha512-cD7DFo6tF9i5eWvwtI4irKOHCpmASFS0xvZ5EQIgEdA1AWfM/XiHHY/iss0gcKHhkqwgYmuo2M0KhJLd5Us6mg== dependencies: - alphanum-sort "^1.0.0" - postcss "^7.0.0" + postcss-value-parser "^4.1.0" + svgo "^2.3.0" + +postcss-unique-selectors@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.0.1.tgz#3be5c1d7363352eff838bd62b0b07a0abad43bfc" + integrity sha512-gwi1NhHV4FMmPn+qwBNuot1sG1t2OmacLQ/AX29lzyggnjd+MnVD5uqQmpXO3J17KGL2WAxQruj1qTd3H0gG/w== + dependencies: + alphanum-sort "^1.0.2" + postcss-selector-parser "^6.0.5" uniqs "^2.0.0" -postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" - integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== - -postcss-value-parser@^4.1.0: +postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== @@ -5322,7 +5162,7 @@ postcss@^6.0.1: source-map "^0.6.1" supports-color "^5.4.0" -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.5: +postcss@^7.0.14: version "7.0.14" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.14.tgz#4527ed6b1ca0d82c53ce5ec1a2041c2346bbd6e5" integrity sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg== @@ -5554,11 +5394,6 @@ pupa@^2.0.1: dependencies: escape-goat "^2.0.0" -q@^1.1.2: - version "1.5.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= - qs@6.7.0, qs@^6.5.1: version "6.7.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" @@ -5924,10 +5759,10 @@ resolve-file@^0.3.0: lazy-cache "^2.0.2" resolve "^1.2.0" -resolve-from@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" - integrity sha1-six699nWiBvItuZTM17rywoYh0g= +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== resolve-url@^0.2.1: version "0.2.1" @@ -6217,13 +6052,6 @@ signal-exit@^3.0.2: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= - dependencies: - is-arrayish "^0.3.1" - snyk-config@4.0.0, snyk-config@^4.0.0-rc.2: version "4.0.0" resolved "https://registry.yarnpkg.com/snyk-config/-/snyk-config-4.0.0.tgz#21d459f19087991246cc07a7ffb4501dce6f4159" @@ -6677,7 +6505,7 @@ source-map@^0.4.2: dependencies: amdefine ">=0.0.4" -source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1: +source-map@^0.5.6, source-map@~0.5.1: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -6935,14 +6763,13 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= -stylehacks@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" - integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g== +stylehacks@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.0.1.tgz#323ec554198520986806388c7fdaebc38d2c06fb" + integrity sha512-Es0rVnHIqbWzveU1b24kbw92HsebBepxfcqe5iix7t9j0PQqhs0IxXVXv0pY2Bxa08CgMkzD6OWql7kbGOuEdA== dependencies: - browserslist "^4.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" + browserslist "^4.16.0" + postcss-selector-parser "^6.0.4" stylus@^0.54.5, stylus@^0.x: version "0.54.8" @@ -7007,25 +6834,18 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -svgo@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.2.0.tgz#305a8fc0f4f9710828c65039bb93d5793225ffc3" - integrity sha512-xBfxJxfk4UeVN8asec9jNxHiv3UAMv/ujwBWGYvQhhMb2u3YTGKkiybPcLFDLq7GLLWE9wa73e0/m8L5nTzQbw== +svgo@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.3.0.tgz#6b3af81d0cbd1e19c83f5f63cec2cb98c70b5373" + integrity sha512-fz4IKjNO6HDPgIQxu4IxwtubtbSfGEAJUq/IXyTPIkGhWck/faiiwfkvsB8LnBkKLvSoyNNIY6d13lZprJMc9Q== dependencies: - chalk "^2.4.1" - coa "^2.0.2" - css-select "^2.0.0" - css-select-base-adapter "^0.1.1" - css-tree "1.0.0-alpha.28" - css-url-regex "^1.1.0" - csso "^3.5.1" - js-yaml "^3.12.0" - mkdirp "~0.5.1" - object.values "^1.1.0" - sax "~1.2.4" + "@trysound/sax" "0.1.1" + chalk "^4.1.0" + commander "^7.1.0" + css-select "^3.1.2" + css-tree "^1.1.2" + csso "^4.2.0" stable "^0.1.8" - unquote "~1.1.1" - util.promisify "~1.0.0" tar-stream@^2.1.0: version "2.1.0" @@ -7332,11 +7152,6 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= -unquote@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" - integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= - update-notifier@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.1.tgz#895fc8562bbe666179500f9f2cebac4f26323746" @@ -7390,14 +7205,6 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -util.promisify@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" - integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== - dependencies: - define-properties "^1.1.2" - object.getownpropertydescriptors "^2.0.3" - utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" @@ -7436,10 +7243,10 @@ vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= -vendors@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.2.tgz#7fcb5eef9f5623b156bcea89ec37d63676f21801" - integrity sha512-w/hry/368nO21AN9QljsaIhb9ZiZtZARoVH5f3CsFbawdLdayCgKRPup7CggujvySMxx0I91NOyxdVENohprLQ== +vendors@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" + integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== verror@1.10.0: version "1.10.0" @@ -7672,6 +7479,11 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + yaml@^1.9.2: version "1.10.0" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" From 646612be09439eed5d05a3d66ac081553f139733 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 26 May 2021 10:40:16 +0530 Subject: [PATCH 086/164] fix: Colocated asset load --- frappe/templates/test/_test_base.html | 13 ++++++++++++- frappe/website/doctype/web_page/test_web_page.py | 15 ++++++--------- frappe/website/page_renderers/template_page.py | 6 ++++-- frappe/www/_test/_test_folder/_test_page.css | 3 +++ frappe/www/_test/_test_folder/_test_page.js | 1 + 5 files changed, 26 insertions(+), 12 deletions(-) create mode 100644 frappe/www/_test/_test_folder/_test_page.css create mode 100644 frappe/www/_test/_test_folder/_test_page.js diff --git a/frappe/templates/test/_test_base.html b/frappe/templates/test/_test_base.html index a0b1a83c97..5a88584a5d 100644 --- a/frappe/templates/test/_test_base.html +++ b/frappe/templates/test/_test_base.html @@ -1,8 +1,19 @@ - + + {%- block style %} + {% if colocated_css -%} + + {%- endif %} + {%- endblock -%} +

This is for testing

{% block content %}{% endblock %} + {%- block script %} + {% if colocated_js -%} + + {%- endif %} + {%- endblock %} diff --git a/frappe/website/doctype/web_page/test_web_page.py b/frappe/website/doctype/web_page/test_web_page.py index b9bd425781..b5da51cca6 100644 --- a/frappe/website/doctype/web_page/test_web_page.py +++ b/frappe/website/doctype/web_page/test_web_page.py @@ -104,18 +104,15 @@ class TestWebPage(unittest.TestCase): self.assertTrue('Next: Test Toc' \ in frappe.as_unicode(content)) - def test_meta_tags(self): - pass + def test_colocated_assets(self): + content = get_response_content('/_test/_test_folder/_test_page') + self.assertTrue("" \ + in frappe.as_unicode(content)) + self.assertTrue("background-color: var(--bg-color);" \ + in frappe.as_unicode(content)) def test_breadcrumbs(self): pass def test_downloadable_file(self): pass - - - - -# breadcrumb -# validate -# page context caching diff --git a/frappe/website/page_renderers/template_page.py b/frappe/website/page_renderers/template_page.py index d8032c6b9c..682c3cda39 100644 --- a/frappe/website/page_renderers/template_page.py +++ b/frappe/website/page_renderers/template_page.py @@ -42,6 +42,7 @@ class TemplatePage(BaseTemplatePage): if os.path.exists(file_path) and not os.path.isdir(file_path): self.app = app self.app_path = app_path + self.basename = os.path.splitext(file_path)[0] self.template_path = os.path.relpath(file_path, self.app_path) self.basepath = os.path.dirname(file_path) return @@ -110,6 +111,7 @@ class TemplatePage(BaseTemplatePage): def update_context(self): self.context.base_template = self.context.base_template or get_base_template(self.path) self.context.basepath = self.basepath + self.context.basename = self.basename self.context.path = self.path self.set_page_properties() self.set_properties_from_source() @@ -197,11 +199,11 @@ class TemplatePage(BaseTemplatePage): def load_colocated_files(self): '''load co-located css/js files with the same name''' - js_path = self.basepath + '.js' + js_path = self.basename + '.js' if os.path.exists(js_path) and '{% block script %}' not in self.source: self.context.colocated_js = self.get_colocated_file(js_path) - css_path = self.basepath + '.css' + css_path = self.basename + '.css' if os.path.exists(css_path) and '{% block style %}' not in self.source: self.context.colocated_css = self.get_colocated_file(css_path) diff --git a/frappe/www/_test/_test_folder/_test_page.css b/frappe/www/_test/_test_folder/_test_page.css new file mode 100644 index 0000000000..e42b809085 --- /dev/null +++ b/frappe/www/_test/_test_folder/_test_page.css @@ -0,0 +1,3 @@ +body { + background-color: var(--bg-color); +} \ No newline at end of file diff --git a/frappe/www/_test/_test_folder/_test_page.js b/frappe/www/_test/_test_folder/_test_page.js new file mode 100644 index 0000000000..6e0c1f3a87 --- /dev/null +++ b/frappe/www/_test/_test_folder/_test_page.js @@ -0,0 +1 @@ +console.log('test data'); \ No newline at end of file From 40bd818c0b6e29c3b682db45fe083d1abd60d42c Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 26 May 2021 10:40:43 +0530 Subject: [PATCH 087/164] test: Add attributes via frontmatter --- frappe/www/_test/_test_folder/_test_toc.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/frappe/www/_test/_test_folder/_test_toc.md b/frappe/www/_test/_test_folder/_test_toc.md index 8d05b120a5..02cc3c82be 100644 --- a/frappe/www/_test/_test_folder/_test_toc.md +++ b/frappe/www/_test/_test_folder/_test_toc.md @@ -1,3 +1,13 @@ +--- +title: Test TOC +add_breadcrumbs: 1 +show_sidebar: 0 + +metatags: + description: Test Description. + keywords: Frappe Framework. +--- + # Level 1 ## Level 1.1 From e19c18e5a9840ed68c4c29ccf5ecb09df03af137 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 26 May 2021 11:03:33 +0530 Subject: [PATCH 088/164] refactor: Move context value setting code to a separate function --- frappe/website/page_renderers/template_page.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/frappe/website/page_renderers/template_page.py b/frappe/website/page_renderers/template_page.py index 682c3cda39..2b326d7383 100644 --- a/frappe/website/page_renderers/template_page.py +++ b/frappe/website/page_renderers/template_page.py @@ -45,6 +45,8 @@ class TemplatePage(BaseTemplatePage): self.basename = os.path.splitext(file_path)[0] self.template_path = os.path.relpath(file_path, self.app_path) self.basepath = os.path.dirname(file_path) + self.filename = os.path.basename(file_path) + self.name = os.path.splitext(self.filename)[0] return def can_render(self): @@ -109,10 +111,6 @@ class TemplatePage(BaseTemplatePage): self.convert_from_markdown() def update_context(self): - self.context.base_template = self.context.base_template or get_base_template(self.path) - self.context.basepath = self.basepath - self.context.basename = self.basename - self.context.path = self.path self.set_page_properties() self.set_properties_from_source() self.load_colocated_files() @@ -138,8 +136,15 @@ class TemplatePage(BaseTemplatePage): self.context[prop] = getattr(self.pymodule, prop) def set_page_properties(self): + self.context.base_template = self.context.base_template \ + or get_base_template(self.path) \ + or 'templates/web.html' + self.context.basepath = self.basepath + self.context.basename = self.basename + self.context.name = self.name + self.context.path = self.path + self.context.route = self.path self.context.template = self.template_path - self.context.base_template = self.context.base_template or 'templates/web.html' def set_properties_from_source(self): if not self.source: From 38fdfc369a1a1dd0b22bda2f2eb03e10543e2b6a Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 26 May 2021 11:51:12 +0530 Subject: [PATCH 089/164] test: Use assertIn for better error message --- .../website/doctype/web_page/test_web_page.py | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/frappe/website/doctype/web_page/test_web_page.py b/frappe/website/doctype/web_page/test_web_page.py index b5da51cca6..d213fd57a8 100644 --- a/frappe/website/doctype/web_page/test_web_page.py +++ b/frappe/website/doctype/web_page/test_web_page.py @@ -28,10 +28,10 @@ class TestWebPage(unittest.TestCase): content = get_page_content('/_test/_test_custom_base.html') # assert the text in base template is rendered - self.assertTrue('

This is for testing

' in frappe.as_unicode(content)) + self.assertIn('

This is for testing

', frappe.as_unicode(content)) # assert template block rendered - self.assertTrue('

Test content

' in frappe.as_unicode(content)) + self.assertIn('

Test content

', frappe.as_unicode(content)) def test_content_type(self): web_page = frappe.get_doc(dict( @@ -44,15 +44,15 @@ class TestWebPage(unittest.TestCase): main_section_html = '
html content
' )).insert() - self.assertTrue('rich text' in get_page_content('/test-content-type')) + self.assertIn('rich text', get_page_content('/test-content-type')) web_page.content_type = 'Markdown' web_page.save() - self.assertTrue('markdown content' in get_page_content('/test-content-type')) + self.assertIn('markdown content', get_page_content('/test-content-type')) web_page.content_type = 'HTML' web_page.save() - self.assertTrue('html content' in get_page_content('/test-content-type')) + self.assertIn('html content', get_page_content('/test-content-type')) web_page.delete() @@ -70,46 +70,47 @@ class TestWebPage(unittest.TestCase): try: content = get_page_content('/doctype-view/DocField') - self.assertTrue('
DocField
' in content) + self.assertIn('
DocField
', content) finally: web_page.delete() def test_custom_base_template_path(self): content = get_response_content('/_test/_test_folder/_test_page') # assert the text in base template is rendered - self.assertTrue('

This is for testing

' in frappe.as_unicode(content)) + self.assertIn('

This is for testing

', frappe.as_unicode(content)) # assert template block rendered - self.assertTrue('

Test content

' in frappe.as_unicode(content)) + self.assertIn('

Test content

', frappe.as_unicode(content)) def test_json_sidebar_data(self): frappe.flags.look_for_sidebar = False content = get_response_content('/_test/_test_folder/_test_page') - self.assertTrue('Test Sidebar' not in frappe.as_unicode(content)) + self.assertNotIn('Test Sidebar', frappe.as_unicode(content)) frappe.flags.look_for_sidebar = True content = get_response_content('/_test/_test_folder/_test_page') - self.assertTrue('Test Sidebar' in frappe.as_unicode(content)) + self.assertIn('Test Sidebar', frappe.as_unicode(content)) frappe.flags.look_for_sidebar = False def test_index_and_next_comment(self): content = get_response_content('/_test/_test_folder') # test if {index} was rendered - self.assertTrue(' Test Page' \ - in frappe.as_unicode(content)) - self.assertTrue(' Test Toc' \ - in frappe.as_unicode(content)) + self.assertIn(' Test Page', + frappe.as_unicode(content)) + + self.assertIn('Test TOC', + frappe.as_unicode(content)) content = get_response_content('/_test/_test_folder/_test_page') # test if {next} was rendered - self.assertTrue('Next: Test Toc' \ - in frappe.as_unicode(content)) + self.assertIn('Next: Test TOC', + frappe.as_unicode(content)) def test_colocated_assets(self): content = get_response_content('/_test/_test_folder/_test_page') - self.assertTrue("" \ - in frappe.as_unicode(content)) - self.assertTrue("background-color: var(--bg-color);" \ - in frappe.as_unicode(content)) + self.assertIn("", + frappe.as_unicode(content)) + self.assertIn("background-color: var(--bg-color);", + frappe.as_unicode(content)) def test_breadcrumbs(self): pass From 33a97196e141e42bf99116f336fe8f65c7672a30 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 26 May 2021 11:51:34 +0530 Subject: [PATCH 090/164] fix: Init page properties in webpage --- frappe/website/page_renderers/web_page.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/website/page_renderers/web_page.py b/frappe/website/page_renderers/web_page.py index 5c3807c0ee..6e738e8df9 100644 --- a/frappe/website/page_renderers/web_page.py +++ b/frappe/website/page_renderers/web_page.py @@ -8,6 +8,9 @@ class WebPage(object): path = frappe.local.request.path self.path = path.strip('/ ') self.basepath = '' + self.basename = '' + self.name = '' + self.route = '' def can_render(self): pass From d13eac6f50ad727c1e77a66b828c7f60f5b4bbcc Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 27 May 2021 12:56:38 +0530 Subject: [PATCH 091/164] fix: Breadcrumbs for a page --- .../website/page_renderers/template_page.py | 25 +++++++++++++------ frappe/website/page_renderers/web_page.py | 9 ++++--- frappe/website/router.py | 2 +- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/frappe/website/page_renderers/template_page.py b/frappe/website/page_renderers/template_page.py index 2b326d7383..8f613ae9bd 100644 --- a/frappe/website/page_renderers/template_page.py +++ b/frappe/website/page_renderers/template_page.py @@ -3,6 +3,7 @@ import os import click import frappe +from frappe.website.router import get_page_info from frappe.website.page_renderers.base_template_page import BaseTemplatePage from frappe.website.router import get_base_template from frappe.website.utils import (extract_comment_tag, extract_title, get_next_link, @@ -37,11 +38,11 @@ class TemplatePage(BaseTemplatePage): for dirname in folders: search_path = os.path.join(app_path, dirname, self.path) - for p in self.get_index_path_options(search_path): - file_path = frappe.as_unicode(p) - if os.path.exists(file_path) and not os.path.isdir(file_path): + for file_path in self.get_index_path_options(search_path): + if os.path.isfile(file_path): self.app = app self.app_path = app_path + self.file_dir = dirname self.basename = os.path.splitext(file_path)[0] self.template_path = os.path.relpath(file_path, self.app_path) self.basepath = os.path.dirname(file_path) @@ -52,8 +53,9 @@ class TemplatePage(BaseTemplatePage): def can_render(self): return hasattr(self, 'template_path') and bool(self.template_path) - def get_index_path_options(self, search_path): - return (f'{search_path}{d}' for d in ('', '.html', '.md', '/index.html', '/index.md')) + @staticmethod + def get_index_path_options(search_path): + return (frappe.as_unicode(f'{search_path}{d}') for d in ('.html', '.md', '/index.html', '/index.md')) def render(self): return build_response(self.path, self.get_html(), self.http_status_code, self.headers) @@ -85,9 +87,18 @@ class TemplatePage(BaseTemplatePage): self.context.sidebar_items = get_sidebar_items(self.context.website_sidebar, self.basepath) if self.context.add_breadcrumbs and not self.context.parents: - # TODO: set correct title and route for breadcrumbs parent_path = os.path.dirname(self.path) - self.context.parents = [dict(route=parent_path, title=extract_title(source='', path=parent_path))] + if self.path.endswith('index'): + # in case of index page move one directory up for parent path + parent_path = os.path.dirname(parent_path) + + for parent_file_path in self.get_index_path_options(parent_path): + parent_file_path = os.path.join(self.app_path, self.file_dir, parent_file_path) + if os.path.isfile(parent_file_path): + parent_page_context = get_page_info(parent_file_path, self.app, self.file_dir) + if parent_page_context: + self.context.parents = [dict(route=os.path.dirname(self.path), title=parent_page_context.title)] + break def set_pymodule(self): ''' diff --git a/frappe/website/page_renderers/web_page.py b/frappe/website/page_renderers/web_page.py index 6e738e8df9..1814a9615a 100644 --- a/frappe/website/page_renderers/web_page.py +++ b/frappe/website/page_renderers/web_page.py @@ -7,10 +7,11 @@ class WebPage(object): if not path: path = frappe.local.request.path self.path = path.strip('/ ') - self.basepath = '' - self.basename = '' - self.name = '' - self.route = '' + self.basepath = None + self.basename = None + self.name = None + self.route = None + self.file_dir = None def can_render(self): pass diff --git a/frappe/website/router.py b/frappe/website/router.py index 23fd5cb21f..6d803886ef 100644 --- a/frappe/website/router.py +++ b/frappe/website/router.py @@ -107,7 +107,7 @@ def get_page_info(path, app, start, basepath=None, app_path=None, fname=None): if basepath is None: basepath = os.path.dirname(path) - page_name, extn = fname.rsplit(".", 1) + page_name, extn = os.path.splitext(fname) # add website route page_info = frappe._dict() From 9ec451b28e2e92589bb7d29a3aaf861776f75a98 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 27 May 2021 12:57:55 +0530 Subject: [PATCH 092/164] test: Add test case to check breadcrumbs --- frappe/templates/includes/full_index.html | 5 ----- frappe/templates/test/_test_base.html | 1 + frappe/website/doctype/web_page/test_web_page.py | 8 +++++++- frappe/www/_test/_test_folder/_test_page.py | 1 + frappe/www/_test/_test_folder/index.md | 6 ++++++ frappe/www/_test/index.html | 1 + 6 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 frappe/www/_test/index.html diff --git a/frappe/templates/includes/full_index.html b/frappe/templates/includes/full_index.html index a7443c482a..eb8fb322f6 100644 --- a/frappe/templates/includes/full_index.html +++ b/frappe/templates/includes/full_index.html @@ -3,11 +3,6 @@ {% for item in children_map[route] %}
  • {{ item.title }} - {# - {% if children_map[item.route] %} - {{ make_item_list(item.route, children_map) }} - {% endif %} - #}
  • {% endfor %} diff --git a/frappe/templates/test/_test_base.html b/frappe/templates/test/_test_base.html index 5a88584a5d..17caf8df1b 100644 --- a/frappe/templates/test/_test_base.html +++ b/frappe/templates/test/_test_base.html @@ -8,6 +8,7 @@ {%- endblock -%} + {% include "templates/includes/breadcrumbs.html" %}

    This is for testing

    {% block content %}{% endblock %} {%- block script %} diff --git a/frappe/website/doctype/web_page/test_web_page.py b/frappe/website/doctype/web_page/test_web_page.py index d213fd57a8..d097ceb083 100644 --- a/frappe/website/doctype/web_page/test_web_page.py +++ b/frappe/website/doctype/web_page/test_web_page.py @@ -113,7 +113,13 @@ class TestWebPage(unittest.TestCase): frappe.as_unicode(content)) def test_breadcrumbs(self): - pass + content = get_response_content('/_test/_test_folder/_test_page') + self.assertIn('Test TOC', content) + self.assertIn(' Test Page', content) + + content = get_response_content('/_test/_test_folder/index') + self.assertIn(' Test', content) + self.assertIn('Test TOC', content) def test_downloadable_file(self): pass diff --git a/frappe/www/_test/_test_folder/_test_page.py b/frappe/www/_test/_test_folder/_test_page.py index fa7b5f5727..1813a06bac 100644 --- a/frappe/www/_test/_test_folder/_test_page.py +++ b/frappe/www/_test/_test_folder/_test_page.py @@ -1,2 +1,3 @@ def get_context(context): context.base_template_path = 'frappe/templates/test/_test_base.html' + context.add_breadcrumbs = 1 diff --git a/frappe/www/_test/_test_folder/index.md b/frappe/www/_test/_test_folder/index.md index d0b531a642..1a5a9e7f81 100644 --- a/frappe/www/_test/_test_folder/index.md +++ b/frappe/www/_test/_test_folder/index.md @@ -1,3 +1,9 @@ +--- +title: Test TOC +add_breadcrumbs: 1 +show_sidebar: 1 +--- + # Index {index} \ No newline at end of file diff --git a/frappe/www/_test/index.html b/frappe/www/_test/index.html new file mode 100644 index 0000000000..0dff60b400 --- /dev/null +++ b/frappe/www/_test/index.html @@ -0,0 +1 @@ +{index} \ No newline at end of file From 6e9207cd626a373d7e6488c56ec4d027458ede9e Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 27 May 2021 12:58:48 +0530 Subject: [PATCH 093/164] test: Refactor webpage test to remove unnecessary operations --- .../website/doctype/web_page/test_web_page.py | 47 +++++++------------ 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/frappe/website/doctype/web_page/test_web_page.py b/frappe/website/doctype/web_page/test_web_page.py index d097ceb083..67b3cdbe89 100644 --- a/frappe/website/doctype/web_page/test_web_page.py +++ b/frappe/website/doctype/web_page/test_web_page.py @@ -2,16 +2,10 @@ from __future__ import unicode_literals import unittest import frappe from frappe.website.path_resolver import PathResolver -from frappe.website.serve import get_response, get_response_content -from frappe.utils import set_request +from frappe.website.serve import get_response_content test_records = frappe.get_test_records('Web Page') -def get_page_content(route): - set_request(method='GET', path = route) - response = get_response() - return frappe.as_unicode(response.data) - class TestWebPage(unittest.TestCase): def setUp(self): frappe.db.sql("delete from `tabWeb Page`") @@ -25,13 +19,13 @@ class TestWebPage(unittest.TestCase): self.assertFalse(PathResolver("test-web-page-1/test-web-page-Random").is_valid_path()) def test_base_template(self): - content = get_page_content('/_test/_test_custom_base.html') + content = get_response_content('/_test/_test_custom_base.html') # assert the text in base template is rendered - self.assertIn('

    This is for testing

    ', frappe.as_unicode(content)) + self.assertIn('

    This is for testing

    ', content) # assert template block rendered - self.assertIn('

    Test content

    ', frappe.as_unicode(content)) + self.assertIn('

    Test content

    ', content) def test_content_type(self): web_page = frappe.get_doc(dict( @@ -44,15 +38,15 @@ class TestWebPage(unittest.TestCase): main_section_html = '
    html content
    ' )).insert() - self.assertIn('rich text', get_page_content('/test-content-type')) + self.assertIn('rich text', get_response_content('/test-content-type')) web_page.content_type = 'Markdown' web_page.save() - self.assertIn('markdown content', get_page_content('/test-content-type')) + self.assertIn('markdown content', get_response_content('/test-content-type')) web_page.content_type = 'HTML' web_page.save() - self.assertIn('html content', get_page_content('/test-content-type')) + self.assertIn('html content', get_response_content('/test-content-type')) web_page.delete() @@ -67,9 +61,9 @@ class TestWebPage(unittest.TestCase): dynamic_template = 1, main_section_html = '
    {{ frappe.form_dict.doctype }}
    ' )).insert() - try: - content = get_page_content('/doctype-view/DocField') + from frappe.utils import get_html_for_route + content = get_html_for_route('/doctype-view/DocField') self.assertIn('
    DocField
    ', content) finally: web_page.delete() @@ -77,40 +71,35 @@ class TestWebPage(unittest.TestCase): def test_custom_base_template_path(self): content = get_response_content('/_test/_test_folder/_test_page') # assert the text in base template is rendered - self.assertIn('

    This is for testing

    ', frappe.as_unicode(content)) + self.assertIn('

    This is for testing

    ', content) # assert template block rendered - self.assertIn('

    Test content

    ', frappe.as_unicode(content)) + self.assertIn('

    Test content

    ', content) def test_json_sidebar_data(self): frappe.flags.look_for_sidebar = False content = get_response_content('/_test/_test_folder/_test_page') - self.assertNotIn('Test Sidebar', frappe.as_unicode(content)) + self.assertNotIn('Test Sidebar', content) frappe.flags.look_for_sidebar = True content = get_response_content('/_test/_test_folder/_test_page') - self.assertIn('Test Sidebar', frappe.as_unicode(content)) + self.assertIn('Test Sidebar', content) frappe.flags.look_for_sidebar = False def test_index_and_next_comment(self): content = get_response_content('/_test/_test_folder') # test if {index} was rendered - self.assertIn(' Test Page', - frappe.as_unicode(content)) + self.assertIn(' Test Page', content) - self.assertIn('Test TOC', - frappe.as_unicode(content)) + self.assertIn('Test TOC', content) content = get_response_content('/_test/_test_folder/_test_page') # test if {next} was rendered - self.assertIn('Next: Test TOC', - frappe.as_unicode(content)) + self.assertIn('Next: Test TOC', content) def test_colocated_assets(self): content = get_response_content('/_test/_test_folder/_test_page') - self.assertIn("", - frappe.as_unicode(content)) - self.assertIn("background-color: var(--bg-color);", - frappe.as_unicode(content)) + self.assertIn("", content) + self.assertIn("background-color: var(--bg-color);", content) def test_breadcrumbs(self): content = get_response_content('/_test/_test_folder/_test_page') From c8749bcc72a48abd7815dea3c4fc2aa59d223e2a Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 27 May 2021 14:27:30 +0530 Subject: [PATCH 094/164] fix: get_index_path_options method --- frappe/website/page_renderers/template_page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/page_renderers/template_page.py b/frappe/website/page_renderers/template_page.py index 8f613ae9bd..d1f0b47032 100644 --- a/frappe/website/page_renderers/template_page.py +++ b/frappe/website/page_renderers/template_page.py @@ -55,7 +55,7 @@ class TemplatePage(BaseTemplatePage): @staticmethod def get_index_path_options(search_path): - return (frappe.as_unicode(f'{search_path}{d}') for d in ('.html', '.md', '/index.html', '/index.md')) + return (frappe.as_unicode(f'{search_path}{d}') for d in ('', '.html', '.md', '/index.html', '/index.md')) def render(self): return build_response(self.path, self.get_html(), self.http_status_code, self.headers) From 369adf5a3a303612edf9f0169c7b37b7c711a852 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 27 May 2021 14:44:47 +0530 Subject: [PATCH 095/164] fix: Set default value as empty string --- frappe/website/page_renderers/web_page.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/website/page_renderers/web_page.py b/frappe/website/page_renderers/web_page.py index 1814a9615a..20e5bab062 100644 --- a/frappe/website/page_renderers/web_page.py +++ b/frappe/website/page_renderers/web_page.py @@ -7,10 +7,10 @@ class WebPage(object): if not path: path = frappe.local.request.path self.path = path.strip('/ ') - self.basepath = None - self.basename = None - self.name = None - self.route = None + self.basepath = '' + self.basename = '' + self.name = '' + self.route = '' self.file_dir = None def can_render(self): From 1a41e16e2a5d724b04b5ef0acaced5887f2db658 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 27 May 2021 15:14:27 +0530 Subject: [PATCH 096/164] fix: Blog post style --- frappe/public/scss/website/blog.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frappe/public/scss/website/blog.scss b/frappe/public/scss/website/blog.scss index 9918b490c5..ea82efed21 100644 --- a/frappe/public/scss/website/blog.scss +++ b/frappe/public/scss/website/blog.scss @@ -14,6 +14,10 @@ position: relative; width: 100%; + .card { + border: 1px solid var(--border-color) + } + .card-body { display: flex; flex-direction: column; From 63a42f54b292d8592b7eb48ddf33686d17778547 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 31 May 2021 14:55:23 +0530 Subject: [PATCH 097/164] fix: Deprecation warning --- frappe/website/page_renderers/template_page.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frappe/website/page_renderers/template_page.py b/frappe/website/page_renderers/template_page.py index d1f0b47032..ea92c7103c 100644 --- a/frappe/website/page_renderers/template_page.py +++ b/frappe/website/page_renderers/template_page.py @@ -70,8 +70,8 @@ class TemplatePage(BaseTemplatePage): self.setup_template() self.update_context() self.post_process_context() - html = self.render_template() + html = self.render_template() html = self.update_toc(html) html = self.add_csrf_token(html) @@ -182,7 +182,8 @@ class TemplatePage(BaseTemplatePage): comment_tag = f"" if comment_tag in self.source: self.context[context_key] = value - click.echo(f'⚠️ DEPRECATION WARNING: {comment_tag} will be deprecated on 2021-12-31.') + click.echo(f'\n⚠️ DEPRECATION WARNING: {comment_tag} will be deprecated on 2021-12-31.') + click.echo(f'Please remove it from {self.template_path} in {self.app}') def run_pymodule_method(self, method): if hasattr(self.pymodule, method): From a42fc3c5dfe674a48c91cb29acb5e61cf03dd61c Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 31 May 2021 14:56:09 +0530 Subject: [PATCH 098/164] feat: Option to add custom renderers --- frappe/website/path_resolver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/website/path_resolver.py b/frappe/website/path_resolver.py index 9816900677..0519d82735 100644 --- a/frappe/website/path_resolver.py +++ b/frappe/website/path_resolver.py @@ -35,7 +35,8 @@ class PathResolver(): return frappe.flags.redirect_location, RedirectPage(self.path) endpoint = resolve_path(self.path) - renderers = (StaticPage, WebFormPage, TemplatePage, ListPage, DocumentPage, PrintPage, NotFoundPage) + custom_renderers = frappe.get_hooks('page_renderer') + renderers = custom_renderers + [StaticPage, WebFormPage, TemplatePage, ListPage, DocumentPage, PrintPage, NotFoundPage] for renderer in renderers: renderer_instance = renderer(endpoint, 200) From 55a49ce445aac635c09bfba14fbdadc621d59811 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 1 Jun 2021 17:39:25 +0530 Subject: [PATCH 099/164] fix: cannot set shortcut for actions menu item --- frappe/public/js/frappe/ui/page.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/page.js b/frappe/public/js/frappe/ui/page.js index e740718ef9..a302c26892 100644 --- a/frappe/public/js/frappe/ui/page.js +++ b/frappe/public/js/frappe/ui/page.js @@ -377,11 +377,12 @@ frappe.ui.Page = class Page { }); } - add_actions_menu_item(label, click, standard) { + add_actions_menu_item(label, click, standard, shortcut) { return this.add_dropdown_item({ label, click, standard, + shortcut, parent: this.actions, show_parent: false }); From 43972e28a73b9d08cd073065fc94d0175cf872ed Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 1 Jun 2021 22:17:24 +0530 Subject: [PATCH 100/164] feat: group by tags in report view --- frappe/public/js/frappe/form/formatters.js | 11 ++++++++--- frappe/public/js/frappe/ui/group_by/group_by.js | 5 +++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index 89c34ed80c..83fe9ffa9c 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -221,9 +221,13 @@ frappe.form.formatters = { Tag: function(value) { var html = ""; $.each((value || "").split(","), function(i, v) { - if(v) html+= ''+v +''; + if (v) html += ` + + ${v} + `; }); return html; }, @@ -310,6 +314,7 @@ frappe.form.get_formatter = function(fieldtype) { frappe.format = function(value, df, options, doc) { if(!df) df = {"fieldtype":"Data"}; + if (df.fieldname == '_user_tags') df.fieldtype = 'Tag' var fieldtype = df.fieldtype || "Data"; // format Dynamic Link as a Link diff --git a/frappe/public/js/frappe/ui/group_by/group_by.js b/frappe/public/js/frappe/ui/group_by/group_by.js index 3ebf9c9d3d..a113396643 100644 --- a/frappe/public/js/frappe/ui/group_by/group_by.js +++ b/frappe/public/js/frappe/ui/group_by/group_by.js @@ -381,10 +381,11 @@ frappe.ui.GroupBy = class { this.group_by_fields = {}; this.all_fields = {}; - let fields = this.report_view.meta.fields.filter((f) => + const fields = this.report_view.meta.fields.filter((f) => ['Select', 'Link', 'Data', 'Int', 'Check'].includes(f.fieldtype) ); - this.group_by_fields[this.doctype] = fields; + const tag_field = {fieldname:'_user_tags', fieldtype:'Data', label:__('Tags')}; + this.group_by_fields[this.doctype] = fields.concat(tag_field); this.all_fields[this.doctype] = this.report_view.meta.fields; const standard_fields_filter = (df) => From 879ecaacabad73ea13f583e61d85f5a086a36f6c Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 3 Jun 2021 10:40:36 +0530 Subject: [PATCH 101/164] fix: Custom page renderer logic --- frappe/website/path_resolver.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/frappe/website/path_resolver.py b/frappe/website/path_resolver.py index 0519d82735..bedd9f19ae 100644 --- a/frappe/website/path_resolver.py +++ b/frappe/website/path_resolver.py @@ -1,4 +1,5 @@ import re +import click from werkzeug.routing import Rule @@ -35,7 +36,7 @@ class PathResolver(): return frappe.flags.redirect_location, RedirectPage(self.path) endpoint = resolve_path(self.path) - custom_renderers = frappe.get_hooks('page_renderer') + custom_renderers = self.get_custom_page_renderers() renderers = custom_renderers + [StaticPage, WebFormPage, TemplatePage, ListPage, DocumentPage, PrintPage, NotFoundPage] for renderer in renderers: @@ -49,6 +50,27 @@ class PathResolver(): _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): ''' From 254496450163cc308db0c9a23224b041daceb07d Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 3 Jun 2021 10:41:22 +0530 Subject: [PATCH 102/164] test: Add test case to validate custom page renderer --- frappe/tests/test_website.py | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/frappe/tests/test_website.py b/frappe/tests/test_website.py index 69425cdc45..84bf3ef8f1 100644 --- a/frappe/tests/test_website.py +++ b/frappe/tests/test_website.py @@ -3,9 +3,10 @@ from __future__ import unicode_literals import unittest import frappe -from frappe.website.serve import get_response -from frappe.website.utils import get_home_page, clear_website_cache from frappe.utils import set_request +from frappe.website.serve import get_response, get_response_content +from frappe.website.utils import (build_response, clear_website_cache, get_home_page) + class TestWebsite(unittest.TestCase): def setUp(self): @@ -178,6 +179,25 @@ class TestWebsite(unittest.TestCase): delattr(frappe.hooks, 'website_redirects') frappe.cache().delete_key('app_hooks') + def test_custom_page_renderer(self): + import frappe.hooks + frappe.hooks.page_renderer = ['frappe.tests.test_website.CustomPageRenderer'] + frappe.cache().delete_key('app_hooks') + set_request(method='GET', path='/custom') + response = get_response() + self.assertEqual(response.status_code, 3984) + + set_request(method='GET', path='/new') + content = get_response_content() + self.assertIn("
    Custom Page Response
    ", content) + + set_request(method='GET', path='/random') + response = get_response() + self.assertEqual(response.status_code, 404) + + delattr(frappe.hooks, 'page_renderer') + frappe.cache().delete_key('app_hooks') + def set_home_page_hook(key, value): from frappe import hooks @@ -189,3 +209,15 @@ def set_home_page_hook(key, value): setattr(hooks, key, value) frappe.cache().delete_key('app_hooks') +class CustomPageRenderer(): + def __init__(self, path, status_code=None): + self.path = path + # custom status code + self.status_code = 3984 + + def can_render(self): + if self.path in ('new', 'custom'): + return True + + def render(self): + return build_response(self.path, """
    Custom Page Response
    """, self.status_code) From 468b8fbd996142193107ecfc8f93a975646911d3 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 3 Jun 2021 11:48:49 +0530 Subject: [PATCH 103/164] fix: Remove unnecessary import --- frappe/website/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/website/utils.py b/frappe/website/utils.py index df3cdefa1f..0f5f182ea2 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -7,7 +7,6 @@ import re from functools import wraps import yaml -from past.builtins import cmp from six import iteritems from werkzeug.wrappers import Response From 1ef83ea15f264b785cd9981bc907d8aebbc1efa1 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 4 Jun 2021 11:50:03 +0530 Subject: [PATCH 104/164] test: Add printview page test --- frappe/tests/test_website.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frappe/tests/test_website.py b/frappe/tests/test_website.py index 68ec4f1f09..6f265d9b94 100644 --- a/frappe/tests/test_website.py +++ b/frappe/tests/test_website.py @@ -196,6 +196,11 @@ class TestWebsite(unittest.TestCase): delattr(frappe.hooks, 'page_renderer') frappe.cache().delete_key('app_hooks') + def test_printview_page(self): + content = get_response_content('/Language/en') + self.assertIn(' {% endif %} + {% if not disable_feedback %} +
    + {% include 'templates/includes/feedback/feedback.html' %} +
    + {% endif %}