diff --git a/frappe/boot.py b/frappe/boot.py index d20aa5b12e..3ad9ec800d 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -385,25 +385,6 @@ def load_print_css(bootinfo, print_settings): ) -def get_unseen_notes(): - note = DocType("Note") - nsb = DocType("Note Seen By").as_("nsb") - - return ( - frappe.qb.from_(note) - .select(note.name, note.title, note.content, note.notify_on_every_login) - .where( - (note.notify_on_login == 1) - & (note.expire_notification_on > frappe.utils.now()) - & ( - ParameterizedValueWrapper(frappe.session.user).notin( - SubQuery(frappe.qb.from_(nsb).select(nsb.user).where(nsb.parent == note.name)) - ) - ) - ) - ).run(as_dict=1) - - def get_success_action(): return frappe.get_all("Success Action", fields=["*"]) diff --git a/frappe/desk/doctype/note/note.py b/frappe/desk/doctype/note/note.py index e303f9b80f..e650b48b12 100644 --- a/frappe/desk/doctype/note/note.py +++ b/frappe/desk/doctype/note/note.py @@ -4,6 +4,8 @@ import frappe from frappe.model.document import Document +UNSEEN_NOTES_KEY = "unseen_notes::" + class Note(Document): # begin: auto-generated types @@ -39,6 +41,10 @@ class Note(Document): self.print_heading = self.name self.sub_heading = "" + def clear_cache(self): + frappe.cache.delete_keys(UNSEEN_NOTES_KEY) + return super().clear_cache() + def mark_seen_by(self, user: str) -> None: if user in [d.user for d in self.seen_by]: return @@ -62,3 +68,33 @@ def get_permission_query_conditions(user): def has_permission(doc, user): return bool(doc.public or doc.owner == user) + + +def get_unseen_notes(): + from frappe.query_builder.terms import ParameterizedValueWrapper, SubQuery + + def _get_unseen_notes(): + note = frappe.qb.DocType("Note") + nsb = frappe.qb.DocType("Note Seen By").as_("nsb") + + return ( + frappe.qb.from_(note) + .select(note.name, note.title, note.content, note.notify_on_every_login) + .where( + (note.notify_on_login == 1) + & (note.expire_notification_on > frappe.utils.now()) + & ( + ParameterizedValueWrapper(frappe.session.user).notin( + SubQuery(frappe.qb.from_(nsb).select(nsb.user).where(nsb.parent == note.name)) + ) + ) + ) + ).run(as_dict=1) + + return ( + frappe.cache.get_value( + f"{UNSEEN_NOTES_KEY}{frappe.session.user}", + generator=_get_unseen_notes, + ) + or [] + ) diff --git a/frappe/sessions.py b/frappe/sessions.py index d903ba7896..56595e4502 100644 --- a/frappe/sessions.py +++ b/frappe/sessions.py @@ -126,7 +126,8 @@ def clear_expired_sessions(): def get(): """get session boot info""" - from frappe.boot import get_bootinfo, get_unseen_notes + from frappe.boot import get_bootinfo + from frappe.desk.doctype.note.note import get_unseen_notes from frappe.utils.change_log import get_change_log bootinfo = None @@ -175,9 +176,9 @@ def get(): "default_path": get_default_path() or "", } - bootinfo["desk_theme"] = frappe.db.get_value("User", frappe.session.user, "desk_theme") or "Light" + bootinfo["desk_theme"] = frappe.get_cached_value("User", frappe.session.user, "desk_theme") or "Light" bootinfo["user"]["impersonated_by"] = frappe.session.data.get("impersonated_by") - bootinfo["navbar_settings"] = frappe.get_cached_doc("Navbar Settings") + bootinfo["navbar_settings"] = frappe.client_cache.get_doc("Navbar Settings") bootinfo.has_app_updates = has_app_update_notifications() return bootinfo diff --git a/frappe/tests/test_boot.py b/frappe/tests/test_boot.py index e7b04a4033..b28cfa6ff1 100644 --- a/frappe/tests/test_boot.py +++ b/frappe/tests/test_boot.py @@ -1,6 +1,6 @@ import frappe -from frappe.boot import get_unseen_notes, get_user_pages_or_reports -from frappe.desk.doctype.note.note import mark_as_seen +from frappe.boot import get_user_pages_or_reports +from frappe.desk.doctype.note.note import get_unseen_notes, mark_as_seen from frappe.tests import IntegrationTestCase diff --git a/frappe/utils/jinja.py b/frappe/utils/jinja.py index 6be55d7fcb..4e5a2b6a1a 100644 --- a/frappe/utils/jinja.py +++ b/frappe/utils/jinja.py @@ -1,5 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE +import frappe +from frappe.utils.caching import site_cache + + def get_jenv(): import frappe @@ -88,22 +92,23 @@ def render_template(template, context=None, is_path=None, safe_render=True): if context is None: context = {} - jenv: SandboxedEnvironment = get_jenv() - if is_path or guess_is_path(template): - is_path = True - compiled_template = jenv.get_template(template) - else: - if safe_render and ".__" in template: - throw(_("Illegal template")) - try: - compiled_template = jenv.from_string(template) - except TemplateError: - import html + try: + if is_path or guess_is_path(template): + is_path = True + compiled_template = compile_template(template) + else: + jenv: SandboxedEnvironment = get_jenv() + if safe_render and ".__" in template: + throw(_("Illegal template")) - throw( - title="Jinja Template Error", - msg=f"
{template}{html.escape(get_traceback())}",
- )
+ compiled_template = jenv.from_string(template)
+ except TemplateError:
+ import html
+
+ throw(
+ title="Jinja Template Error",
+ msg=f"{template}{html.escape(get_traceback())}",
+ )
import time
@@ -136,30 +141,38 @@ def guess_is_path(template):
def get_jloader():
+ jloader = _get_jloader()
+ frappe.local.jloader = jloader # backward compat
+ return jloader
+
+
+@site_cache(ttl=10 * 60, maxsize=16)
+def compile_template(path):
+ jenv = get_jenv()
+ return jenv.get_template(path)
+
+
+@site_cache(ttl=10 * 60, maxsize=8)
+def _get_jloader():
+ from jinja2 import ChoiceLoader, PackageLoader, PrefixLoader
+
import frappe
- if not getattr(frappe.local, "jloader", None):
- from jinja2 import ChoiceLoader, PackageLoader, PrefixLoader
+ apps = frappe.get_hooks("template_apps")
+ if not apps:
+ apps = list(reversed(frappe.get_installed_apps(_ensure_on_bench=True)))
- apps = frappe.get_hooks("template_apps")
- if not apps:
- apps = list(
- reversed(
- frappe.local.flags.web_pages_apps or frappe.get_installed_apps(_ensure_on_bench=True)
- )
- )
+ if "frappe" not in apps:
+ apps.append("frappe")
- if "frappe" not in apps:
- apps.append("frappe")
+ jloader = ChoiceLoader(
+ # search for something like app/templates/...
+ [PrefixLoader({app: PackageLoader(app, ".") for app in apps})]
+ # search for something like templates/...
+ + [PackageLoader(app, ".") for app in apps]
+ )
- frappe.local.jloader = ChoiceLoader(
- # search for something like app/templates/...
- [PrefixLoader({app: PackageLoader(app, ".") for app in apps})]
- # search for something like templates/...
- + [PackageLoader(app, ".") for app in apps]
- )
-
- return frappe.local.jloader
+ return jloader
def set_filters(jenv):
diff --git a/frappe/utils/user.py b/frappe/utils/user.py
index 872fa8dca8..355d968eb7 100644
--- a/frappe/utils/user.py
+++ b/frappe/utils/user.py
@@ -287,8 +287,8 @@ def get_user_fullname(user: str) -> str:
def get_fullname_and_avatar(user: str) -> _dict:
- first_name, last_name, avatar, name = frappe.db.get_value(
- "User", user, ["first_name", "last_name", "user_image", "name"], order_by=None
+ first_name, last_name, avatar, name = frappe.get_cached_value(
+ "User", user, ["first_name", "last_name", "user_image", "name"]
)
return _dict(
{
diff --git a/frappe/website/doctype/website_route_meta/website_route_meta.py b/frappe/website/doctype/website_route_meta/website_route_meta.py
index 9164196b13..0846ec130f 100644
--- a/frappe/website/doctype/website_route_meta/website_route_meta.py
+++ b/frappe/website/doctype/website_route_meta/website_route_meta.py
@@ -20,3 +20,9 @@ class WebsiteRouteMeta(Document):
def autoname(self):
if self.name and self.name.startswith("/"):
self.name = self.name[1:]
+
+ def clear_cache(self):
+ from frappe.website.website_components.metatags import has_meta_tags
+
+ has_meta_tags.clear_cache()
+ return super().clear_cache()
diff --git a/frappe/website/page_renderers/template_page.py b/frappe/website/page_renderers/template_page.py
index d3b2b07506..55243ab5db 100644
--- a/frappe/website/page_renderers/template_page.py
+++ b/frappe/website/page_renderers/template_page.py
@@ -148,7 +148,7 @@ class TemplatePage(BaseTemplatePage):
def setup_template_source(self):
"""Setup template source, frontmatter and markdown conversion"""
- self.source = self.get_raw_template()
+ self.original_source = self.source = self.get_raw_template()
self.extract_frontmatter()
self.convert_from_markdown()
@@ -233,7 +233,10 @@ class TemplatePage(BaseTemplatePage):
else:
safe_render = True
- html = frappe.render_template(self.source, self.context, safe_render=safe_render)
+ src_modified = self.source is not self.original_source
+ html = frappe.render_template(
+ self.source if src_modified else self.context.template, self.context, safe_render=safe_render
+ )
return html
diff --git a/frappe/website/router.py b/frappe/website/router.py
index ac0ecc951b..766d13fb6d 100644
--- a/frappe/website/router.py
+++ b/frappe/website/router.py
@@ -89,7 +89,7 @@ def get_pages(app=None):
if app:
apps = [app]
else:
- apps = frappe.local.flags.web_pages_apps or frappe.get_installed_apps()
+ apps = frappe.get_installed_apps()
for app in apps:
app_path = frappe.get_app_path(app)
diff --git a/frappe/website/utils.py b/frappe/website/utils.py
index e8587f551f..94a923729e 100644
--- a/frappe/website/utils.py
+++ b/frappe/website/utils.py
@@ -423,7 +423,7 @@ def get_frontmatter(string):
body = result.group(2)
return {
- "attributes": yaml.safe_load(frontmatter),
+ "attributes": yaml.safe_load(frontmatter) if frontmatter else "",
"body": body,
}
diff --git a/frappe/website/website_components/metatags.py b/frappe/website/website_components/metatags.py
index 3d35be5514..7708a5d42e 100644
--- a/frappe/website/website_components/metatags.py
+++ b/frappe/website/website_components/metatags.py
@@ -1,4 +1,5 @@
import frappe
+from frappe.utils.caching import site_cache
METATAGS = ("title", "description", "image", "author", "published_on")
@@ -58,14 +59,17 @@ class MetaTags:
route = self.path
if route == "":
# homepage
- route = frappe.db.get_single_value("Website Settings", "home_page")
+ route = frappe.get_website_settings("home_page")
- route_exists = (
- route and not route.endswith((".js", ".css")) and frappe.db.exists("Website Route Meta", route)
- )
+ route_exists = route and not route.endswith((".js", ".css")) and has_meta_tags(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)
+
+
+@site_cache(ttl=10 * 60, maxsize=16)
+def has_meta_tags(route):
+ return bool(frappe.db.exists("Website Route Meta", route))
diff --git a/frappe/www/app.py b/frappe/www/app.py
index 4414b5a849..114bd62292 100644
--- a/frappe/www/app.py
+++ b/frappe/www/app.py
@@ -23,7 +23,7 @@ def get_context(context):
frappe.msgprint(_("Log in to access this page."))
frappe.redirect(f"/login?{urlencode({'redirect-to': frappe.request.path})}")
- elif frappe.db.get_value("User", frappe.session.user, "user_type", order_by=None) == "Website User":
+ elif frappe.session.data.user_type == "Website User":
frappe.throw(_("You are not permitted to access this page."), frappe.PermissionError)
try: